chore: do not show stale source in the trace (#23163)
This commit is contained in:
parent
bd090e67df
commit
0e1aeaaecf
|
|
@ -116,8 +116,12 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativePath.startsWith('/sha1/')) {
|
if (relativePath.startsWith('/sha1/')) {
|
||||||
// Sha1 is unique, load it from either of the models for simplicity.
|
// Sha1 for sources is based on the file path, can't load it of a random model.
|
||||||
for (const { traceModel } of loadedTraces.values()) {
|
const traceUrls = clientIdToTraceUrls.get(event.clientId);
|
||||||
|
for (const [trace, { traceModel }] of loadedTraces) {
|
||||||
|
// We will accept explicit ?trace= value as well as the clientId associated with the trace.
|
||||||
|
if (traceUrl !== trace && !traceUrls.includes(trace))
|
||||||
|
continue;
|
||||||
const blob = await traceModel!.resourceForSha1(relativePath.slice('/sha1/'.length));
|
const blob = await traceModel!.resourceForSha1(relativePath.slice('/sha1/'.length));
|
||||||
if (blob)
|
if (blob)
|
||||||
return new Response(blob, { status: 200 });
|
return new Response(blob, { status: 200 });
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,14 @@ import { asLocator } from '@isomorphic/locatorGenerators';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
import type { TreeState } from '@web/components/treeView';
|
import type { TreeState } from '@web/components/treeView';
|
||||||
import { TreeView } from '@web/components/treeView';
|
import { TreeView } from '@web/components/treeView';
|
||||||
|
import type { ActionTraceEventInContext } from './modelUtil';
|
||||||
|
|
||||||
export interface ActionListProps {
|
export interface ActionListProps {
|
||||||
actions: ActionTraceEvent[],
|
actions: ActionTraceEventInContext[],
|
||||||
selectedAction: ActionTraceEvent | undefined,
|
selectedAction: ActionTraceEventInContext | undefined,
|
||||||
sdkLanguage: Language | undefined;
|
sdkLanguage: Language | undefined;
|
||||||
onSelected: (action: ActionTraceEvent) => void,
|
onSelected: (action: ActionTraceEventInContext) => void,
|
||||||
onHighlighted: (action: ActionTraceEvent | undefined) => void,
|
onHighlighted: (action: ActionTraceEventInContext | undefined) => void,
|
||||||
revealConsole: () => void,
|
revealConsole: () => void,
|
||||||
isLive?: boolean,
|
isLive?: boolean,
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +39,7 @@ type ActionTreeItem = {
|
||||||
id: string;
|
id: string;
|
||||||
children: ActionTreeItem[];
|
children: ActionTreeItem[];
|
||||||
parent: ActionTreeItem | undefined;
|
parent: ActionTreeItem | undefined;
|
||||||
action?: ActionTraceEvent;
|
action?: ActionTraceEventInContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionTreeView = TreeView<ActionTreeItem>;
|
const ActionTreeView = TreeView<ActionTreeItem>;
|
||||||
|
|
|
||||||
|
|
@ -14,39 +14,40 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ActionTraceEvent } from '@trace/trace';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './attachmentsTab.css';
|
import './attachmentsTab.css';
|
||||||
import { ImageDiffView } from '@web/components/imageDiffView';
|
import { ImageDiffView } from '@web/components/imageDiffView';
|
||||||
import type { TestAttachment } from '@web/components/imageDiffView';
|
import type { TestAttachment } from '@web/components/imageDiffView';
|
||||||
|
import type { ActionTraceEventInContext } from './modelUtil';
|
||||||
|
|
||||||
export const AttachmentsTab: React.FunctionComponent<{
|
export const AttachmentsTab: React.FunctionComponent<{
|
||||||
action: ActionTraceEvent | undefined,
|
action: ActionTraceEventInContext | undefined,
|
||||||
}> = ({ action }) => {
|
}> = ({ action }) => {
|
||||||
if (!action)
|
if (!action)
|
||||||
return null;
|
return null;
|
||||||
const expected = action.attachments?.find(a => a.name.endsWith('-expected.png') && (a.path || a.sha1)) as TestAttachment | undefined;
|
const expected = action.attachments?.find(a => a.name.endsWith('-expected.png') && (a.path || a.sha1)) as TestAttachment | undefined;
|
||||||
const actual = action.attachments?.find(a => a.name.endsWith('-actual.png') && (a.path || a.sha1)) as TestAttachment | undefined;
|
const actual = action.attachments?.find(a => a.name.endsWith('-actual.png') && (a.path || a.sha1)) as TestAttachment | undefined;
|
||||||
const diff = action.attachments?.find(a => a.name.endsWith('-diff.png') && (a.path || a.sha1)) as TestAttachment | undefined;
|
const diff = action.attachments?.find(a => a.name.endsWith('-diff.png') && (a.path || a.sha1)) as TestAttachment | undefined;
|
||||||
|
const traceUrl = action.context.traceUrl;
|
||||||
|
|
||||||
return <div className='attachments-tab'>
|
return <div className='attachments-tab'>
|
||||||
{expected && actual && <div className='attachments-section'>Image diff</div>}
|
{expected && actual && <div className='attachments-section'>Image diff</div>}
|
||||||
{expected && actual && <ImageDiffView imageDiff={{
|
{expected && actual && <ImageDiffView imageDiff={{
|
||||||
name: 'Image diff',
|
name: 'Image diff',
|
||||||
expected: { attachment: { ...expected, path: attachmentURL(expected) }, title: 'Expected' },
|
expected: { attachment: { ...expected, path: attachmentURL(traceUrl, expected) }, title: 'Expected' },
|
||||||
actual: { attachment: { ...actual, path: attachmentURL(actual) } },
|
actual: { attachment: { ...actual, path: attachmentURL(traceUrl, actual) } },
|
||||||
diff: diff ? { attachment: { ...diff, path: attachmentURL(diff) } } : undefined,
|
diff: diff ? { attachment: { ...diff, path: attachmentURL(traceUrl, diff) } } : undefined,
|
||||||
}} />}
|
}} />}
|
||||||
{<div className='attachments-section'>Attachments</div>}
|
{<div className='attachments-section'>Attachments</div>}
|
||||||
{action.attachments?.map(a => {
|
{action.attachments?.map(a => {
|
||||||
return <div className='attachment-item'>
|
return <div className='attachment-item'>
|
||||||
<a target='_blank' href={`sha1/${a.sha1}`}>{a.name}</a>
|
<a target='_blank' href={attachmentURL(traceUrl, a)}>{a.name}</a>
|
||||||
</div>;
|
</div>;
|
||||||
})}
|
})}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function attachmentURL(attachment: {
|
function attachmentURL(traceUrl: string, attachment: {
|
||||||
name: string;
|
name: string;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
|
|
@ -54,6 +55,6 @@ function attachmentURL(attachment: {
|
||||||
body?: string;
|
body?: string;
|
||||||
}) {
|
}) {
|
||||||
if (attachment.sha1)
|
if (attachment.sha1)
|
||||||
return 'sha1/' + attachment.sha1;
|
return 'sha1/' + attachment.sha1 + '?trace=' + traceUrl;
|
||||||
return 'file?path=' + attachment.path;
|
return 'file?path=' + attachment.path;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@ export type SourceModel = {
|
||||||
content: string | undefined;
|
content: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ActionTraceEventInContext = ActionTraceEvent & {
|
||||||
|
context: ContextEntry;
|
||||||
|
};
|
||||||
|
|
||||||
export class MultiTraceModel {
|
export class MultiTraceModel {
|
||||||
readonly startTime: number;
|
readonly startTime: number;
|
||||||
readonly endTime: number;
|
readonly endTime: number;
|
||||||
|
|
@ -46,7 +50,7 @@ export class MultiTraceModel {
|
||||||
readonly title?: string;
|
readonly title?: string;
|
||||||
readonly options: trace.BrowserContextEventOptions;
|
readonly options: trace.BrowserContextEventOptions;
|
||||||
readonly pages: PageEntry[];
|
readonly pages: PageEntry[];
|
||||||
readonly actions: trace.ActionTraceEvent[];
|
readonly actions: ActionTraceEventInContext[];
|
||||||
readonly events: trace.EventTraceEvent[];
|
readonly events: trace.EventTraceEvent[];
|
||||||
readonly hasSource: boolean;
|
readonly hasSource: boolean;
|
||||||
readonly sdkLanguage: Language | undefined;
|
readonly sdkLanguage: Language | undefined;
|
||||||
|
|
@ -89,7 +93,7 @@ function indexModel(context: ContextEntry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeActions(contexts: ContextEntry[]) {
|
function mergeActions(contexts: ContextEntry[]) {
|
||||||
const map = new Map<string, ActionTraceEvent>();
|
const map = new Map<string, ActionTraceEventInContext>();
|
||||||
|
|
||||||
// Protocol call aka isPrimary contexts have startTime/endTime as server-side times.
|
// Protocol call aka isPrimary contexts have startTime/endTime as server-side times.
|
||||||
// Step aka non-isPrimary contexts have startTime/endTime are client-side times.
|
// Step aka non-isPrimary contexts have startTime/endTime are client-side times.
|
||||||
|
|
@ -100,7 +104,7 @@ function mergeActions(contexts: ContextEntry[]) {
|
||||||
|
|
||||||
for (const context of primaryContexts) {
|
for (const context of primaryContexts) {
|
||||||
for (const action of context.actions)
|
for (const action of context.actions)
|
||||||
map.set(`${action.apiName}@${action.wallTime}`, action);
|
map.set(`${action.apiName}@${action.wallTime}`, { ...action, context });
|
||||||
if (!offset && context.actions.length)
|
if (!offset && context.actions.length)
|
||||||
offset = context.actions[0].startTime - context.actions[0].wallTime;
|
offset = context.actions[0].startTime - context.actions[0].wallTime;
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +130,7 @@ function mergeActions(contexts: ContextEntry[]) {
|
||||||
existing.parentId = action.parentId;
|
existing.parentId = action.parentId;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
map.set(key, action);
|
map.set(key, { ...action, context });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,16 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ActionTraceEvent, EventTraceEvent } from '@trace/trace';
|
import type { EventTraceEvent } from '@trace/trace';
|
||||||
import { msToString, useMeasure } from '@web/uiUtils';
|
import { msToString, useMeasure } from '@web/uiUtils';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { Boundaries } from '../geometry';
|
import type { Boundaries } from '../geometry';
|
||||||
import { FilmStrip } from './filmStrip';
|
import { FilmStrip } from './filmStrip';
|
||||||
import type { MultiTraceModel } from './modelUtil';
|
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
||||||
import './timeline.css';
|
import './timeline.css';
|
||||||
|
|
||||||
type TimelineBar = {
|
type TimelineBar = {
|
||||||
action?: ActionTraceEvent;
|
action?: ActionTraceEventInContext;
|
||||||
event?: EventTraceEvent;
|
event?: EventTraceEvent;
|
||||||
leftPosition: number;
|
leftPosition: number;
|
||||||
rightPosition: number;
|
rightPosition: number;
|
||||||
|
|
@ -38,8 +38,8 @@ type TimelineBar = {
|
||||||
|
|
||||||
export const Timeline: React.FunctionComponent<{
|
export const Timeline: React.FunctionComponent<{
|
||||||
model: MultiTraceModel | undefined,
|
model: MultiTraceModel | undefined,
|
||||||
selectedAction: ActionTraceEvent | undefined,
|
selectedAction: ActionTraceEventInContext | undefined,
|
||||||
onSelected: (action: ActionTraceEvent) => void,
|
onSelected: (action: ActionTraceEventInContext) => void,
|
||||||
hideTimelineBars?: boolean,
|
hideTimelineBars?: boolean,
|
||||||
}> = ({ model, selectedAction, onSelected, hideTimelineBars }) => {
|
}> = ({ model, selectedAction, onSelected, hideTimelineBars }) => {
|
||||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,13 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ActionTraceEvent } from '@trace/trace';
|
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ActionList } from './actionList';
|
import { ActionList } from './actionList';
|
||||||
import { CallTab } from './callTab';
|
import { CallTab } from './callTab';
|
||||||
import { ConsoleTab } from './consoleTab';
|
import { ConsoleTab } from './consoleTab';
|
||||||
import * as modelUtil from './modelUtil';
|
import * as modelUtil from './modelUtil';
|
||||||
import type { MultiTraceModel } from './modelUtil';
|
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
||||||
import { NetworkTab } from './networkTab';
|
import { NetworkTab } from './networkTab';
|
||||||
import { SnapshotTab } from './snapshotTab';
|
import { SnapshotTab } from './snapshotTab';
|
||||||
import { SourceTab } from './sourceTab';
|
import { SourceTab } from './sourceTab';
|
||||||
|
|
@ -39,12 +38,12 @@ export const Workbench: React.FunctionComponent<{
|
||||||
showSourcesFirst?: boolean,
|
showSourcesFirst?: boolean,
|
||||||
rootDir?: string,
|
rootDir?: string,
|
||||||
fallbackLocation?: modelUtil.SourceLocation,
|
fallbackLocation?: modelUtil.SourceLocation,
|
||||||
initialSelection?: ActionTraceEvent,
|
initialSelection?: ActionTraceEventInContext,
|
||||||
onSelectionChanged?: (action: ActionTraceEvent) => void,
|
onSelectionChanged?: (action: ActionTraceEventInContext) => void,
|
||||||
isLive?: boolean,
|
isLive?: boolean,
|
||||||
}> = ({ model, hideTimelineBars, hideStackFrames, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive }) => {
|
}> = ({ model, hideTimelineBars, hideStackFrames, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive }) => {
|
||||||
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>(undefined);
|
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEventInContext | undefined>(undefined);
|
||||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
|
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>();
|
||||||
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
|
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
|
||||||
const [selectedPropertiesTab, setSelectedPropertiesTab] = React.useState<string>(showSourcesFirst ? 'source' : 'call');
|
const [selectedPropertiesTab, setSelectedPropertiesTab] = React.useState<string>(showSourcesFirst ? 'source' : 'call');
|
||||||
const activeAction = model ? highlightedAction || selectedAction : undefined;
|
const activeAction = model ? highlightedAction || selectedAction : undefined;
|
||||||
|
|
@ -63,7 +62,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
setSelectedAction(model.actions[model.actions.length - 1]);
|
setSelectedAction(model.actions[model.actions.length - 1]);
|
||||||
}, [model, selectedAction, setSelectedAction, setSelectedPropertiesTab, initialSelection]);
|
}, [model, selectedAction, setSelectedAction, setSelectedPropertiesTab, initialSelection]);
|
||||||
|
|
||||||
const onActionSelected = React.useCallback((action: ActionTraceEvent) => {
|
const onActionSelected = React.useCallback((action: ActionTraceEventInContext) => {
|
||||||
setSelectedAction(action);
|
setSelectedAction(action);
|
||||||
onSelectionChanged?.(action);
|
onSelectionChanged?.(action);
|
||||||
}, [setSelectedAction, onSelectionChanged]);
|
}, [setSelectedAction, onSelectionChanged]);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue