chore: use ContextManager for trace-viewer state handling

This commit is contained in:
Max Schmitt 2024-07-18 15:29:57 +02:00
parent 6491e5b415
commit 33e4b31dcc
9 changed files with 48 additions and 28 deletions

View file

@ -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]);

View file

@ -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 };

View file

@ -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' />;

View file

@ -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' }}>

View file

@ -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';

View file

@ -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>();

View file

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

View 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);

View file

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