chore: fix har date types (#26783)

This commit is contained in:
Pavel Feldman 2023-08-30 12:40:46 -07:00 committed by GitHub
parent d98e3a2bed
commit fd31f5bc50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 62 deletions

View File

@ -127,8 +127,9 @@ export class HarTracer {
return; return;
let pageEntry = this._pageEntries.get(page); let pageEntry = this._pageEntries.get(page);
if (!pageEntry) { if (!pageEntry) {
const date = new Date();
pageEntry = { pageEntry = {
startedDateTime: new Date(), startedDateTime: date.toISOString(),
id: page.guid, id: page.guid,
title: '', title: '',
pageTimings: this._options.omitTiming ? {} : { pageTimings: this._options.omitTiming ? {} : {
@ -136,6 +137,7 @@ export class HarTracer {
onLoad: -1, onLoad: -1,
}, },
}; };
(pageEntry as any)[startedDateSymbol] = date;
page.mainFrame().on(Frame.Events.AddLifecycle, (event: LifecycleEvent) => { page.mainFrame().on(Frame.Events.AddLifecycle, (event: LifecycleEvent) => {
if (event === 'load') if (event === 'load')
@ -221,7 +223,7 @@ export class HarTracer {
harEntry.response.cookies = this._options.omitCookies ? [] : event.cookies.map(c => { harEntry.response.cookies = this._options.omitCookies ? [] : event.cookies.map(c => {
return { return {
...c, ...c,
expires: c.expires === -1 ? undefined : new Date(c.expires) expires: c.expires === -1 ? undefined : new Date(c.expires).toISOString()
}; };
}); });
@ -456,9 +458,10 @@ export class HarTracer {
}; };
if (!this._options.omitTiming) { if (!this._options.omitTiming) {
const startDateTime = pageEntry ? ((pageEntry as any)[startedDateSymbol] as Date).valueOf() : 0;
const timing = response.timing(); const timing = response.timing();
if (pageEntry && pageEntry.startedDateTime.valueOf() > timing.startTime) if (pageEntry && startDateTime > timing.startTime)
pageEntry.startedDateTime = new Date(timing.startTime); pageEntry.startedDateTime = new Date(timing.startTime).toISOString();
const dns = timing.domainLookupEnd !== -1 ? helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1; const dns = timing.domainLookupEnd !== -1 ? helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1;
const connect = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1; const connect = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1;
const ssl = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1; const ssl = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1;
@ -535,12 +538,13 @@ export class HarTracer {
}; };
if (!this._options.omitTiming) { if (!this._options.omitTiming) {
for (const pageEntry of log.pages || []) { for (const pageEntry of log.pages || []) {
const startDateTime = ((pageEntry as any)[startedDateSymbol] as Date).valueOf();
if (typeof pageEntry.pageTimings.onContentLoad === 'number' && pageEntry.pageTimings.onContentLoad >= 0) if (typeof pageEntry.pageTimings.onContentLoad === 'number' && pageEntry.pageTimings.onContentLoad >= 0)
pageEntry.pageTimings.onContentLoad -= pageEntry.startedDateTime.valueOf(); pageEntry.pageTimings.onContentLoad -= startDateTime;
else else
pageEntry.pageTimings.onContentLoad = -1; pageEntry.pageTimings.onContentLoad = -1;
if (typeof pageEntry.pageTimings.onLoad === 'number' && pageEntry.pageTimings.onLoad >= 0) if (typeof pageEntry.pageTimings.onLoad === 'number' && pageEntry.pageTimings.onLoad >= 0)
pageEntry.pageTimings.onLoad -= pageEntry.startedDateTime.valueOf(); pageEntry.pageTimings.onLoad -= startDateTime;
else else
pageEntry.pageTimings.onLoad = -1; pageEntry.pageTimings.onLoad = -1;
} }
@ -597,7 +601,7 @@ function createHarEntry(method: string, url: URL, frameref: string | undefined,
const harEntry: har.Entry = { const harEntry: har.Entry = {
_frameref: options.includeTraceInfo ? frameref : undefined, _frameref: options.includeTraceInfo ? frameref : undefined,
_monotonicTime: options.includeTraceInfo ? monotonicTime() : undefined, _monotonicTime: options.includeTraceInfo ? monotonicTime() : undefined,
startedDateTime: new Date(), startedDateTime: new Date().toISOString(),
time: -1, time: -1,
request: { request: {
method: method, method: method,
@ -654,11 +658,11 @@ function parseCookie(c: string): har.Cookie {
if (name === 'Domain') if (name === 'Domain')
cookie.domain = value; cookie.domain = value;
if (name === 'Expires') if (name === 'Expires')
cookie.expires = new Date(value); cookie.expires = new Date(value).toISOString();
if (name === 'HttpOnly') if (name === 'HttpOnly')
cookie.httpOnly = true; cookie.httpOnly = true;
if (name === 'Max-Age') if (name === 'Max-Age')
cookie.expires = new Date(Date.now() + (+value) * 1000); cookie.expires = new Date(Date.now() + (+value) * 1000).toISOString();
if (name === 'Path') if (name === 'Path')
cookie.path = value; cookie.path = value;
if (name === 'SameSite') if (name === 'SameSite')
@ -668,3 +672,5 @@ function parseCookie(c: string): har.Cookie {
} }
return cookie; return cookie;
} }
const startedDateSymbol = Symbol('startedDate');

View File

@ -493,6 +493,8 @@ function visitTraceEvent(object: any, sha1s: Set<string>): any {
return object.map(o => visitTraceEvent(o, sha1s)); return object.map(o => visitTraceEvent(o, sha1s));
if (object instanceof Buffer) if (object instanceof Buffer)
return undefined; return undefined;
if (object instanceof Date)
return object;
if (typeof object === 'object') { if (typeof object === 'object') {
const result: any = {}; const result: any = {};
for (const key in object) { for (const key in object) {

View File

@ -29,12 +29,18 @@
background-color: var(--vscode-statusBarItem-remoteBackground); background-color: var(--vscode-statusBarItem-remoteBackground);
} }
.network-request-start {
flex: 0 0 65px;
justify-content: right;
padding-right: 10px;
}
.network-request-status { .network-request-status {
flex: 0 0 70px; flex: 0 0 65px;
} }
.network-request-method { .network-request-method {
flex: 0 0 70px; flex: 0 0 65px;
} }
.network-request-file { .network-request-file {
@ -49,7 +55,7 @@
} }
.network-request-content-type, .network-request-content-type,
.network-request-time, .network-request-duration,
.network-request-route, .network-request-route,
.network-request-size { .network-request-size {
overflow: hidden; overflow: hidden;
@ -81,6 +87,14 @@
display: flex; display: flex;
align-items: center; align-items: center;
white-space: nowrap; white-space: nowrap;
height: 100%;
}
.network-request-header.filter-start.positive .network-request-start .codicon-triangle-down {
display: initial !important;
}
.network-request-header.filter-start.negative .network-request-start .codicon-triangle-up {
display: initial !important;
} }
.network-request-header.filter-status.positive .network-request-status .codicon-triangle-down { .network-request-header.filter-status.positive .network-request-status .codicon-triangle-down {
@ -111,10 +125,10 @@
display: initial !important; display: initial !important;
} }
.network-request-header.filter-time.positive .network-request-time .codicon-triangle-down { .network-request-header.filter-duration.positive .network-request-duration .codicon-triangle-down {
display: initial !important; display: initial !important;
} }
.network-request-header.filter-time.negative .network-request-time .codicon-triangle-up { .network-request-header.filter-duration.negative .network-request-duration .codicon-triangle-up {
display: initial !important; display: initial !important;
} }

View File

@ -25,16 +25,17 @@ import { bytesToString, msToString } from '@web/uiUtils';
const NetworkListView = ListView<Entry>; const NetworkListView = ListView<Entry>;
type Filter = 'status' | 'method' | 'file' | 'time' | 'size' | 'content-type'; type SortBy = 'start' | 'status' | 'method' | 'file' | 'duration' | 'size' | 'content-type';
type Sorting = { by: SortBy, negate: boolean};
export const NetworkTab: React.FunctionComponent<{ export const NetworkTab: React.FunctionComponent<{
model: modelUtil.MultiTraceModel | undefined, model: modelUtil.MultiTraceModel | undefined,
boundaries: Boundaries,
selectedTime: Boundaries | undefined, selectedTime: Boundaries | undefined,
onEntryHovered: (entry: Entry | undefined) => void, onEntryHovered: (entry: Entry | undefined) => void,
}> = ({ model, selectedTime, onEntryHovered }) => { }> = ({ model, boundaries, selectedTime, onEntryHovered }) => {
const [resource, setResource] = React.useState<Entry | undefined>(); const [resource, setResource] = React.useState<Entry | undefined>();
const [filter, setFilter] = React.useState<Filter | undefined>(undefined); const [sorting, setSorting] = React.useState<Sorting | undefined>(undefined);
const [negateFilter, setNegateFilter] = React.useState<boolean>(false);
const resources = React.useMemo(() => { const resources = React.useMemo(() => {
const resources = model?.resources || []; const resources = model?.resources || [];
@ -43,27 +44,22 @@ export const NetworkTab: React.FunctionComponent<{
return true; return true;
return !!resource._monotonicTime && (resource._monotonicTime >= selectedTime.minimum && resource._monotonicTime <= selectedTime.maximum); return !!resource._monotonicTime && (resource._monotonicTime >= selectedTime.minimum && resource._monotonicTime <= selectedTime.maximum);
}); });
if (filter) if (sorting)
sort(filtered, filter, negateFilter); sort(filtered, sorting);
return filtered; return filtered;
}, [filter, model, negateFilter, selectedTime]); }, [sorting, model, selectedTime]);
const toggleFilter = React.useCallback((f: Filter) => { const toggleSorting = React.useCallback((f: SortBy) => {
if (filter === f) { setSorting({ by: f, negate: sorting?.by === f ? !sorting.negate : false });
setNegateFilter(!negateFilter); }, [sorting]);
} else {
setNegateFilter(false);
setFilter(f);
}
}, [filter, negateFilter]);
return <> return <>
{!resource && <div className='vbox'> {!resource && <div className='vbox'>
<NetworkHeader filter={filter} negateFilter={negateFilter} toggleFilter={toggleFilter} /> <NetworkHeader sorting={sorting} toggleSorting={toggleSorting} />
<NetworkListView <NetworkListView
dataTestId='network-request-list' dataTestId='network-request-list'
items={resources} items={resources}
render={entry => <NetworkResource resource={entry}></NetworkResource>} render={entry => <NetworkResource boundaries={boundaries} resource={entry}></NetworkResource>}
onSelected={setResource} onSelected={setResource}
onHighlighted={onEntryHovered} onHighlighted={onEntryHovered}
/> />
@ -73,37 +69,40 @@ export const NetworkTab: React.FunctionComponent<{
}; };
const NetworkHeader: React.FunctionComponent<{ const NetworkHeader: React.FunctionComponent<{
filter: Filter | undefined, sorting: Sorting | undefined,
negateFilter: boolean, toggleSorting: (sortBy: SortBy) => void,
toggleFilter: (filter: Filter) => void, }> = ({ toggleSorting: toggleSortBy, sorting }) => {
}> = ({ toggleFilter, filter, negateFilter }) => { return <div className={'hbox network-request-header' + (sorting ? ' filter-' + sorting.by + (sorting.negate ? ' negative' : ' positive') : '')}>
return <div className={'hbox network-request-header' + (filter ? ' filter-' + filter : '') + (negateFilter ? ' negative' : ' positive')}> <div className='network-request-start' onClick={() => toggleSortBy('start') }>
<div className='network-request-status' onClick={() => toggleFilter('status') }> <span className='codicon codicon-triangle-up' />
<span className='codicon codicon-triangle-down' />
</div>
<div className='network-request-status' onClick={() => toggleSortBy('status') }>
&nbsp;Status &nbsp;Status
<span className='codicon codicon-triangle-up' /> <span className='codicon codicon-triangle-up' />
<span className='codicon codicon-triangle-down' /> <span className='codicon codicon-triangle-down' />
</div> </div>
<div className='network-request-method' onClick={() => toggleFilter('method') }> <div className='network-request-method' onClick={() => toggleSortBy('method') }>
Method Method
<span className='codicon codicon-triangle-up' /> <span className='codicon codicon-triangle-up' />
<span className='codicon codicon-triangle-down' /> <span className='codicon codicon-triangle-down' />
</div> </div>
<div className='network-request-file' onClick={() => toggleFilter('file') }> <div className='network-request-file' onClick={() => toggleSortBy('file') }>
Request Request
<span className='codicon codicon-triangle-up' /> <span className='codicon codicon-triangle-up' />
<span className='codicon codicon-triangle-down' /> <span className='codicon codicon-triangle-down' />
</div> </div>
<div className='network-request-content-type' onClick={() => toggleFilter('content-type') }> <div className='network-request-content-type' onClick={() => toggleSortBy('content-type') }>
Content Type Content Type
<span className='codicon codicon-triangle-up' /> <span className='codicon codicon-triangle-up' />
<span className='codicon codicon-triangle-down' /> <span className='codicon codicon-triangle-down' />
</div> </div>
<div className='network-request-time' onClick={() => toggleFilter('time') }> <div className='network-request-duration' onClick={() => toggleSortBy('duration') }>
Time Duration
<span className='codicon codicon-triangle-up' /> <span className='codicon codicon-triangle-up' />
<span className='codicon codicon-triangle-down' /> <span className='codicon codicon-triangle-down' />
</div> </div>
<div className='network-request-size' onClick={() => toggleFilter('size') }> <div className='network-request-size' onClick={() => toggleSortBy('size') }>
Size Size
<span className='codicon codicon-triangle-up' /> <span className='codicon codicon-triangle-up' />
<span className='codicon codicon-triangle-down' /> <span className='codicon codicon-triangle-down' />
@ -114,7 +113,8 @@ const NetworkHeader: React.FunctionComponent<{
const NetworkResource: React.FunctionComponent<{ const NetworkResource: React.FunctionComponent<{
resource: Entry, resource: Entry,
}> = ({ resource }) => { boundaries: Boundaries,
}> = ({ resource, boundaries }) => {
const { routeStatus, resourceName, contentType } = React.useMemo(() => { const { routeStatus, resourceName, contentType } = React.useMemo(() => {
const routeStatus = formatRouteStatus(resource); const routeStatus = formatRouteStatus(resource);
const resourceName = resource.request.url.substring(resource.request.url.lastIndexOf('/')); const resourceName = resource.request.url.substring(resource.request.url.lastIndexOf('/'));
@ -126,6 +126,9 @@ const NetworkResource: React.FunctionComponent<{
}, [resource]); }, [resource]);
return <div className='hbox'> return <div className='hbox'>
<div className='hbox network-request-start'>
<div>{msToString(resource._monotonicTime! - boundaries.minimum)}</div>
</div>
<div className='hbox network-request-status'> <div className='hbox network-request-status'>
<div className={formatStatus(resource.response.status)} title={resource.response.statusText}>{resource.response.status}</div> <div className={formatStatus(resource.response.status)} title={resource.response.statusText}>{resource.response.status}</div>
</div> </div>
@ -136,7 +139,7 @@ const NetworkResource: React.FunctionComponent<{
<div className='network-request-file-url' title={resource.request.url}>{resourceName}</div> <div className='network-request-file-url' title={resource.request.url}>{resourceName}</div>
</div> </div>
<div className='network-request-content-type' title={contentType}>{contentType}</div> <div className='network-request-content-type' title={contentType}>{contentType}</div>
<div className='network-request-time'>{msToString(resource.time)}</div> <div className='network-request-duration'>{msToString(resource.time)}</div>
<div className='network-request-size'>{bytesToString(resource.response._transferSize! > 0 ? resource.response._transferSize! : resource.response.bodySize)}</div> <div className='network-request-size'>{bytesToString(resource.response._transferSize! > 0 ? resource.response._transferSize! : resource.response.bodySize)}</div>
<div className='network-request-route'> <div className='network-request-route'>
{routeStatus && <div className={`status-route ${routeStatus}`}>{routeStatus}</div>} {routeStatus && <div className={`status-route ${routeStatus}`}>{routeStatus}</div>}
@ -164,22 +167,25 @@ function formatRouteStatus(request: Entry): string {
return ''; return '';
} }
function sort(resources: Entry[], filter: Filter | undefined, negate: boolean) { function sort(resources: Entry[], sorting: Sorting) {
const c = comparator(filter); const c = comparator(sorting?.by);
if (c) if (c)
resources.sort(c); resources.sort(c);
if (negate) if (sorting.negate)
resources.reverse(); resources.reverse();
} }
function comparator(filter: Filter | undefined) { function comparator(sortBy: SortBy) {
if (filter === 'time') if (sortBy === 'start')
return (a: Entry, b: Entry) => a._monotonicTime! - b._monotonicTime!;
if (sortBy === 'duration')
return (a: Entry, b: Entry) => a.time - b.time; return (a: Entry, b: Entry) => a.time - b.time;
if (filter === 'status') if (sortBy === 'status')
return (a: Entry, b: Entry) => a.response.status - b.response.status; return (a: Entry, b: Entry) => a.response.status - b.response.status;
if (filter === 'method') { if (sortBy === 'method') {
return (a: Entry, b: Entry) => { return (a: Entry, b: Entry) => {
const valueA = a.request.method; const valueA = a.request.method;
const valueB = b.request.method; const valueB = b.request.method;
@ -187,7 +193,7 @@ function comparator(filter: Filter | undefined) {
}; };
} }
if (filter === 'size') { if (sortBy === 'size') {
return (a: Entry, b: Entry) => { return (a: Entry, b: Entry) => {
const sizeA = a.response._transferSize! > 0 ? a.response._transferSize! : a.response.bodySize; const sizeA = a.response._transferSize! > 0 ? a.response._transferSize! : a.response.bodySize;
const sizeB = b.response._transferSize! > 0 ? b.response._transferSize! : b.response.bodySize; const sizeB = b.response._transferSize! > 0 ? b.response._transferSize! : b.response.bodySize;
@ -195,7 +201,7 @@ function comparator(filter: Filter | undefined) {
}; };
} }
if (filter === 'content-type') { if (sortBy === 'content-type') {
return (a: Entry, b: Entry) => { return (a: Entry, b: Entry) => {
const valueA = a.response.content.mimeType; const valueA = a.response.content.mimeType;
const valueB = b.response.content.mimeType; const valueB = b.response.content.mimeType;
@ -203,7 +209,7 @@ function comparator(filter: Filter | undefined) {
}; };
} }
if (filter === 'file') { if (sortBy === 'file') {
return (a: Entry, b: Entry) => { return (a: Entry, b: Entry) => {
const nameA = a.request.url.substring(a.request.url.lastIndexOf('/')); const nameA = a.request.url.substring(a.request.url.lastIndexOf('/'));
const nameB = b.request.url.substring(b.request.url.lastIndexOf('/')); const nameB = b.request.url.substring(b.request.url.lastIndexOf('/'));

View File

@ -73,7 +73,7 @@ export const Timeline: React.FunctionComponent<{
rightTime: entry.endTime || boundaries.maximum, rightTime: entry.endTime || boundaries.maximum,
leftPosition: timeToPosition(measure.width, boundaries, entry.startTime), leftPosition: timeToPosition(measure.width, boundaries, entry.startTime),
rightPosition: timeToPosition(measure.width, boundaries, entry.endTime || boundaries.maximum), rightPosition: timeToPosition(measure.width, boundaries, entry.endTime || boundaries.maximum),
active: highlightedAction === entry, active: false,
}); });
} }
@ -86,11 +86,16 @@ export const Timeline: React.FunctionComponent<{
rightTime: endTime, rightTime: endTime,
leftPosition: timeToPosition(measure.width, boundaries, startTime), leftPosition: timeToPosition(measure.width, boundaries, startTime),
rightPosition: timeToPosition(measure.width, boundaries, endTime), rightPosition: timeToPosition(measure.width, boundaries, endTime),
active: highlightedEntry === resource, active: false,
}); });
} }
return bars; return bars;
}, [model, boundaries, measure, highlightedAction, highlightedEntry]); }, [model, boundaries, measure]);
React.useMemo(() => {
for (const bar of bars)
bar.active = (!!highlightedAction && bar.action === highlightedAction) || (!!highlightedEntry && bar.resource === highlightedEntry);
}, [bars, highlightedAction, highlightedEntry]);
const onMouseDown = React.useCallback((event: React.MouseEvent) => { const onMouseDown = React.useCallback((event: React.MouseEvent) => {
setPreviewPoint(undefined); setPreviewPoint(undefined);

View File

@ -124,7 +124,7 @@ export const Workbench: React.FunctionComponent<{
const networkTab: TabbedPaneTabModel = { const networkTab: TabbedPaneTabModel = {
id: 'network', id: 'network',
title: 'Network', title: 'Network',
render: () => <NetworkTab model={model} selectedTime={selectedTime} onEntryHovered={setHighlightedEntry}/> render: () => <NetworkTab model={model} boundaries={boundaries} selectedTime={selectedTime} onEntryHovered={setHighlightedEntry}/>
}; };
const attachmentsTab: TabbedPaneTabModel = { const attachmentsTab: TabbedPaneTabModel = {
id: 'attachments', id: 'attachments',

View File

@ -41,7 +41,7 @@ export type Browser = {
}; };
export type Page = { export type Page = {
startedDateTime: Date; startedDateTime: string;
id: string; id: string;
title: string; title: string;
pageTimings: PageTimings; pageTimings: PageTimings;
@ -56,7 +56,7 @@ export type PageTimings = {
export type Entry = { export type Entry = {
pageref?: string; pageref?: string;
startedDateTime: Date; startedDateTime: string;
time: number; time: number;
request: Request; request: Request;
response: Response; response: Response;
@ -107,7 +107,7 @@ export type Cookie = {
value: string; value: string;
path?: string; path?: string;
domain?: string; domain?: string;
expires?: Date; expires?: string;
httpOnly?: boolean; httpOnly?: boolean;
secure?: boolean; secure?: boolean;
sameSite?: string; sameSite?: string;