Skip to content

Commit

Permalink
chore: filter actions, console and network based on the timeline wind…
Browse files Browse the repository at this point in the history
…ow (#26509)
  • Loading branch information
pavelfeldman authored Aug 16, 2023
1 parent 0149c7d commit a705d68
Show file tree
Hide file tree
Showing 19 changed files with 334 additions and 270 deletions.
14 changes: 9 additions & 5 deletions packages/trace-viewer/src/ui/actionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import type { Language } from '@isomorphic/locatorGenerators';
import type { TreeState } from '@web/components/treeView';
import { TreeView } from '@web/components/treeView';
import type { ActionTraceEventInContext, ActionTreeItem } from './modelUtil';
import type { Boundaries } from '../geometry';

export interface ActionListProps {
actions: ActionTraceEventInContext[],
selectedAction: ActionTraceEventInContext | undefined,
selectedTime: Boundaries | undefined,
sdkLanguage: Language | undefined;
onSelected: (action: ActionTraceEventInContext) => void,
onHighlighted: (action: ActionTraceEventInContext | undefined) => void,
Expand All @@ -40,6 +42,7 @@ const ActionTreeView = TreeView<ActionTreeItem>;
export const ActionList: React.FC<ActionListProps> = ({
actions,
selectedAction,
selectedTime,
sdkLanguage,
onSelected,
onHighlighted,
Expand All @@ -63,15 +66,16 @@ export const ActionList: React.FC<ActionListProps> = ({
onSelected={item => onSelected(item.action!)}
onHighlighted={item => onHighlighted(item?.action)}
isError={item => !!item.action?.error?.message}
isVisible={item => !selectedTime || (item.action!.startTime <= selectedTime.maximum && item.action!.endTime >= selectedTime.minimum)}
render={item => renderAction(item.action!, sdkLanguage, revealConsole, isLive || false)}
/>;
};

const renderAction = (
export const renderAction = (
action: ActionTraceEvent,
sdkLanguage: Language | undefined,
revealConsole: () => void,
isLive: boolean,
sdkLanguage?: Language,
revealConsole?: () => void,
isLive?: boolean,
) => {
const { errors, warnings } = modelUtil.stats(action);
const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector, false /* isFrameLocator */, true /* playSafe */) : undefined;
Expand All @@ -90,7 +94,7 @@ const renderAction = (
{action.method === 'goto' && action.params.url && <div className='action-url' title={action.params.url}>{action.params.url}</div>}
</div>
<div className='action-duration' style={{ flex: 'none' }}>{time || <span className='codicon codicon-loading'></span>}</div>
<div className='action-icons' onClick={() => revealConsole()}>
<div className='action-icons' onClick={() => revealConsole?.()}>
{!!errors && <div className='action-icon'><span className='codicon codicon-error'></span><span className="action-icon-value">{errors}</span></div>}
{!!warnings && <div className='action-icon'><span className='codicon codicon-warning'></span><span className="action-icon-value">{warnings}</span></div>}
</div>
Expand Down
25 changes: 12 additions & 13 deletions packages/trace-viewer/src/ui/consoleTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
*/

import type * as channels from '@protocol/channels';
import type { ActionTraceEvent } from '@trace/trace';
import * as React from 'react';
import './consoleTab.css';
import * as modelUtil from './modelUtil';
import { ListView } from '@web/components/listView';
import { ansi2htmlMarkup } from '@web/components/errorMessage';
import type { Boundaries } from '../geometry';

type ConsoleEntry = {
message?: channels.ConsoleMessageInitializer;
Expand All @@ -31,35 +31,31 @@ type ConsoleEntry = {
isError: boolean;
},
timestamp: number;
highlight: boolean;
};

const ConsoleListView = ListView<ConsoleEntry>;

export const ConsoleTab: React.FunctionComponent<{
model: modelUtil.MultiTraceModel | undefined,
action: ActionTraceEvent | undefined,
}> = ({ model, action }) => {
selectedTime: Boundaries | undefined,
}> = ({ model, selectedTime }) => {
const { entries } = React.useMemo(() => {
if (!model)
return { entries: [] };
const entries: ConsoleEntry[] = [];
const actionEvents = action ? modelUtil.eventsForAction(action) : [];
for (const event of model.events) {
if (event.method !== 'console' && event.method !== 'pageError')
continue;
if (event.method === 'console') {
const { guid } = event.params.message;
entries.push({
message: modelUtil.context(event).initializers[guid],
highlight: actionEvents.includes(event),
timestamp: event.time,
});
}
if (event.method === 'pageError') {
entries.push({
error: event.params.error,
highlight: actionEvents.includes(event),
timestamp: event.time,
});
}
Expand All @@ -72,16 +68,21 @@ export const ConsoleTab: React.FunctionComponent<{
isError: event.type === 'stderr',
},
timestamp: event.timestamp,
highlight: false,
});
}
entries.sort((a, b) => a.timestamp - b.timestamp);
return { entries };
}, [model, action]);
}, [model]);

const filteredEntries = React.useMemo(() => {
if (!selectedTime)
return entries;
return entries.filter(entry => entry.timestamp >= selectedTime.minimum && entry.timestamp <= selectedTime.maximum);
}, [entries, selectedTime]);

return <div className='console-tab'>
<ConsoleListView
items={entries}
items={filteredEntries}
isError={entry => !!entry.error || entry.message?.type === 'error' || entry.nodeMessage?.isError || false}
isWarning={entry => entry.message?.type === 'warning'}
render={entry => {
Expand Down Expand Up @@ -122,9 +123,7 @@ export const ConsoleTab: React.FunctionComponent<{
</div>;
}
return null;
}
}
isHighlighted={entry => !!entry.highlight}
}}
/>
</div>;
};
Expand Down
8 changes: 6 additions & 2 deletions packages/trace-viewer/src/ui/filmStrip.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,15 @@
.film-strip-hover {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: white;
box-shadow: rgba(0, 0, 0, 0.133) 0px 1.6px 10px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 10px 0px;
z-index: 200;
pointer-events: none;
}

.film-strip-hover-title {
padding: 2px 4px;
display: flex;
align-items: center;
}
26 changes: 20 additions & 6 deletions packages/trace-viewer/src/ui/filmStrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@ import type { Boundaries, Size } from '../geometry';
import * as React from 'react';
import { useMeasure, upperBound } from '@web/uiUtils';
import type { PageEntry } from '../entries';
import type { MultiTraceModel } from './modelUtil';
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
import { renderAction } from './actionList';
import type { Language } from '@isomorphic/locatorGenerators';

export type FilmStripPreviewPoint = {
x: number;
clientY: number;
action?: ActionTraceEventInContext;
sdkLanguage: Language;
};

const tileSize = { width: 200, height: 45 };
const frameMargin = 2.5;
Expand All @@ -28,7 +37,7 @@ const rowHeight = tileSize.height + frameMargin * 2;
export const FilmStrip: React.FunctionComponent<{
model?: MultiTraceModel,
boundaries: Boundaries,
previewPoint?: { x: number, clientY: number },
previewPoint?: FilmStripPreviewPoint,
}> = ({ model, boundaries, previewPoint }) => {
const [measure, ref] = useMeasure<HTMLDivElement>();
const lanesRef = React.useRef<HTMLDivElement>(null);
Expand All @@ -45,7 +54,11 @@ export const FilmStrip: React.FunctionComponent<{
if (previewPoint !== undefined && screencastFrames) {
const previewTime = boundaries.minimum + (boundaries.maximum - boundaries.minimum) * previewPoint.x / measure.width;
previewImage = screencastFrames[upperBound(screencastFrames, previewTime, timeComparator) - 1];
previewSize = previewImage ? inscribe({ width: previewImage.width, height: previewImage.height }, { width: (window.innerWidth * 3 / 4) | 0, height: (window.innerHeight * 3 / 4) | 0 }) : undefined;
const fitInto = {
width: Math.min(500, (window.innerWidth / 2) | 0),
height: Math.min(500, (window.innerHeight / 2) | 0),
};
previewSize = previewImage ? inscribe({ width: previewImage.width, height: previewImage.height }, fitInto) : undefined;
}

return <div className='film-strip' ref={ref}>
Expand All @@ -59,12 +72,13 @@ export const FilmStrip: React.FunctionComponent<{
}</div>
{previewImage && previewSize && previewPoint?.x !== undefined &&
<div className='film-strip-hover' style={{
width: previewSize.width,
height: previewSize.height,
top: measure.bottom + 5,
left: Math.min(previewPoint!.x, measure.width - previewSize.width - 10),
}}>
<img src={`sha1/${previewImage.sha1}`} width={previewSize.width} height={previewSize.height} />
<div style={{ width: previewSize.width, height: previewSize.height }}>
<img src={`sha1/${previewImage.sha1}`} width={previewSize.width} height={previewSize.height} />
</div>
{previewPoint.action && <div className='film-strip-hover-title'>{renderAction(previewPoint.action, previewPoint.sdkLanguage)}</div>}
</div>
}
</div>;
Expand Down
15 changes: 1 addition & 14 deletions packages/trace-viewer/src/ui/modelUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const contextSymbol = Symbol('context');
const nextInContextSymbol = Symbol('next');
const prevInListSymbol = Symbol('prev');
const eventsSymbol = Symbol('events');
const resourcesSymbol = Symbol('resources');

export type SourceLocation = {
file: string;
Expand Down Expand Up @@ -87,6 +86,7 @@ export class MultiTraceModel {
this.resources = [...contexts.map(c => c.resources)].flat();

this.events.sort((a1, a2) => a1.time - a2.time);
this.resources.sort((a1, a2) => a1._monotonicTime! - a2._monotonicTime!);
this.sources = collectSources(this.actions);
}
}
Expand Down Expand Up @@ -239,19 +239,6 @@ export function eventsForAction(action: ActionTraceEvent): EventTraceEvent[] {
return result;
}

export function resourcesForAction(action: ActionTraceEvent): ResourceSnapshot[] {
let result: ResourceSnapshot[] = (action as any)[resourcesSymbol];
if (result)
return result;

const nextAction = nextInContext(action);
result = context(action).resources.filter(resource => {
return typeof resource._monotonicTime === 'number' && resource._monotonicTime > action.startTime && (!nextAction || resource._monotonicTime < nextAction.startTime);
});
(action as any)[resourcesSymbol] = result;
return result;
}

function collectSources(actions: trace.ActionTraceEvent[]): Map<string, SourceModel> {
const result = new Map<string, SourceModel>();
for (const action of actions) {
Expand Down
4 changes: 0 additions & 4 deletions packages/trace-viewer/src/ui/networkResourceDetails.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@
flex: 1;
}

.network-request.highlighted {
background-color: var(--vscode-list-inactiveSelectionBackground);
}

.network-request-title-status {
padding: 0 2px;
border-radius: 4px;
Expand Down
5 changes: 2 additions & 3 deletions packages/trace-viewer/src/ui/networkResourceDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import type { Entry } from '@trace/har';

export const NetworkResourceDetails: React.FunctionComponent<{
resource: ResourceSnapshot,
highlighted: boolean,
}> = ({ resource, highlighted }) => {
}> = ({ resource }) => {
const [expanded, setExpanded] = React.useState(false);
const [requestBody, setRequestBody] = React.useState<string | null>(null);
const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string } | null>(null);
Expand Down Expand Up @@ -86,7 +85,7 @@ export const NetworkResourceDetails: React.FunctionComponent<{
}, [contentType, resource, resourceName, routeStatus]);

return <div
className={'network-request' + (highlighted ? ' highlighted' : '')}>
className='network-request'>
<Expandable expanded={expanded} setExpanded={setExpanded} title={ renderTitle() }>
<div className='network-request-details'>
<div className='network-request-details-time'>{resource.time}ms</div>
Expand Down
21 changes: 13 additions & 8 deletions packages/trace-viewer/src/ui/networkTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,28 @@
*/

import * as React from 'react';
import type { ActionTraceEvent } from '@trace/trace';
import * as modelUtil from './modelUtil';
import type * as modelUtil from './modelUtil';
import { NetworkResourceDetails } from './networkResourceDetails';
import './networkTab.css';
import type { Boundaries } from '../geometry';

export const NetworkTab: React.FunctionComponent<{
model: modelUtil.MultiTraceModel | undefined,
action: ActionTraceEvent | undefined,
}> = ({ model, action }) => {
const actionResources = action ? modelUtil.resourcesForAction(action) : [];
const resources = model?.resources || [];
return <div className='network-tab'>{
selectedTime: Boundaries | undefined,
}> = ({ model, selectedTime }) => {
const resources = React.useMemo(() => {
const resources = model?.resources || [];
if (!selectedTime)
return resources;
return resources.filter(resource => {
return !!resource._monotonicTime && (resource._monotonicTime >= selectedTime.minimum && resource._monotonicTime <= selectedTime.maximum);
});
}, [model, selectedTime]);
return <div className='network-tab'> {
resources.map((resource, index) => {
return <NetworkResourceDetails
resource={resource}
key={index}
highlighted={actionResources.includes(resource)}
/>;
})
}</div>;
Expand Down
Loading

0 comments on commit a705d68

Please sign in to comment.