feat(ui-mode): add annotations tab (#31945)
<img width="867" alt="image" src="https://github.com/user-attachments/assets/7d714723-1d3f-49b2-944a-0a476d79aee8"> --------- Signed-off-by: Dmitry Gozman <dgozman@gmail.com> Co-authored-by: Dmitry Gozman <dgozman@gmail.com>
This commit is contained in:
parent
6af2635343
commit
47714d6559
|
|
@ -20,7 +20,7 @@ import * as icons from './icons';
|
||||||
import { TreeItem } from './treeItem';
|
import { TreeItem } from './treeItem';
|
||||||
import { CopyToClipboard } from './copyToClipboard';
|
import { CopyToClipboard } from './copyToClipboard';
|
||||||
import './links.css';
|
import './links.css';
|
||||||
import { linkifyText } from './renderUtils';
|
import { linkifyText } from '@web/renderUtils';
|
||||||
import { clsx } from '@web/uiUtils';
|
import { clsx } from '@web/uiUtils';
|
||||||
|
|
||||||
export function navigate(href: string) {
|
export function navigate(href: string) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { ProjectLink } from './links';
|
||||||
import { statusIcon } from './statusIcon';
|
import { statusIcon } from './statusIcon';
|
||||||
import './testCaseView.css';
|
import './testCaseView.css';
|
||||||
import { TestResultView } from './testResultView';
|
import { TestResultView } from './testResultView';
|
||||||
import { linkifyText } from './renderUtils';
|
import { linkifyText } from '@web/renderUtils';
|
||||||
import { hashStringToInt, msToString } from './utils';
|
import { hashStringToInt, msToString } from './utils';
|
||||||
import { clsx } from '@web/uiUtils';
|
import { clsx } from '@web/uiUtils';
|
||||||
|
|
||||||
|
|
|
||||||
28
packages/trace-viewer/src/ui/annotationsTab.css
Normal file
28
packages/trace-viewer/src/ui/annotationsTab.css
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.annotations-tab {
|
||||||
|
flex: auto;
|
||||||
|
line-height: 24px;
|
||||||
|
white-space: pre;
|
||||||
|
overflow: auto;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.annotation-item {
|
||||||
|
margin: 4px 8px;
|
||||||
|
text-wrap: wrap;
|
||||||
|
}
|
||||||
39
packages/trace-viewer/src/ui/annotationsTab.tsx
Normal file
39
packages/trace-viewer/src/ui/annotationsTab.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* 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 * as React from 'react';
|
||||||
|
import './annotationsTab.css';
|
||||||
|
import { PlaceholderPanel } from './placeholderPanel';
|
||||||
|
import { linkifyText } from '@web/renderUtils';
|
||||||
|
|
||||||
|
type Annotation = { type: string; description?: string; };
|
||||||
|
|
||||||
|
export const AnnotationsTab: React.FunctionComponent<{
|
||||||
|
annotations: Annotation[],
|
||||||
|
}> = ({ annotations }) => {
|
||||||
|
|
||||||
|
if (!annotations.length)
|
||||||
|
return <PlaceholderPanel text='No annotations' />;
|
||||||
|
|
||||||
|
return <div className='annotations-tab'>
|
||||||
|
{annotations.map((annotation, i) => {
|
||||||
|
return <div className='annotation-item' key={`annotation-${i}`}>
|
||||||
|
<span style={{ fontWeight: 'bold' }}>{annotation.type}</span>
|
||||||
|
{annotation.description && <span>: {linkifyText(annotation.description)}</span>}
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
@ -97,6 +97,7 @@ export const TraceView: React.FC<{
|
||||||
fallbackLocation={item.testFile}
|
fallbackLocation={item.testFile}
|
||||||
isLive={model?.isLive}
|
isLive={model?.isLive}
|
||||||
status={item.treeItem?.status}
|
status={item.treeItem?.status}
|
||||||
|
annotations={item.testCase?.annotations || []}
|
||||||
onOpenExternally={onOpenExternally}
|
onOpenExternally={onOpenExternally}
|
||||||
revealSource={revealSource}
|
revealSource={revealSource}
|
||||||
/>;
|
/>;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import type { TabbedPaneTabModel } from '@web/components/tabbedPane';
|
||||||
import { Timeline } from './timeline';
|
import { Timeline } from './timeline';
|
||||||
import { MetadataView } from './metadataView';
|
import { MetadataView } from './metadataView';
|
||||||
import { AttachmentsTab } from './attachmentsTab';
|
import { AttachmentsTab } from './attachmentsTab';
|
||||||
|
import { AnnotationsTab } from './annotationsTab';
|
||||||
import type { Boundaries } from '../geometry';
|
import type { Boundaries } from '../geometry';
|
||||||
import { InspectorTab } from './inspectorTab';
|
import { InspectorTab } from './inspectorTab';
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
|
|
@ -52,12 +53,13 @@ export const Workbench: React.FunctionComponent<{
|
||||||
onSelectionChanged?: (action: ActionTraceEventInContext) => void,
|
onSelectionChanged?: (action: ActionTraceEventInContext) => void,
|
||||||
isLive?: boolean,
|
isLive?: boolean,
|
||||||
status?: UITestStatus,
|
status?: UITestStatus,
|
||||||
|
annotations?: { type: string; description?: string; }[];
|
||||||
inert?: boolean,
|
inert?: boolean,
|
||||||
openPage?: (url: string, target?: string) => Window | any,
|
openPage?: (url: string, target?: string) => Window | any,
|
||||||
onOpenExternally?: (location: modelUtil.SourceLocation) => void,
|
onOpenExternally?: (location: modelUtil.SourceLocation) => void,
|
||||||
revealSource?: boolean,
|
revealSource?: boolean,
|
||||||
showSettings?: boolean,
|
showSettings?: boolean,
|
||||||
}> = ({ model, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive, status, inert, openPage, onOpenExternally, revealSource, showSettings }) => {
|
}> = ({ model, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive, status, annotations, inert, openPage, onOpenExternally, revealSource, showSettings }) => {
|
||||||
const [selectedAction, setSelectedActionImpl] = React.useState<ActionTraceEventInContext | undefined>(undefined);
|
const [selectedAction, setSelectedActionImpl] = React.useState<ActionTraceEventInContext | undefined>(undefined);
|
||||||
const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined);
|
const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined);
|
||||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>();
|
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>();
|
||||||
|
|
@ -223,6 +225,17 @@ export const Workbench: React.FunctionComponent<{
|
||||||
sourceTab,
|
sourceTab,
|
||||||
attachmentsTab,
|
attachmentsTab,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (annotations !== undefined) {
|
||||||
|
const annotationsTab: TabbedPaneTabModel = {
|
||||||
|
id: 'annotations',
|
||||||
|
title: 'Annotations',
|
||||||
|
count: annotations.length,
|
||||||
|
render: () => <AnnotationsTab annotations={annotations} />
|
||||||
|
};
|
||||||
|
tabs.push(annotationsTab);
|
||||||
|
}
|
||||||
|
|
||||||
if (showSourcesFirst) {
|
if (showSourcesFirst) {
|
||||||
const sourceTabIndex = tabs.indexOf(sourceTab);
|
const sourceTabIndex = tabs.indexOf(sourceTab);
|
||||||
tabs.splice(sourceTabIndex, 1);
|
tabs.splice(sourceTabIndex, 1);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue