Skip to content

Commit

Permalink
feat(trace-viewer): show nework request source id (#30810)
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-s committed May 15, 2024
1 parent 89cdf3d commit 2734a05
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 8 deletions.
4 changes: 3 additions & 1 deletion packages/trace-viewer/src/ui/modelUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ function indexModel(context: ContextEntry) {
}
for (const event of context.events)
(event as any)[contextSymbol] = context;
for (const resource of context.resources)
(resource as any)[contextSymbol] = context;
}

function mergeActionsAndUpdateTiming(contexts: ContextEntry[]) {
Expand Down Expand Up @@ -330,7 +332,7 @@ export function idForAction(action: ActionTraceEvent) {
return `${action.pageId || 'none'}:${action.callId}`;
}

export function context(action: ActionTraceEvent | trace.EventTraceEvent): ContextEntry {
export function context(action: ActionTraceEvent | trace.EventTraceEvent | ResourceSnapshot): ContextEntry {
return (action as any)[contextSymbol];
}

Expand Down
92 changes: 85 additions & 7 deletions packages/trace-viewer/src/ui/networkTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ import './networkTab.css';
import { NetworkResourceDetails } from './networkResourceDetails';
import { bytesToString, msToString } from '@web/uiUtils';
import { PlaceholderPanel } from './placeholderPanel';
import type { MultiTraceModel } from './modelUtil';
import { context, type MultiTraceModel } from './modelUtil';
import { GridView, type RenderedGridCell } from '@web/components/gridView';
import { SplitView } from '@web/components/splitView';
import type { ContextEntry } from '../entries';

type NetworkTabModel = {
resources: Entry[],
contextIdMap: ContextIdMap,
};

type RenderedEntry = {
Expand All @@ -39,6 +41,7 @@ type RenderedEntry = {
start: number,
route: string,
resource: Entry,
contextId: string,
};
type ColumnName = keyof RenderedEntry;
type Sorting = { by: ColumnName, negate: boolean};
Expand All @@ -54,7 +57,8 @@ export function useNetworkTabModel(model: MultiTraceModel | undefined, selectedT
});
return filtered;
}, [model, selectedTime]);
return { resources };
const contextIdMap = React.useMemo(() => new ContextIdMap(model), [model]);
return { resources, contextIdMap };
}

export const NetworkTab: React.FunctionComponent<{
Expand All @@ -66,11 +70,11 @@ export const NetworkTab: React.FunctionComponent<{
const [selectedEntry, setSelectedEntry] = React.useState<RenderedEntry | undefined>(undefined);

const { renderedEntries } = React.useMemo(() => {
const renderedEntries = networkModel.resources.map(entry => renderEntry(entry, boundaries));
const renderedEntries = networkModel.resources.map(entry => renderEntry(entry, boundaries, networkModel.contextIdMap));
if (sorting)
sort(renderedEntries, sorting);
return { renderedEntries };
}, [networkModel.resources, sorting, boundaries]);
}, [networkModel.resources, networkModel.contextIdMap, sorting, boundaries]);

if (!networkModel.resources.length)
return <PlaceholderPanel text='No network calls' />;
Expand All @@ -81,7 +85,7 @@ export const NetworkTab: React.FunctionComponent<{
selectedItem={selectedEntry}
onSelected={item => setSelectedEntry(item)}
onHighlighted={item => onEntryHovered(item?.resource)}
columns={selectedEntry ? ['name'] : ['name', 'method', 'status', 'contentType', 'duration', 'size', 'start', 'route']}
columns={visibleColumns(!!selectedEntry, renderedEntries)}
columnTitle={columnTitle}
columnWidth={columnWidth}
isError={item => item.status.code >= 400}
Expand All @@ -100,6 +104,8 @@ export const NetworkTab: React.FunctionComponent<{
};

const columnTitle = (column: ColumnName) => {
if (column === 'contextId')
return 'Source';
if (column === 'name')
return 'Name';
if (column === 'method')
Expand Down Expand Up @@ -128,10 +134,28 @@ const columnWidth = (column: ColumnName) => {
return 60;
if (column === 'contentType')
return 200;
if (column === 'contextId')
return 60;
return 100;
};

function visibleColumns(entrySelected: boolean, renderedEntries: RenderedEntry[]): (keyof RenderedEntry)[] {
if (entrySelected)
return ['name'];
const columns: (keyof RenderedEntry)[] = [];
if (hasMultipleContexts(renderedEntries))
columns.push('contextId');
columns.push('name', 'method', 'status', 'contentType', 'duration', 'size', 'start', 'route');
return columns;
}

const renderCell = (entry: RenderedEntry, column: ColumnName): RenderedGridCell => {
if (column === 'contextId') {
return {
body: entry.contextId,
title: entry.name.url,
};
}
if (column === 'name') {
return {
body: entry.name.name,
Expand Down Expand Up @@ -159,7 +183,57 @@ const renderCell = (entry: RenderedEntry, column: ColumnName): RenderedGridCell
return { body: '' };
};

const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry => {
class ContextIdMap {
private _pagerefToShortId = new Map<string, string>();
private _contextToId = new Map<ContextEntry, string>();
private _lastPageId = 0;
private _lastApiRequestContextId = 0;

constructor(model: MultiTraceModel | undefined) {}

contextId(resource: Entry): string {
if (resource.pageref)
return this._pageId(resource.pageref);
else if (resource._apiRequest)
return this._apiRequestContextId(resource);
return '';
}

private _pageId(pageref: string): string {
let shortId = this._pagerefToShortId.get(pageref);
if (!shortId) {
++this._lastPageId;
shortId = 'page#' + this._lastPageId;
this._pagerefToShortId.set(pageref, shortId);
}
return shortId;
}

private _apiRequestContextId(resource: Entry): string {
const contextEntry = context(resource);
if (!contextEntry)
return '';
let contextId = this._contextToId.get(contextEntry);
if (!contextId) {
++this._lastApiRequestContextId;
contextId = 'api#' + this._lastApiRequestContextId;
this._contextToId.set(contextEntry, contextId);
}
return contextId;
}
}

function hasMultipleContexts(renderedEntries: RenderedEntry[]): boolean {
const contextIds = new Set<string>();
for (const entry of renderedEntries) {
contextIds.add(entry.contextId);
if (contextIds.size > 1)
return true;
}
return false;
}

const renderEntry = (resource: Entry, boundaries: Boundaries, contextIdGenerator: ContextIdMap): RenderedEntry => {
const routeStatus = formatRouteStatus(resource);
let resourceName: string;
try {
Expand All @@ -184,7 +258,8 @@ const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry =>
size: resource.response._transferSize! > 0 ? resource.response._transferSize! : resource.response.bodySize,
start: resource._monotonicTime! - boundaries.minimum,
route: routeStatus,
resource
resource,
contextId: contextIdGenerator.contextId(resource),
};
};

Expand Down Expand Up @@ -249,4 +324,7 @@ function comparator(sortBy: ColumnName) {
return a.route.localeCompare(b.route);
};
}

if (sortBy === 'contextId')
return (a: RenderedEntry, b: RenderedEntry) => a.contextId.localeCompare(b.contextId);
}
21 changes: 21 additions & 0 deletions tests/playwright-test/ui-mode-trace.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,24 @@ test('should reveal errors in the sourcetab', async ({ runUITest }) => {
await page.getByText('a.spec.ts:4', { exact: true }).click();
await expect(page.locator('.source-line-running')).toContainText(`throw new Error('Oh my');`);
});

test('should show request source context id', async ({ runUITest, server }) => {
const { page } = await runUITest({
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('pass', async ({ page, context, request }) => {
await page.goto('${server.EMPTY_PAGE}');
const page2 = await context.newPage();
await page2.goto('${server.EMPTY_PAGE}');
await request.get('${server.EMPTY_PAGE}');
});
`,
});

await page.getByText('pass').dblclick();
await page.getByText('Network', { exact: true }).click();
await expect(page.locator('span').filter({ hasText: 'Source' })).toBeVisible();
await expect(page.getByText('page#1')).toBeVisible();
await expect(page.getByText('page#2')).toBeVisible();
await expect(page.getByText('api#1')).toBeVisible();
});

0 comments on commit 2734a05

Please sign in to comment.