From 6dbc7b54e8ea62c1f31c43aa8dad7e079e136003 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 18 Jul 2024 16:39:40 +0200 Subject: [PATCH] feat(ui-mode): highlight console message in timeline on hover (#31756) --- packages/trace-viewer/src/ui/consoleTab.tsx | 6 +++- packages/trace-viewer/src/ui/timeline.css | 5 +++ packages/trace-viewer/src/ui/timeline.tsx | 38 +++++++++++++++++---- packages/trace-viewer/src/ui/workbench.tsx | 12 ++++++- packages/web/src/components/listView.tsx | 2 +- 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/packages/trace-viewer/src/ui/consoleTab.tsx b/packages/trace-viewer/src/ui/consoleTab.tsx index 4fe99f6863..882419ae38 100644 --- a/packages/trace-viewer/src/ui/consoleTab.tsx +++ b/packages/trace-viewer/src/ui/consoleTab.tsx @@ -107,13 +107,17 @@ export const ConsoleTab: React.FunctionComponent<{ boundaries: Boundaries, consoleModel: ConsoleTabModel, selectedTime: Boundaries | undefined, -}> = ({ consoleModel, boundaries }) => { + onEntryHovered: (entry: ConsoleEntry | undefined) => void, + onAccepted: (entry: ConsoleEntry) => void, +}> = ({ consoleModel, boundaries, onEntryHovered, onAccepted }) => { if (!consoleModel.entries.length) return ; return
entry.isError} isWarning={entry => entry.isWarning} diff --git a/packages/trace-viewer/src/ui/timeline.css b/packages/trace-viewer/src/ui/timeline.css index be06c4c53b..b22b48a4c5 100644 --- a/packages/trace-viewer/src/ui/timeline.css +++ b/packages/trace-viewer/src/ui/timeline.css @@ -98,6 +98,11 @@ --action-background-color: #1a85ff66; } +.timeline-bar.console-message { + --action-color: var(--vscode-charts-purple); + --action-background-color: #1a85ff66; +} + body.dark-mode .timeline-bar.action.error { --action-color: var(--vscode-errorForeground); --action-background-color: #f4877166; diff --git a/packages/trace-viewer/src/ui/timeline.tsx b/packages/trace-viewer/src/ui/timeline.tsx index 67f4e3b071..1471220aa2 100644 --- a/packages/trace-viewer/src/ui/timeline.tsx +++ b/packages/trace-viewer/src/ui/timeline.tsx @@ -24,10 +24,12 @@ import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil'; import './timeline.css'; import type { Language } from '@isomorphic/locatorGenerators'; import type { Entry } from '@trace/har'; +import type { ConsoleEntry } from './consoleTab'; type TimelineBar = { action?: ActionTraceEventInContext; resource?: Entry; + consoleMessage?: ConsoleEntry; leftPosition: number; rightPosition: number; leftTime: number; @@ -38,14 +40,16 @@ type TimelineBar = { export const Timeline: React.FunctionComponent<{ model: MultiTraceModel | undefined, + consoleEntries: ConsoleEntry[] | undefined, boundaries: Boundaries, highlightedAction: ActionTraceEventInContext | undefined, highlightedEntry: Entry | undefined, + highlightedConsoleEntry: ConsoleEntry | undefined, onSelected: (action: ActionTraceEventInContext) => void, selectedTime: Boundaries | undefined, setSelectedTime: (time: Boundaries | undefined) => void, sdkLanguage: Language, -}> = ({ model, boundaries, onSelected, highlightedAction, highlightedEntry, selectedTime, setSelectedTime, sdkLanguage }) => { +}> = ({ model, boundaries, consoleEntries, onSelected, highlightedAction, highlightedEntry, highlightedConsoleEntry, selectedTime, setSelectedTime, sdkLanguage }) => { const [measure, ref] = useMeasure(); const [dragWindow, setDragWindow] = React.useState<{ startX: number, endX: number, pivot?: number, type: 'resize' | 'move' } | undefined>(); const [previewPoint, setPreviewPoint] = React.useState(); @@ -92,13 +96,34 @@ export const Timeline: React.FunctionComponent<{ error: false, }); } + + for (const consoleMessage of consoleEntries || []) { + bars.push({ + consoleMessage, + leftTime: consoleMessage.timestamp, + rightTime: consoleMessage.timestamp, + leftPosition: timeToPosition(measure.width, boundaries, consoleMessage.timestamp), + rightPosition: timeToPosition(measure.width, boundaries, consoleMessage.timestamp), + active: false, + error: consoleMessage.isError, + }); + } + return bars; - }, [model, boundaries, measure]); + }, [model, consoleEntries, boundaries, measure]); React.useMemo(() => { - for (const bar of bars) - bar.active = (!!highlightedAction && bar.action === highlightedAction) || (!!highlightedEntry && bar.resource === highlightedEntry); - }, [bars, highlightedAction, highlightedEntry]); + for (const bar of bars) { + if (highlightedAction) + bar.active = bar.action === highlightedAction; + else if (highlightedEntry) + bar.active = bar.resource === highlightedEntry; + else if (highlightedConsoleEntry) + bar.active = bar.consoleMessage === highlightedConsoleEntry; + else + bar.active = false; + } + }, [bars, highlightedAction, highlightedEntry, highlightedConsoleEntry]); const onMouseDown = React.useCallback((event: React.MouseEvent) => { setPreviewPoint(undefined); @@ -229,11 +254,12 @@ export const Timeline: React.FunctionComponent<{ return
(undefined); const [highlightedAction, setHighlightedAction] = React.useState(); const [highlightedEntry, setHighlightedEntry] = React.useState(); + const [highlightedConsoleMessage, setHighlightedConsoleMessage] = React.useState(); const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState('actions'); const [selectedPropertiesTab, setSelectedPropertiesTab] = useSetting('propertiesTab', showSourcesFirst ? 'source' : 'call'); const [isInspecting, setIsInspecting] = React.useState(false); @@ -167,7 +169,13 @@ export const Workbench: React.FunctionComponent<{ id: 'console', title: 'Console', count: consoleModel.entries.length, - render: () => + render: () => setSelectedTime({ minimum: m.timestamp, maximum: m.timestamp })} + onEntryHovered={setHighlightedConsoleMessage} + /> }; const networkTab: TabbedPaneTabModel = { id: 'network', @@ -218,9 +226,11 @@ export const Workbench: React.FunctionComponent<{ return
({
selectedItem && onAccepted?.(selectedItem, items.indexOf(selectedItem))} onKeyDown={event => { if (selectedItem && event.key === 'Enter') { onAccepted?.(selectedItem, items.indexOf(selectedItem)); @@ -143,6 +142,7 @@ export function ListView({ const rendered = render(item, index); return
onAccepted?.(item, index)} role='listitem' className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix + infoSuffix} onClick={() => onSelected?.(item, index)}