fix: miscellaneous improvements for tracing UI (#23558)
- feat(tracing): mark API requests with "API" label - feat(tracing): do not attribute any resources to `route.` API calls; otherwise, network traffic might get inside the `route.` actions. - fix(tracing): map actionIds from primary contexts to actionIds from non-primary contexts - fix(tracing): show leading `/` in URL path in network panel This is a result of a pair-programming session with @pavelfeldman
This commit is contained in:
parent
0f4090472c
commit
2e327c9d0c
|
|
@ -196,6 +196,7 @@ export class HarTracer {
|
||||||
if (!this._shouldIncludeEntryWithUrl(event.url.toString()))
|
if (!this._shouldIncludeEntryWithUrl(event.url.toString()))
|
||||||
return;
|
return;
|
||||||
const harEntry = createHarEntry(event.method, event.url, undefined, this._options);
|
const harEntry = createHarEntry(event.method, event.url, undefined, this._options);
|
||||||
|
harEntry._apiRequest = true;
|
||||||
if (!this._options.omitCookies)
|
if (!this._options.omitCookies)
|
||||||
harEntry.request.cookies = event.cookies;
|
harEntry.request.cookies = event.cookies;
|
||||||
harEntry.request.headers = Object.entries(event.headers).map(([name, value]) => ({ name, value }));
|
harEntry.request.headers = Object.entries(event.headers).map(([name, value]) => ({ name, value }));
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,13 @@ function indexModel(context: ContextEntry) {
|
||||||
for (let i = 0; i < context.actions.length; ++i) {
|
for (let i = 0; i < context.actions.length; ++i) {
|
||||||
const action = context.actions[i] as any;
|
const action = context.actions[i] as any;
|
||||||
action[contextSymbol] = context;
|
action[contextSymbol] = context;
|
||||||
action[nextInContextSymbol] = context.actions[i + 1];
|
}
|
||||||
|
let lastNonRouteAction = undefined;
|
||||||
|
for (let i = context.actions.length - 1; i >= 0; i--) {
|
||||||
|
const action = context.actions[i] as any;
|
||||||
|
action[nextInContextSymbol] = lastNonRouteAction;
|
||||||
|
if (!action.apiName.includes('route.'))
|
||||||
|
lastNonRouteAction = action;
|
||||||
}
|
}
|
||||||
for (const event of context.events)
|
for (const event of context.events)
|
||||||
(event as any)[contextSymbol] = context;
|
(event as any)[contextSymbol] = context;
|
||||||
|
|
@ -109,6 +115,7 @@ function mergeActions(contexts: ContextEntry[]) {
|
||||||
offset = context.actions[0].startTime - context.actions[0].wallTime;
|
offset = context.actions[0].startTime - context.actions[0].wallTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nonPrimaryIdToPrimaryId = new Map<string, string>();
|
||||||
for (const context of nonPrimaryContexts) {
|
for (const context of nonPrimaryContexts) {
|
||||||
for (const action of context.actions) {
|
for (const action of context.actions) {
|
||||||
if (offset) {
|
if (offset) {
|
||||||
|
|
@ -122,12 +129,13 @@ function mergeActions(contexts: ContextEntry[]) {
|
||||||
const key = `${action.apiName}@${action.wallTime}`;
|
const key = `${action.apiName}@${action.wallTime}`;
|
||||||
const existing = map.get(key);
|
const existing = map.get(key);
|
||||||
if (existing && existing.apiName === action.apiName) {
|
if (existing && existing.apiName === action.apiName) {
|
||||||
|
nonPrimaryIdToPrimaryId.set(action.callId, existing.callId);
|
||||||
if (action.error)
|
if (action.error)
|
||||||
existing.error = action.error;
|
existing.error = action.error;
|
||||||
if (action.attachments)
|
if (action.attachments)
|
||||||
existing.attachments = action.attachments;
|
existing.attachments = action.attachments;
|
||||||
if (action.parentId)
|
if (action.parentId)
|
||||||
existing.parentId = action.parentId;
|
existing.parentId = nonPrimaryIdToPrimaryId.get(action.parentId) ?? action.parentId;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
map.set(key, { ...action, context });
|
map.set(key, { ...action, context });
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-request-title-status.status-failure {
|
.network-request-title-status.status-failure {
|
||||||
|
|
@ -51,6 +53,11 @@
|
||||||
background-color: var(--vscode-statusBar-background);
|
background-color: var(--vscode-statusBar-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.network-request-title-status.status-route.api {
|
||||||
|
color: var(--vscode-statusBar-foreground);
|
||||||
|
background-color: var(--vscode-statusBarItem-remoteBackground);
|
||||||
|
}
|
||||||
|
|
||||||
.network-request-title-url {
|
.network-request-title-url {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export const NetworkResourceDetails: React.FunctionComponent<{
|
||||||
const routeStatus = formatRouteStatus(resource);
|
const routeStatus = formatRouteStatus(resource);
|
||||||
const requestContentTypeHeader = resource.request.headers.find(q => q.name === 'Content-Type');
|
const requestContentTypeHeader = resource.request.headers.find(q => q.name === 'Content-Type');
|
||||||
const requestContentType = requestContentTypeHeader ? requestContentTypeHeader.value : '';
|
const requestContentType = requestContentTypeHeader ? requestContentTypeHeader.value : '';
|
||||||
const resourceName = resource.request.url.substring(resource.request.url.lastIndexOf('/') + 1);
|
const resourceName = resource.request.url.substring(resource.request.url.lastIndexOf('/'));
|
||||||
let contentType = resource.response.content.mimeType;
|
let contentType = resource.response.content.mimeType;
|
||||||
const charset = contentType.match(/^(.*);\s*charset=.*$/);
|
const charset = contentType.match(/^(.*);\s*charset=.*$/);
|
||||||
if (charset)
|
if (charset)
|
||||||
|
|
@ -79,7 +79,7 @@ export const NetworkResourceDetails: React.FunctionComponent<{
|
||||||
|
|
||||||
const renderTitle = React.useCallback(() => {
|
const renderTitle = React.useCallback(() => {
|
||||||
return <div className='network-request-title'>
|
return <div className='network-request-title'>
|
||||||
{routeStatus && <div className={'network-request-title-status status-route'}>{routeStatus}</div> }
|
{routeStatus && <div className={`network-request-title-status status-route ${routeStatus}`}>{routeStatus}</div> }
|
||||||
{resource.response._failureText && <div className={'network-request-title-status status-failure'}>{resource.response._failureText}</div>}
|
{resource.response._failureText && <div className={'network-request-title-status status-failure'}>{resource.response._failureText}</div>}
|
||||||
{!resource.response._failureText && <div className={'network-request-title-status ' + formatStatus(resource.response.status)}>{resource.response.status}</div>}
|
{!resource.response._failureText && <div className={'network-request-title-status ' + formatStatus(resource.response.status)}>{resource.response.status}</div>}
|
||||||
<div className='network-request-title-status'>{resource.request.method}</div>
|
<div className='network-request-title-status'>{resource.request.method}</div>
|
||||||
|
|
@ -147,5 +147,7 @@ function formatRouteStatus(request: Entry): string {
|
||||||
return 'continued';
|
return 'continued';
|
||||||
if (request._wasFulfilled)
|
if (request._wasFulfilled)
|
||||||
return 'fulfilled';
|
return 'fulfilled';
|
||||||
|
if (request._apiRequest)
|
||||||
|
return 'api';
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ export type Entry = {
|
||||||
_wasAborted?: boolean;
|
_wasAborted?: boolean;
|
||||||
_wasFulfilled?: boolean;
|
_wasFulfilled?: boolean;
|
||||||
_wasContinued?: boolean;
|
_wasContinued?: boolean;
|
||||||
|
_apiRequest?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Request = {
|
export type Request = {
|
||||||
|
|
|
||||||
|
|
@ -216,9 +216,9 @@ test('should have network requests', async ({ showTraceViewer }) => {
|
||||||
const traceViewer = await showTraceViewer([traceFile]);
|
const traceViewer = await showTraceViewer([traceFile]);
|
||||||
await traceViewer.selectAction('http://localhost');
|
await traceViewer.selectAction('http://localhost');
|
||||||
await traceViewer.showNetworkTab();
|
await traceViewer.showNetworkTab();
|
||||||
await expect(traceViewer.networkRequests).toContainText([/200GETframe.htmltext\/html/]);
|
await expect(traceViewer.networkRequests).toContainText([/200GET\/frame.htmltext\/html/]);
|
||||||
await expect(traceViewer.networkRequests).toContainText([/200GETstyle.csstext\/css/]);
|
await expect(traceViewer.networkRequests).toContainText([/200GET\/style.csstext\/css/]);
|
||||||
await expect(traceViewer.networkRequests).toContainText([/200GETscript.jsapplication\/javascript/]);
|
await expect(traceViewer.networkRequests).toContainText([/200GET\/script.jsapplication\/javascript/]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should have network request overrides', async ({ page, server, runAndTrace }) => {
|
test('should have network request overrides', async ({ page, server, runAndTrace }) => {
|
||||||
|
|
@ -228,7 +228,7 @@ test('should have network request overrides', async ({ page, server, runAndTrace
|
||||||
});
|
});
|
||||||
await traceViewer.selectAction('http://localhost');
|
await traceViewer.selectAction('http://localhost');
|
||||||
await traceViewer.showNetworkTab();
|
await traceViewer.showNetworkTab();
|
||||||
await expect(traceViewer.networkRequests).toContainText([/200GETframe.htmltext\/html/]);
|
await expect(traceViewer.networkRequests).toContainText([/200GET\/frame.htmltext\/html/]);
|
||||||
await expect(traceViewer.networkRequests).toContainText([/abort.*style.cssx-unknown/]);
|
await expect(traceViewer.networkRequests).toContainText([/abort.*style.cssx-unknown/]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -239,7 +239,7 @@ test('should have network request overrides 2', async ({ page, server, runAndTra
|
||||||
});
|
});
|
||||||
await traceViewer.selectAction('http://localhost');
|
await traceViewer.selectAction('http://localhost');
|
||||||
await traceViewer.showNetworkTab();
|
await traceViewer.showNetworkTab();
|
||||||
await expect(traceViewer.networkRequests).toContainText([/200GETframe.htmltext\/html/]);
|
await expect(traceViewer.networkRequests).toContainText([/200GET\/frame.htmltext\/html/]);
|
||||||
await expect(traceViewer.networkRequests).toContainText([/continue.*script.jsapplication\/javascript/]);
|
await expect(traceViewer.networkRequests).toContainText([/continue.*script.jsapplication\/javascript/]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue