From 9e7abb2a7655e702faf916f091ae825a61de7804 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 7 Mar 2023 12:43:16 -0800 Subject: [PATCH] chore: rearrange settings (#21467) --- packages/trace-viewer/src/ui/actionList.tsx | 2 +- packages/trace-viewer/src/ui/metadataView.tsx | 2 +- packages/trace-viewer/src/ui/timeline.css | 2 +- packages/trace-viewer/src/ui/timeline.tsx | 80 ++--- packages/trace-viewer/src/ui/watchMode.css | 13 +- packages/trace-viewer/src/ui/watchMode.tsx | 290 +++++++++--------- packages/trace-viewer/src/ui/workbench.tsx | 45 +-- packages/web/src/common.css | 1 - packages/web/src/components/listView.css | 15 +- packages/web/src/components/listView.tsx | 6 +- packages/web/src/components/tabbedPane.tsx | 22 +- tests/config/traceViewerFixtures.ts | 2 +- 12 files changed, 250 insertions(+), 230 deletions(-) diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index b47f8b0e98..a948377c30 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -48,7 +48,7 @@ export const ActionList: React.FC = ({ itemKey={(action: ActionTraceEvent) => action.callId} itemType={(action: ActionTraceEvent) => action.error?.message ? 'error' : undefined} itemRender={(action: ActionTraceEvent) => renderAction(action, sdkLanguage, revealConsole)} - showNoItemsMessage={true} + noItemsMessage='No actions' >; }; diff --git a/packages/trace-viewer/src/ui/metadataView.tsx b/packages/trace-viewer/src/ui/metadataView.tsx index aecfa7041e..52c6df4916 100644 --- a/packages/trace-viewer/src/ui/metadataView.tsx +++ b/packages/trace-viewer/src/ui/metadataView.tsx @@ -26,7 +26,7 @@ export const MetadataView: React.FunctionComponent<{ return <>; return
Time
- {model.wallTime &&
start time:{new Date(model.wallTime).toLocaleString()}
} + {!!model.wallTime &&
start time:{new Date(model.wallTime).toLocaleString()}
}
duration:{msToString(model.endTime - model.startTime)}
Browser
engine:{model.browserName}
diff --git a/packages/trace-viewer/src/ui/timeline.css b/packages/trace-viewer/src/ui/timeline.css index 6a4fbfeb97..bfcd66dbbd 100644 --- a/packages/trace-viewer/src/ui/timeline.css +++ b/packages/trace-viewer/src/ui/timeline.css @@ -22,7 +22,7 @@ padding: 20px 0 5px; cursor: text; user-select: none; - border-bottom: 1px solid var(--vscode-panel-border); + margin-left: 10px; } .timeline-divider { diff --git a/packages/trace-viewer/src/ui/timeline.tsx b/packages/trace-viewer/src/ui/timeline.tsx index ae93dcb27b..48901bb396 100644 --- a/packages/trace-viewer/src/ui/timeline.tsx +++ b/packages/trace-viewer/src/ui/timeline.tsx @@ -153,45 +153,47 @@ export const Timeline: React.FunctionComponent<{ onSelected(entry); }; - return
-
{ - offsets.map((offset, index) => { - return
-
{msToString(offset.time - boundaries.minimum)}
-
; - }) - }
-
{ - bars.map((bar, index) => { - return
- {bar.label} -
; - }) - }
-
{ - bars.map((bar, index) => { - return
; - }) - }
- -
+ return
+
+
{ + offsets.map((offset, index) => { + return
+
{msToString(offset.time - boundaries.minimum)}
+
; + }) + }
+
{ + bars.map((bar, index) => { + return
+ {bar.label} +
; + }) + }
+
{ + bars.map((bar, index) => { + return
; + }) + }
+ +
+
; }; diff --git a/packages/trace-viewer/src/ui/watchMode.css b/packages/trace-viewer/src/ui/watchMode.css index c164bd8771..404a44282f 100644 --- a/packages/trace-viewer/src/ui/watchMode.css +++ b/packages/trace-viewer/src/ui/watchMode.css @@ -18,7 +18,7 @@ background-color: var(--vscode-sideBar-background); } -.watch-mode-sidebar input { +.watch-mode-sidebar input[type=search] { flex: auto; } @@ -44,16 +44,11 @@ margin: 0; } -.watch-mode-sidebar .toolbar h3.title { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; +.watch-mode-sidebar .section-title { font-size: 11px; - min-width: 3ch; - margin: 0 10px; - -webkit-margin-before: 0; - -webkit-margin-after: 0; text-transform: uppercase; + font-weight: bold; + margin: 5px; } .watch-mode-sidebar .spacer { diff --git a/packages/trace-viewer/src/ui/watchMode.tsx b/packages/trace-viewer/src/ui/watchMode.tsx index 5164eedb02..caa105bf19 100644 --- a/packages/trace-viewer/src/ui/watchMode.tsx +++ b/packages/trace-viewer/src/ui/watchMode.tsx @@ -33,23 +33,97 @@ import type * as trace from '@trace/trace'; let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {}; let updateStepsProgress: () => void = () => {}; let runWatchedTests = () => {}; +let runVisibleTests = () => {}; export const WatchModeView: React.FC<{}> = ({ }) => { + const [projectNames, setProjectNames] = React.useState([]); const [rootSuite, setRootSuite] = React.useState<{ value: Suite | undefined }>({ value: undefined }); + const [isRunningTest, setIsRunningTest] = React.useState(false); const [progress, setProgress] = React.useState({ total: 0, passed: 0, failed: 0 }); + const [selectedTestItem, setSelectedTestItem] = React.useState(undefined); + const [settingsVisible, setSettingsVisible] = React.useState(false); + updateRootSuite = (rootSuite: Suite, { passed, failed }: Progress) => { setRootSuite({ value: rootSuite }); progress.passed = passed; progress.failed = failed; setProgress({ ...progress }); }; - const [selectedTreeItemId, setSelectedTreeItemId] = React.useState(); - const [isRunningTest, setIsRunningTest] = React.useState(false); - const [filterText, setFilterText] = React.useState(''); - const [projectNames, setProjectNames] = React.useState([]); - const [expandedItems, setExpandedItems] = React.useState>(new Map()); + const runTests = (testIds: string[]) => { + setProgress({ total: testIds.length, passed: 0, failed: 0 }); + setIsRunningTest(true); + sendMessage('run', { testIds }).then(() => { + setIsRunningTest(false); + }); + }; + + React.useEffect(() => { + if (projectNames.length === 0 && rootSuite.value?.suites.length) + setProjectNames([rootSuite.value?.suites[0].title]); + }, [projectNames, rootSuite]); + + return + +
+ +
setSettingsVisible(false)}>Tests
+ + sendMessageNoReply('stop')} disabled={!isRunningTest}> + +
+ { setSettingsVisible(!settingsVisible); }}> +
+ { !settingsVisible && } + { settingsVisible &&
+
+
Projects
+
+ setSettingsVisible(false)}> +
+ {(rootSuite.value?.suites || []).map(suite => { + return
+ { + const copy = [...projectNames]; + if (copy.includes(suite.title)) + copy.splice(copy.indexOf(suite.title), 1); + else + copy.push(suite.title); + setProjectNames(copy); + }} style={{ margin: '0 5px 0 10px' }} /> + +
; + })} +
Appearance
+
+ toggleTheme()}>Toggle color mode +
+
} + {isRunningTest &&
+ Running: {progress.total} tests | {progress.passed} passed | {progress.failed} failed +
} +
+
; +}; + +export const TestList: React.FC<{ + projectNames: string[], + rootSuite: { value: Suite | undefined }, + runTests: (testIds: string[]) => void, + isRunningTest: boolean, + onTestItemSelected: (test: TestItem | undefined) => void, +}> = ({ projectNames, rootSuite, runTests, isRunningTest, onTestItemSelected }) => { + const [filterText, setFilterText] = React.useState(''); + const [selectedTreeItemId, setSelectedTreeItemId] = React.useState(); + const [expandedItems, setExpandedItems] = React.useState>(new Map()); const inputRef = React.useRef(null); React.useEffect(() => { @@ -57,11 +131,6 @@ export const WatchModeView: React.FC<{}> = ({ resetCollectingRootSuite(); }, []); - React.useEffect(() => { - if (projectNames.length === 0 && rootSuite.value?.suites.length) - setProjectNames([rootSuite.value?.suites[0].title]); - }, [projectNames, rootSuite]); - const { filteredItems, treeItemMap, visibleTestIds } = React.useMemo(() => { const treeItems = createTree(rootSuite.value, projectNames); const filteredItems = filterTree(treeItems, filterText); @@ -78,6 +147,7 @@ export const WatchModeView: React.FC<{}> = ({ return { treeItemMap, visibleTestIds, filteredItems }; }, [filterText, rootSuite, projectNames]); + runVisibleTests = () => runTests([...visibleTestIds]); const { listItems } = React.useMemo(() => { const listItems = flattenTree(filteredItems, expandedItems, !!filterText.trim()); @@ -95,6 +165,8 @@ export const WatchModeView: React.FC<{}> = ({ return { selectedTreeItem, selectedTestItem }; }, [selectedTreeItemId, treeItemMap]); + onTestItemSelected(selectedTestItem); + const runTreeItem = (treeItem: TreeItem) => { expandedItems.set(treeItem.id, true); setSelectedTreeItemId(treeItem.id); @@ -105,128 +177,79 @@ export const WatchModeView: React.FC<{}> = ({ runTests(collectTestIds(selectedTreeItem)); }; - const runTests = (testIds: string[]) => { - setProgress({ total: testIds.length, passed: 0, failed: 0 }); - setIsRunningTest(true); - sendMessage('run', { testIds }).then(() => { - setIsRunningTest(false); - }); - }; - - return - -
- -

Test explorer

- runTests([...visibleTestIds])} disabled={isRunningTest}> - sendMessageNoReply('stop')} disabled={!isRunningTest}> - -
- toggleTheme()}> -
- - { - setFilterText(e.target.value); - }} - onKeyDown={e => { - if (e.key === 'Enter') - runTests([...visibleTestIds]); - }}> - - treeItem.id } - itemRender={(treeItem: TreeItem) => { - return
-
{treeItem.title}
- runTreeItem(treeItem)} disabled={isRunningTest}> -
; + return
+ + { + setFilterText(e.target.value); }} - itemIcon={(treeItem: TreeItem) => { - if (treeItem.kind === 'case' && treeItem.children?.length === 1) - treeItem = treeItem.children[0]; - if (treeItem.kind === 'test') { - const ok = treeItem.test.outcome() === 'expected'; - const failed = treeItem.test.results.length && treeItem.test.outcome() !== 'expected'; - const running = treeItem.test.results.some(r => r.duration === -1); - if (running) - return 'codicon-loading'; - if (ok) - return 'codicon-check'; - if (failed) - return 'codicon-error'; - } else { - return treeItem.expanded ? 'codicon-chevron-down' : 'codicon-chevron-right'; - } - }} - itemIndent={(treeItem: TreeItem) => treeItem.kind === 'file' ? 0 : treeItem.kind === 'case' ? 1 : 2} - selectedItem={selectedTreeItem} - onAccepted={runTreeItem} - onLeftArrow={(treeItem: TreeItem) => { - if (treeItem.children && treeItem.expanded) { - expandedItems.set(treeItem.id, false); - setExpandedItems(new Map(expandedItems)); - } else { - setSelectedTreeItemId(treeItem.parent?.id); - } - }} - onRightArrow={(treeItem: TreeItem) => { - if (treeItem.children) { - expandedItems.set(treeItem.id, true); - setExpandedItems(new Map(expandedItems)); - } - setRootSuite({ ...rootSuite }); - }} - onSelected={(treeItem: TreeItem) => { - setSelectedTreeItemId(treeItem.id); - }} - onIconClicked={(treeItem: TreeItem) => { - if (treeItem.kind === 'test') - return; - if (treeItem.expanded) - expandedItems.set(treeItem.id, false); - else - expandedItems.set(treeItem.id, true); + onKeyDown={e => { + if (e.key === 'Enter') + runVisibleTests(); + }}> + + treeItem.id } + itemRender={(treeItem: TreeItem) => { + return
+
{treeItem.title}
+ runTreeItem(treeItem)} disabled={isRunningTest}> +
; + }} + itemIcon={(treeItem: TreeItem) => { + if (treeItem.kind === 'case' && treeItem.children?.length === 1) + treeItem = treeItem.children[0]; + if (treeItem.kind === 'test') { + const ok = treeItem.test.outcome() === 'expected'; + const failed = treeItem.test.results.length && treeItem.test.outcome() !== 'expected'; + const running = treeItem.test.results.some(r => r.duration === -1); + if (running) + return 'codicon-loading'; + if (ok) + return 'codicon-check'; + if (failed) + return 'codicon-error'; + } else { + return treeItem.expanded ? 'codicon-chevron-down' : 'codicon-chevron-right'; + } + }} + itemIndent={(treeItem: TreeItem) => treeItem.kind === 'file' ? 0 : treeItem.kind === 'case' ? 1 : 2} + selectedItem={selectedTreeItem} + onAccepted={runTreeItem} + onLeftArrow={(treeItem: TreeItem) => { + if (treeItem.children && treeItem.expanded) { + expandedItems.set(treeItem.id, false); setExpandedItems(new Map(expandedItems)); - }} - showNoItemsMessage={true}>
- {(rootSuite.value?.suites.length || 0) > 1 &&
- -

Projects

-
- { - const copy = [...projectNames]; - if (copy.includes(suite.title)) - copy.splice(copy.indexOf(suite.title), 1); - else - copy.push(suite.title); - setProjectNames(copy); - }} - itemRender={(suite: Suite) => { - return ; - }} - /> -
} - {isRunningTest &&
- Running: {progress.total} tests | {progress.passed} passed | {progress.failed} failed -
} - {!isRunningTest &&
- Total: {visibleTestIds.size} tests -
} -
- ; + } else { + setSelectedTreeItemId(treeItem.parent?.id); + } + }} + onRightArrow={(treeItem: TreeItem) => { + if (treeItem.children) { + expandedItems.set(treeItem.id, true); + setExpandedItems(new Map(expandedItems)); + } + }} + onSelected={(treeItem: TreeItem) => { + setSelectedTreeItemId(treeItem.id); + }} + onIconClicked={(treeItem: TreeItem) => { + if (treeItem.kind === 'test') + return; + if (treeItem.expanded) + expandedItems.set(treeItem.id, false); + else + expandedItems.set(treeItem.id, true); + setExpandedItems(new Map(expandedItems)); + }} + noItemsMessage='No tests' />; +
; }; export const TraceView: React.FC<{ testItem: TestItem | undefined, - isRunningTest: boolean, -}> = ({ testItem, isRunningTest }) => { +}> = ({ testItem }) => { const [model, setModel] = React.useState(); const [stepsProgress, setStepsProgress] = React.useState(0); updateStepsProgress = () => setStepsProgress(stepsProgress + 1); @@ -249,20 +272,9 @@ export const TraceView: React.FC<{ setModel(undefined); } })(); - }, [testItem, isRunningTest, stepsProgress]); + }, [testItem, stepsProgress]); - if (!model) { - return
-
-
Run test to see the trace
-
-
Double click a test or hit Enter
-
-
-
; - } - - return ; + return ; }; declare global { diff --git a/packages/trace-viewer/src/ui/workbench.tsx b/packages/trace-viewer/src/ui/workbench.tsx index 52056c9b60..aaa60c360c 100644 --- a/packages/trace-viewer/src/ui/workbench.tsx +++ b/packages/trace-viewer/src/ui/workbench.tsx @@ -31,25 +31,26 @@ import './workbench.css'; import { MetadataView } from './metadataView'; export const Workbench: React.FunctionComponent<{ - model: MultiTraceModel, + model?: MultiTraceModel, }> = ({ model }) => { const [selectedAction, setSelectedAction] = React.useState(); const [highlightedAction, setHighlightedAction] = React.useState(); const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState('actions'); const [selectedPropertiesTab, setSelectedPropertiesTab] = React.useState('logs'); - const activeAction = highlightedAction || selectedAction; + const activeAction = model ? highlightedAction || selectedAction : undefined; const { errors, warnings } = activeAction ? modelUtil.stats(activeAction) : { errors: 0, warnings: 0 }; const consoleCount = errors + warnings; const networkCount = activeAction ? modelUtil.resourcesForAction(activeAction).length : 0; + const sdkLanguage = model?.sdkLanguage || 'javascript'; const tabs = [ - { id: 'logs', title: 'Call', count: 0, render: () => }, + { id: 'logs', title: 'Call', count: 0, render: () => }, { id: 'console', title: 'Console', count: consoleCount, render: () => }, { id: 'network', title: 'Network', count: networkCount, render: () => }, ]; - if (model.hasSource) + if (model?.hasSource) tabs.push({ id: 'source', title: 'Source', count: 0, render: () => }); return
@@ -60,27 +61,33 @@ export const Workbench: React.FunctionComponent<{ /> - + { - setSelectedAction(action); - }} - onHighlighted={action => { - setHighlightedAction(action); - }} - revealConsole={() => setSelectedPropertiesTab('console')} - /> }, - { id: 'metadata', + { + id: 'actions', + title: 'Actions', + count: 0, + component: { + setSelectedAction(action); + }} + onHighlighted={action => { + setHighlightedAction(action); + }} + revealConsole={() => setSelectedPropertiesTab('console')} + /> + }, + { + id: 'metadata', title: 'Metadata', count: 0, - render: () => + component: }, ] } selectedTab={selectedNavigatorTab} setSelectedTab={setSelectedNavigatorTab}/> diff --git a/packages/web/src/common.css b/packages/web/src/common.css index 056f3c8bb7..d15a5350de 100644 --- a/packages/web/src/common.css +++ b/packages/web/src/common.css @@ -101,7 +101,6 @@ svg { color: var(--green); } -.codicon-close, .codicon-error { color: var(--red); } diff --git a/packages/web/src/components/listView.css b/packages/web/src/components/listView.css index 69f13969d6..46cbe62afd 100644 --- a/packages/web/src/components/listView.css +++ b/packages/web/src/components/listView.css @@ -34,25 +34,24 @@ padding-left: 5px; } -.list-view-entry.highlighted, -.list-view-entry.selected { - background-color: var(--vscode-list-inactiveSelectionBackground); +.list-view-entry.highlighted:not(.selected) { + background-color: var(--vscode-list-inactiveSelectionBackground) !important; } .list-view-entry.selected { z-index: 10; } -.list-view-entry.highlighted { - background-color: var(--vscode-list-inactiveSelectionBackground); -} - -.list-view-content:focus .list-view-entry.selected:not(.error) { +.list-view-content:focus .list-view-entry.selected { background-color: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); outline: 1px solid var(--vscode-focusBorder); } +.list-view-content:focus .list-view-entry.selected * { + color: var(--vscode-list-activeSelectionForeground) !important; +} + .list-view-content:focus .list-view-entry.error.selected { outline: 1px solid var(--vscode-inputValidation-errorBorder); } diff --git a/packages/web/src/components/listView.tsx b/packages/web/src/components/listView.tsx index e766e9d752..045b0cd740 100644 --- a/packages/web/src/components/listView.tsx +++ b/packages/web/src/components/listView.tsx @@ -31,7 +31,7 @@ export type ListViewProps = { onRightArrow?: (item: any) => void, onHighlighted?: (item: any | undefined) => void, onIconClicked?: (item: any) => void, - showNoItemsMessage?: boolean, + noItemsMessage?: string, dataTestId?: string, }; @@ -49,7 +49,7 @@ export const ListView: React.FC = ({ onRightArrow, onHighlighted, onIconClicked, - showNoItemsMessage, + noItemsMessage, dataTestId, }) => { const itemListRef = React.createRef(); @@ -102,7 +102,7 @@ export const ListView: React.FC = ({ }} ref={itemListRef} > - {showNoItemsMessage && items.length === 0 &&
No items
} + {noItemsMessage && items.length === 0 &&
{noItemsMessage}
} {items.map((item, index) => React.ReactElement; + component?: React.ReactElement; + render?: () => React.ReactElement; } export const TabbedPane: React.FunctionComponent<{ tabs: TabbedPaneTabModel[], + leftToolbar?: React.ReactElement[], + rightToolbar?: React.ReactElement[], selectedTab: string, setSelectedTab: (tab: string) => void -}> = ({ tabs, selectedTab, setSelectedTab }) => { +}> = ({ tabs, selectedTab, setSelectedTab, leftToolbar, rightToolbar }) => { return
- { - tabs.map(tab => ( + {[ + ...leftToolbar || [], + ...tabs.map(tab => ( - )) - } + >)), + ...rightToolbar || [], + ]} { tabs.map(tab => { + if (tab.component) + return
{tab.component}
; if (selectedTab === tab.id) - return
{tab.render()}
; + return
{tab.component || tab.render!()}
; }) }
diff --git a/tests/config/traceViewerFixtures.ts b/tests/config/traceViewerFixtures.ts index 86bf30faaf..aea98a6f54 100644 --- a/tests/config/traceViewerFixtures.ts +++ b/tests/config/traceViewerFixtures.ts @@ -45,7 +45,7 @@ class TraceViewerPage { constructor(public page: Page) { this.actionTitles = page.locator('.action-title'); - this.callLines = page.locator('.call-line'); + this.callLines = page.locator('.call-tab .call-line'); this.consoleLines = page.locator('.console-line'); this.consoleLineMessages = page.locator('.console-line-message'); this.consoleStacks = page.locator('.console-stack');