chore: use ContextManager for trace-viewer state handling
This commit is contained in:
parent
6491e5b415
commit
33e4b31dcc
|
|
@ -25,13 +25,13 @@ import type { TreeState } from '@web/components/treeView';
|
||||||
import { TreeView } from '@web/components/treeView';
|
import { TreeView } from '@web/components/treeView';
|
||||||
import type { ActionTraceEventInContext, ActionTreeItem } from './modelUtil';
|
import type { ActionTraceEventInContext, ActionTreeItem } from './modelUtil';
|
||||||
import type { Boundaries } from '../geometry';
|
import type { Boundaries } from '../geometry';
|
||||||
|
import { WorkbenchContext } from './workbenchContext';
|
||||||
|
|
||||||
export interface ActionListProps {
|
export interface ActionListProps {
|
||||||
actions: ActionTraceEventInContext[],
|
actions: ActionTraceEventInContext[],
|
||||||
selectedAction: ActionTraceEventInContext | undefined,
|
selectedAction: ActionTraceEventInContext | undefined,
|
||||||
selectedTime: Boundaries | undefined,
|
selectedTime: Boundaries | undefined,
|
||||||
setSelectedTime: (time: Boundaries | undefined) => void,
|
setSelectedTime: (time: Boundaries | undefined) => void,
|
||||||
sdkLanguage: Language | undefined;
|
|
||||||
onSelected: (action: ActionTraceEventInContext) => void,
|
onSelected: (action: ActionTraceEventInContext) => void,
|
||||||
onHighlighted: (action: ActionTraceEventInContext | undefined) => void,
|
onHighlighted: (action: ActionTraceEventInContext | undefined) => void,
|
||||||
revealConsole: () => void,
|
revealConsole: () => void,
|
||||||
|
|
@ -45,12 +45,12 @@ export const ActionList: React.FC<ActionListProps> = ({
|
||||||
selectedAction,
|
selectedAction,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
setSelectedTime,
|
setSelectedTime,
|
||||||
sdkLanguage,
|
|
||||||
onSelected,
|
onSelected,
|
||||||
onHighlighted,
|
onHighlighted,
|
||||||
revealConsole,
|
revealConsole,
|
||||||
isLive,
|
isLive,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { sdkLanguage } = React.useContext(WorkbenchContext);
|
||||||
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
||||||
const { rootItem, itemMap } = React.useMemo(() => modelUtil.buildActionTree(actions), [actions]);
|
const { rootItem, itemMap } = React.useMemo(() => modelUtil.buildActionTree(actions), [actions]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@ import { asLocator } from '@isomorphic/locatorGenerators';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
import { PlaceholderPanel } from './placeholderPanel';
|
import { PlaceholderPanel } from './placeholderPanel';
|
||||||
import type { ActionTraceEventInContext } from './modelUtil';
|
import type { ActionTraceEventInContext } from './modelUtil';
|
||||||
|
import { WorkbenchContext } from './workbenchContext';
|
||||||
|
|
||||||
export const CallTab: React.FunctionComponent<{
|
export const CallTab: React.FunctionComponent<{
|
||||||
action: ActionTraceEventInContext | undefined,
|
action: ActionTraceEventInContext | undefined,
|
||||||
sdkLanguage: Language | undefined,
|
}> = ({ action }) => {
|
||||||
}> = ({ action, sdkLanguage }) => {
|
const { sdkLanguage } = React.useContext(WorkbenchContext);
|
||||||
if (!action)
|
if (!action)
|
||||||
return <PlaceholderPanel text='No action selected' />;
|
return <PlaceholderPanel text='No action selected' />;
|
||||||
const params = { ...action.params };
|
const params = { ...action.params };
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import * as React from 'react';
|
||||||
import type * as modelUtil from './modelUtil';
|
import type * as modelUtil from './modelUtil';
|
||||||
import { PlaceholderPanel } from './placeholderPanel';
|
import { PlaceholderPanel } from './placeholderPanel';
|
||||||
import { renderAction } from './actionList';
|
import { renderAction } from './actionList';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
|
||||||
import type { StackFrame } from '@protocol/channels';
|
import type { StackFrame } from '@protocol/channels';
|
||||||
|
import { WorkbenchContext } from './workbenchContext';
|
||||||
|
|
||||||
type ErrorDescription = {
|
type ErrorDescription = {
|
||||||
action?: modelUtil.ActionTraceEventInContext;
|
action?: modelUtil.ActionTraceEventInContext;
|
||||||
|
|
@ -44,9 +44,9 @@ export function useErrorsTabModel(model: modelUtil.MultiTraceModel | undefined):
|
||||||
|
|
||||||
export const ErrorsTab: React.FunctionComponent<{
|
export const ErrorsTab: React.FunctionComponent<{
|
||||||
errorsModel: ErrorsTabModel,
|
errorsModel: ErrorsTabModel,
|
||||||
sdkLanguage: Language,
|
|
||||||
revealInSource: (error: ErrorDescription) => void,
|
revealInSource: (error: ErrorDescription) => void,
|
||||||
}> = ({ errorsModel, sdkLanguage, revealInSource }) => {
|
}> = ({ errorsModel, revealInSource }) => {
|
||||||
|
const { sdkLanguage } = React.useContext(WorkbenchContext);
|
||||||
if (!errorsModel.errors.size)
|
if (!errorsModel.errors.size)
|
||||||
return <PlaceholderPanel text='No errors' />;
|
return <PlaceholderPanel text='No errors' />;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
||||||
import type { Language } from '@web/components/codeMirrorWrapper';
|
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
import { copy } from '@web/uiUtils';
|
import { copy } from '@web/uiUtils';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './sourceTab.css';
|
import './sourceTab.css';
|
||||||
|
import { WorkbenchContext } from './workbenchContext';
|
||||||
|
|
||||||
export const InspectorTab: React.FunctionComponent<{
|
export const InspectorTab: React.FunctionComponent<{
|
||||||
sdkLanguage: Language,
|
|
||||||
setIsInspecting: (isInspecting: boolean) => void,
|
setIsInspecting: (isInspecting: boolean) => void,
|
||||||
highlightedLocator: string,
|
highlightedLocator: string,
|
||||||
setHighlightedLocator: (locator: string) => void,
|
setHighlightedLocator: (locator: string) => void,
|
||||||
}> = ({ sdkLanguage, setIsInspecting, highlightedLocator, setHighlightedLocator }) => {
|
}> = ({ setIsInspecting, highlightedLocator, setHighlightedLocator }) => {
|
||||||
|
const { sdkLanguage } = React.useContext(WorkbenchContext);
|
||||||
return <div className='vbox' style={{ backgroundColor: 'var(--vscode-sideBar-background)' }}>
|
return <div className='vbox' style={{ backgroundColor: 'var(--vscode-sideBar-background)' }}>
|
||||||
<div style={{ margin: '10px 0px 10px 10px', color: 'var(--vscode-editorCodeLens-foreground)', flex: 'none' }}>Locator</div>
|
<div style={{ margin: '10px 0px 10px 10px', color: 'var(--vscode-editorCodeLens-foreground)', flex: 'none' }}>Locator</div>
|
||||||
<div style={{ margin: '0 10px 10px', flex: 'auto' }}>
|
<div style={{ margin: '0 10px 10px', flex: 'auto' }}>
|
||||||
|
|
|
||||||
|
|
@ -29,17 +29,18 @@ import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
|
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
|
||||||
import { TabbedPaneTab } from '@web/components/tabbedPane';
|
import { TabbedPaneTab } from '@web/components/tabbedPane';
|
||||||
import { BrowserFrame } from './browserFrame';
|
import { BrowserFrame } from './browserFrame';
|
||||||
|
import { WorkbenchContext } from './workbenchContext';
|
||||||
|
|
||||||
export const SnapshotTab: React.FunctionComponent<{
|
export const SnapshotTab: React.FunctionComponent<{
|
||||||
action: ActionTraceEvent | undefined,
|
action: ActionTraceEvent | undefined,
|
||||||
sdkLanguage: Language,
|
|
||||||
testIdAttributeName: string,
|
testIdAttributeName: string,
|
||||||
isInspecting: boolean,
|
isInspecting: boolean,
|
||||||
setIsInspecting: (isInspecting: boolean) => void,
|
setIsInspecting: (isInspecting: boolean) => void,
|
||||||
highlightedLocator: string,
|
highlightedLocator: string,
|
||||||
setHighlightedLocator: (locator: string) => void,
|
setHighlightedLocator: (locator: string) => void,
|
||||||
openPage?: (url: string, target?: string) => Window | any,
|
openPage?: (url: string, target?: string) => Window | any,
|
||||||
}> = ({ action, sdkLanguage, testIdAttributeName, isInspecting, setIsInspecting, highlightedLocator, setHighlightedLocator, openPage }) => {
|
}> = ({ action, testIdAttributeName, isInspecting, setIsInspecting, highlightedLocator, setHighlightedLocator, openPage }) => {
|
||||||
|
const { sdkLanguage } = React.useContext(WorkbenchContext);
|
||||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||||
const [snapshotTab, setSnapshotTab] = React.useState<'action'|'before'|'after'>('action');
|
const [snapshotTab, setSnapshotTab] = React.useState<'action'|'before'|'after'>('action');
|
||||||
|
|
||||||
|
|
@ -166,7 +167,6 @@ export const SnapshotTab: React.FunctionComponent<{
|
||||||
>
|
>
|
||||||
<InspectModeController
|
<InspectModeController
|
||||||
isInspecting={isInspecting}
|
isInspecting={isInspecting}
|
||||||
sdkLanguage={sdkLanguage}
|
|
||||||
testIdAttributeName={testIdAttributeName}
|
testIdAttributeName={testIdAttributeName}
|
||||||
highlightedLocator={highlightedLocator}
|
highlightedLocator={highlightedLocator}
|
||||||
setHighlightedLocator={setHighlightedLocator}
|
setHighlightedLocator={setHighlightedLocator}
|
||||||
|
|
@ -174,7 +174,6 @@ export const SnapshotTab: React.FunctionComponent<{
|
||||||
iteration={loadingRef.current.iteration} />
|
iteration={loadingRef.current.iteration} />
|
||||||
<InspectModeController
|
<InspectModeController
|
||||||
isInspecting={isInspecting}
|
isInspecting={isInspecting}
|
||||||
sdkLanguage={sdkLanguage}
|
|
||||||
testIdAttributeName={testIdAttributeName}
|
testIdAttributeName={testIdAttributeName}
|
||||||
highlightedLocator={highlightedLocator}
|
highlightedLocator={highlightedLocator}
|
||||||
setHighlightedLocator={setHighlightedLocator}
|
setHighlightedLocator={setHighlightedLocator}
|
||||||
|
|
@ -229,12 +228,12 @@ function renderTitle(snapshotTitle: string): string {
|
||||||
export const InspectModeController: React.FunctionComponent<{
|
export const InspectModeController: React.FunctionComponent<{
|
||||||
iframe: HTMLIFrameElement | null,
|
iframe: HTMLIFrameElement | null,
|
||||||
isInspecting: boolean,
|
isInspecting: boolean,
|
||||||
sdkLanguage: Language,
|
|
||||||
testIdAttributeName: string,
|
testIdAttributeName: string,
|
||||||
highlightedLocator: string,
|
highlightedLocator: string,
|
||||||
setHighlightedLocator: (locator: string) => void,
|
setHighlightedLocator: (locator: string) => void,
|
||||||
iteration: number,
|
iteration: number,
|
||||||
}> = ({ iframe, isInspecting, sdkLanguage, testIdAttributeName, highlightedLocator, setHighlightedLocator, iteration }) => {
|
}> = ({ iframe, isInspecting, testIdAttributeName, highlightedLocator, setHighlightedLocator, iteration }) => {
|
||||||
|
const { sdkLanguage } = React.useContext(WorkbenchContext);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const recorders: { recorder: Recorder, frameSelector: string }[] = [];
|
const recorders: { recorder: Recorder, frameSelector: string }[] = [];
|
||||||
const isUnderTest = new URLSearchParams(window.location.search).get('isUnderTest') === 'true';
|
const isUnderTest = new URLSearchParams(window.location.search).get('isUnderTest') === 'true';
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import { FilmStrip } from './filmStrip';
|
||||||
import type { FilmStripPreviewPoint } from './filmStrip';
|
import type { FilmStripPreviewPoint } from './filmStrip';
|
||||||
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
||||||
import './timeline.css';
|
import './timeline.css';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
|
||||||
import type { Entry } from '@trace/har';
|
import type { Entry } from '@trace/har';
|
||||||
|
import { WorkbenchContext } from './workbenchContext';
|
||||||
|
|
||||||
type TimelineBar = {
|
type TimelineBar = {
|
||||||
action?: ActionTraceEventInContext;
|
action?: ActionTraceEventInContext;
|
||||||
|
|
@ -44,8 +44,8 @@ export const Timeline: React.FunctionComponent<{
|
||||||
onSelected: (action: ActionTraceEventInContext) => void,
|
onSelected: (action: ActionTraceEventInContext) => void,
|
||||||
selectedTime: Boundaries | undefined,
|
selectedTime: Boundaries | undefined,
|
||||||
setSelectedTime: (time: Boundaries | undefined) => void,
|
setSelectedTime: (time: Boundaries | undefined) => void,
|
||||||
sdkLanguage: Language,
|
}> = ({ model, boundaries, onSelected, highlightedAction, highlightedEntry, selectedTime, setSelectedTime }) => {
|
||||||
}> = ({ model, boundaries, onSelected, highlightedAction, highlightedEntry, selectedTime, setSelectedTime, sdkLanguage }) => {
|
const { sdkLanguage } = React.useContext(WorkbenchContext);
|
||||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||||
const [dragWindow, setDragWindow] = React.useState<{ startX: number, endX: number, pivot?: number, type: 'resize' | 'move' } | undefined>();
|
const [dragWindow, setDragWindow] = React.useState<{ startX: number, endX: number, pivot?: number, type: 'resize' | 'move' } | undefined>();
|
||||||
const [previewPoint, setPreviewPoint] = React.useState<FilmStripPreviewPoint | undefined>();
|
const [previewPoint, setPreviewPoint] = React.useState<FilmStripPreviewPoint | undefined>();
|
||||||
|
|
|
||||||
|
|
@ -120,13 +120,10 @@ export const Workbench: React.FunctionComponent<{
|
||||||
return model?.actions.map(a => a.attachments || []).flat() || [];
|
return model?.actions.map(a => a.attachments || []).flat() || [];
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
const sdkLanguage = model?.sdkLanguage || 'javascript';
|
|
||||||
|
|
||||||
const inspectorTab: TabbedPaneTabModel = {
|
const inspectorTab: TabbedPaneTabModel = {
|
||||||
id: 'inspector',
|
id: 'inspector',
|
||||||
title: 'Locator',
|
title: 'Locator',
|
||||||
render: () => <InspectorTab
|
render: () => <InspectorTab
|
||||||
sdkLanguage={sdkLanguage}
|
|
||||||
setIsInspecting={setIsInspecting}
|
setIsInspecting={setIsInspecting}
|
||||||
highlightedLocator={highlightedLocator}
|
highlightedLocator={highlightedLocator}
|
||||||
setHighlightedLocator={setHighlightedLocator} />,
|
setHighlightedLocator={setHighlightedLocator} />,
|
||||||
|
|
@ -134,7 +131,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
const callTab: TabbedPaneTabModel = {
|
const callTab: TabbedPaneTabModel = {
|
||||||
id: 'call',
|
id: 'call',
|
||||||
title: 'Call',
|
title: 'Call',
|
||||||
render: () => <CallTab action={activeAction} sdkLanguage={sdkLanguage} />
|
render: () => <CallTab action={activeAction} />
|
||||||
};
|
};
|
||||||
const logTab: TabbedPaneTabModel = {
|
const logTab: TabbedPaneTabModel = {
|
||||||
id: 'log',
|
id: 'log',
|
||||||
|
|
@ -145,7 +142,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
id: 'errors',
|
id: 'errors',
|
||||||
title: 'Errors',
|
title: 'Errors',
|
||||||
errorCount: errorsModel.errors.size,
|
errorCount: errorsModel.errors.size,
|
||||||
render: () => <ErrorsTab errorsModel={errorsModel} sdkLanguage={sdkLanguage} revealInSource={error => {
|
render: () => <ErrorsTab errorsModel={errorsModel} revealInSource={error => {
|
||||||
if (error.action)
|
if (error.action)
|
||||||
setSelectedAction(error.action);
|
setSelectedAction(error.action);
|
||||||
else
|
else
|
||||||
|
|
@ -222,7 +219,6 @@ export const Workbench: React.FunctionComponent<{
|
||||||
highlightedAction={highlightedAction}
|
highlightedAction={highlightedAction}
|
||||||
highlightedEntry={highlightedEntry}
|
highlightedEntry={highlightedEntry}
|
||||||
onSelected={onActionSelected}
|
onSelected={onActionSelected}
|
||||||
sdkLanguage={sdkLanguage}
|
|
||||||
selectedTime={selectedTime}
|
selectedTime={selectedTime}
|
||||||
setSelectedTime={setSelectedTime}
|
setSelectedTime={setSelectedTime}
|
||||||
/>
|
/>
|
||||||
|
|
@ -230,7 +226,6 @@ export const Workbench: React.FunctionComponent<{
|
||||||
<SplitView sidebarSize={250} orientation='horizontal' sidebarIsFirst={true} settingName='actionListSidebar'>
|
<SplitView sidebarSize={250} orientation='horizontal' sidebarIsFirst={true} settingName='actionListSidebar'>
|
||||||
<SnapshotTab
|
<SnapshotTab
|
||||||
action={activeAction}
|
action={activeAction}
|
||||||
sdkLanguage={sdkLanguage}
|
|
||||||
testIdAttributeName={model?.testIdAttributeName || 'data-testid'}
|
testIdAttributeName={model?.testIdAttributeName || 'data-testid'}
|
||||||
isInspecting={isInspecting}
|
isInspecting={isInspecting}
|
||||||
setIsInspecting={setIsInspecting}
|
setIsInspecting={setIsInspecting}
|
||||||
|
|
@ -250,7 +245,6 @@ export const Workbench: React.FunctionComponent<{
|
||||||
<div className='workbench-run-duration'>{time ? msToString(time) : ''}</div>
|
<div className='workbench-run-duration'>{time ? msToString(time) : ''}</div>
|
||||||
</div>}
|
</div>}
|
||||||
<ActionList
|
<ActionList
|
||||||
sdkLanguage={sdkLanguage}
|
|
||||||
actions={model?.actions || []}
|
actions={model?.actions || []}
|
||||||
selectedAction={model ? selectedAction : undefined}
|
selectedAction={model ? selectedAction : undefined}
|
||||||
selectedTime={selectedTime}
|
selectedTime={selectedTime}
|
||||||
|
|
|
||||||
23
packages/trace-viewer/src/ui/workbenchContext.tsx
Normal file
23
packages/trace-viewer/src/ui/workbenchContext.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
sdkLanguage: Language,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkbenchContext = createContext<Context>(null as any);
|
||||||
|
|
@ -22,6 +22,7 @@ import './workbenchLoader.css';
|
||||||
import { toggleTheme } from '@web/theme';
|
import { toggleTheme } from '@web/theme';
|
||||||
import { Workbench } from './workbench';
|
import { Workbench } from './workbench';
|
||||||
import { TestServerConnection } from '@testIsomorphic/testServerConnection';
|
import { TestServerConnection } from '@testIsomorphic/testServerConnection';
|
||||||
|
import { WorkbenchContext } from './workbenchContext';
|
||||||
|
|
||||||
export const WorkbenchLoader: React.FunctionComponent<{
|
export const WorkbenchLoader: React.FunctionComponent<{
|
||||||
}> = () => {
|
}> = () => {
|
||||||
|
|
@ -165,7 +166,9 @@ export const WorkbenchLoader: React.FunctionComponent<{
|
||||||
<div className='progress'>
|
<div className='progress'>
|
||||||
<div className='inner-progress' style={{ width: progress.total ? (100 * progress.done / progress.total) + '%' : 0 }}></div>
|
<div className='inner-progress' style={{ width: progress.total ? (100 * progress.done / progress.total) + '%' : 0 }}></div>
|
||||||
</div>
|
</div>
|
||||||
<Workbench model={model} inert={showFileUploadDropArea} />
|
<WorkbenchContext.Provider value={{ sdkLanguage: model?.sdkLanguage || 'javascript' }}>
|
||||||
|
<Workbench model={model} inert={showFileUploadDropArea} />
|
||||||
|
</WorkbenchContext.Provider>
|
||||||
{fileForLocalModeError && <div className='drop-target'>
|
{fileForLocalModeError && <div className='drop-target'>
|
||||||
<div>Trace Viewer uses Service Workers to show traces. To view trace:</div>
|
<div>Trace Viewer uses Service Workers to show traces. To view trace:</div>
|
||||||
<div style={{ paddingTop: 20 }}>
|
<div style={{ paddingTop: 20 }}>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue