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)}