split up highlight and reveal
This commit is contained in:
parent
c397634fc8
commit
26e7b1e77a
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ActionTraceEvent } from '@trace/trace';
|
import type { ActionTraceEvent, AfterActionTraceEventAttachment } from '@trace/trace';
|
||||||
import { msToString } from '@web/uiUtils';
|
import { msToString } from '@web/uiUtils';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './actionList.css';
|
import './actionList.css';
|
||||||
|
|
@ -36,7 +36,7 @@ export interface ActionListProps {
|
||||||
onSelected?: (action: ActionTraceEventInContext) => void,
|
onSelected?: (action: ActionTraceEventInContext) => void,
|
||||||
onHighlighted?: (action: ActionTraceEventInContext | undefined) => void,
|
onHighlighted?: (action: ActionTraceEventInContext | undefined) => void,
|
||||||
revealConsole?: () => void,
|
revealConsole?: () => void,
|
||||||
revealAttachments(): void,
|
revealAttachment(attachment: AfterActionTraceEventAttachment): void,
|
||||||
isLive?: boolean,
|
isLive?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ export const ActionList: React.FC<ActionListProps> = ({
|
||||||
onSelected,
|
onSelected,
|
||||||
onHighlighted,
|
onHighlighted,
|
||||||
revealConsole,
|
revealConsole,
|
||||||
revealAttachments,
|
revealAttachment,
|
||||||
isLive,
|
isLive,
|
||||||
}) => {
|
}) => {
|
||||||
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
||||||
|
|
@ -71,8 +71,8 @@ export const ActionList: React.FC<ActionListProps> = ({
|
||||||
}, [setSelectedTime]);
|
}, [setSelectedTime]);
|
||||||
|
|
||||||
const render = React.useCallback((item: ActionTreeItem) => {
|
const render = React.useCallback((item: ActionTreeItem) => {
|
||||||
return renderAction(item.action!, { sdkLanguage, revealConsole, revealAttachments, isLive, showDuration: true, showBadges: true });
|
return renderAction(item.action!, { sdkLanguage, revealConsole, revealAttachment, isLive, showDuration: true, showBadges: true });
|
||||||
}, [isLive, revealConsole, revealAttachments, sdkLanguage]);
|
}, [isLive, revealConsole, revealAttachment, sdkLanguage]);
|
||||||
|
|
||||||
const isVisible = React.useCallback((item: ActionTreeItem) => {
|
const isVisible = React.useCallback((item: ActionTreeItem) => {
|
||||||
return !selectedTime || !item.action || (item.action!.startTime <= selectedTime.maximum && item.action!.endTime >= selectedTime.minimum);
|
return !selectedTime || !item.action || (item.action!.startTime <= selectedTime.maximum && item.action!.endTime >= selectedTime.minimum);
|
||||||
|
|
@ -109,15 +109,15 @@ export const renderAction = (
|
||||||
options: {
|
options: {
|
||||||
sdkLanguage?: Language,
|
sdkLanguage?: Language,
|
||||||
revealConsole?: () => void,
|
revealConsole?: () => void,
|
||||||
revealAttachments?(): void,
|
revealAttachment?(attachment: AfterActionTraceEventAttachment): void,
|
||||||
isLive?: boolean,
|
isLive?: boolean,
|
||||||
showDuration?: boolean,
|
showDuration?: boolean,
|
||||||
showBadges?: boolean,
|
showBadges?: boolean,
|
||||||
}) => {
|
}) => {
|
||||||
const { sdkLanguage, revealConsole, revealAttachments, isLive, showDuration, showBadges } = options;
|
const { sdkLanguage, revealConsole, revealAttachment, isLive, showDuration, showBadges } = options;
|
||||||
const { errors, warnings } = modelUtil.stats(action);
|
const { errors, warnings } = modelUtil.stats(action);
|
||||||
const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined;
|
const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined;
|
||||||
const showAttachments = !!action.attachments?.length && !!revealAttachments;
|
const showAttachments = !!action.attachments?.length && !!revealAttachment;
|
||||||
|
|
||||||
let time: string = '';
|
let time: string = '';
|
||||||
if (action.endTime)
|
if (action.endTime)
|
||||||
|
|
@ -134,7 +134,7 @@ export const renderAction = (
|
||||||
{action.class === 'APIRequestContext' && action.params.url && <div className='action-url' title={action.params.url}>{excludeOrigin(action.params.url)}</div>}
|
{action.class === 'APIRequestContext' && action.params.url && <div className='action-url' title={action.params.url}>{excludeOrigin(action.params.url)}</div>}
|
||||||
</div>
|
</div>
|
||||||
{(showDuration || showBadges || showAttachments) && <div className='spacer'></div>}
|
{(showDuration || showBadges || showAttachments) && <div className='spacer'></div>}
|
||||||
{showAttachments && <ToolbarButton icon='attach' title='Open Attachment' onClick={revealAttachments} />}
|
{showAttachments && <ToolbarButton icon='attach' title='Open Attachment' onClick={() => revealAttachment(action.attachments![0])} />}
|
||||||
{showDuration && <div className='action-duration'>{time || <span className='codicon codicon-loading'></span>}</div>}
|
{showDuration && <div className='action-duration'>{time || <span className='codicon codicon-loading'></span>}</div>}
|
||||||
{showBadges && <div className='action-icons' onClick={() => revealConsole?.()}>
|
{showBadges && <div className='action-icons' onClick={() => revealConsole?.()}>
|
||||||
{!!errors && <div className='action-icon'><span className='codicon codicon-error'></span><span className='action-icon-value'>{errors}</span></div>}
|
{!!errors && <div className='action-icon'><span className='codicon codicon-error'></span><span className='action-icon-value'>{errors}</span></div>}
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,11 @@ type Attachment = AfterActionTraceEventAttachment & { traceUrl: string };
|
||||||
|
|
||||||
type ExpandableAttachmentProps = {
|
type ExpandableAttachmentProps = {
|
||||||
attachment: Attachment;
|
attachment: Attachment;
|
||||||
highlight?: boolean;
|
reveal: boolean;
|
||||||
|
highlight: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ExpandableAttachment: React.FunctionComponent<ExpandableAttachmentProps> = ({ attachment, highlight }) => {
|
const ExpandableAttachment: React.FunctionComponent<ExpandableAttachmentProps> = ({ attachment, reveal, highlight }) => {
|
||||||
const [expanded, setExpanded] = React.useState(false);
|
const [expanded, setExpanded] = React.useState(false);
|
||||||
const [attachmentText, setAttachmentText] = React.useState<string | null>(null);
|
const [attachmentText, setAttachmentText] = React.useState<string | null>(null);
|
||||||
const [placeholder, setPlaceholder] = React.useState<string | null>(null);
|
const [placeholder, setPlaceholder] = React.useState<string | null>(null);
|
||||||
|
|
@ -43,9 +44,9 @@ const ExpandableAttachment: React.FunctionComponent<ExpandableAttachmentProps> =
|
||||||
const hasContent = !!attachment.sha1 || !!attachment.path;
|
const hasContent = !!attachment.sha1 || !!attachment.path;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (highlight)
|
if (reveal)
|
||||||
ref.current?.scrollIntoView({ behavior: 'smooth' });
|
ref.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
}, [highlight]);
|
}, [reveal]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (expanded && attachmentText === null && placeholder === null) {
|
if (expanded && attachmentText === null && placeholder === null) {
|
||||||
|
|
@ -92,7 +93,8 @@ const ExpandableAttachment: React.FunctionComponent<ExpandableAttachmentProps> =
|
||||||
export const AttachmentsTab: React.FunctionComponent<{
|
export const AttachmentsTab: React.FunctionComponent<{
|
||||||
model: MultiTraceModel | undefined,
|
model: MultiTraceModel | undefined,
|
||||||
selectedAction: ActionTraceEventInContext | undefined,
|
selectedAction: ActionTraceEventInContext | undefined,
|
||||||
}> = ({ model, selectedAction }) => {
|
revealedAttachment?: AfterActionTraceEventAttachment,
|
||||||
|
}> = ({ model, selectedAction, revealedAttachment }) => {
|
||||||
const { diffMap, screenshots, attachments } = React.useMemo(() => {
|
const { diffMap, screenshots, attachments } = React.useMemo(() => {
|
||||||
const attachments = new Set<Attachment>();
|
const attachments = new Set<Attachment>();
|
||||||
const screenshots = new Set<Attachment>();
|
const screenshots = new Set<Attachment>();
|
||||||
|
|
@ -149,14 +151,18 @@ export const AttachmentsTab: React.FunctionComponent<{
|
||||||
{attachments.size ? <div className='attachments-section'>Attachments</div> : undefined}
|
{attachments.size ? <div className='attachments-section'>Attachments</div> : undefined}
|
||||||
{[...attachments.values()].map((a, i) => {
|
{[...attachments.values()].map((a, i) => {
|
||||||
return <div className='attachment-item' key={attachmentKey(a, i)}>
|
return <div className='attachment-item' key={attachmentKey(a, i)}>
|
||||||
<ExpandableAttachment attachment={a} highlight={isActiveAttachment(a, selectedAction)} />
|
<ExpandableAttachment
|
||||||
|
attachment={a}
|
||||||
|
highlight={selectedAction?.attachments?.some(selected => isEqualAttachment(a, selected)) ?? false}
|
||||||
|
reveal={!!revealedAttachment && isEqualAttachment(a, revealedAttachment)}
|
||||||
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
})}
|
})}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isActiveAttachment(attachment: Attachment, activeAction: ActionTraceEventInContext | undefined): boolean {
|
function isEqualAttachment(a: Attachment, b: AfterActionTraceEventAttachment): boolean {
|
||||||
return activeAction?.attachments?.some(a => a.name === attachment.name && a.path === attachment.path && a.sha1 === attachment.sha1) ?? false;
|
return a.name === b.name && a.path === b.path && a.sha1 === b.sha1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachmentURL(attachment: Attachment, queryParams: Record<string, string> = {}) {
|
function attachmentURL(attachment: Attachment, queryParams: Record<string, string> = {}) {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import type { Entry } from '@trace/har';
|
||||||
import './workbench.css';
|
import './workbench.css';
|
||||||
import { testStatusIcon, testStatusText } from './testUtils';
|
import { testStatusIcon, testStatusText } from './testUtils';
|
||||||
import type { UITestStatus } from './testUtils';
|
import type { UITestStatus } from './testUtils';
|
||||||
|
import type { AfterActionTraceEventAttachment } from '@trace/trace';
|
||||||
|
|
||||||
export const Workbench: React.FunctionComponent<{
|
export const Workbench: React.FunctionComponent<{
|
||||||
model?: modelUtil.MultiTraceModel,
|
model?: modelUtil.MultiTraceModel,
|
||||||
|
|
@ -58,6 +59,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
}> = ({ model, showSourcesFirst, rootDir, fallbackLocation, isLive, hideTimeline, status, annotations, inert, openPage, onOpenExternally, revealSource }) => {
|
}> = ({ model, showSourcesFirst, rootDir, fallbackLocation, isLive, hideTimeline, status, annotations, inert, openPage, onOpenExternally, revealSource }) => {
|
||||||
const [selectedCallId, setSelectedCallId] = React.useState<string | undefined>(undefined);
|
const [selectedCallId, setSelectedCallId] = React.useState<string | undefined>(undefined);
|
||||||
const [revealedError, setRevealedError] = React.useState<ErrorDescription | undefined>(undefined);
|
const [revealedError, setRevealedError] = React.useState<ErrorDescription | undefined>(undefined);
|
||||||
|
const [revealedAttachment, setRevealedAttachment] = React.useState<AfterActionTraceEventAttachment | undefined>(undefined);
|
||||||
const [highlightedCallId, setHighlightedCallId] = React.useState<string | undefined>();
|
const [highlightedCallId, setHighlightedCallId] = React.useState<string | undefined>();
|
||||||
const [highlightedEntry, setHighlightedEntry] = React.useState<Entry | undefined>();
|
const [highlightedEntry, setHighlightedEntry] = React.useState<Entry | undefined>();
|
||||||
const [highlightedConsoleMessage, setHighlightedConsoleMessage] = React.useState<ConsoleEntry | undefined>();
|
const [highlightedConsoleMessage, setHighlightedConsoleMessage] = React.useState<ConsoleEntry | undefined>();
|
||||||
|
|
@ -231,7 +233,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
id: 'attachments',
|
id: 'attachments',
|
||||||
title: 'Attachments',
|
title: 'Attachments',
|
||||||
count: attachments.length,
|
count: attachments.length,
|
||||||
render: () => <AttachmentsTab model={model} selectedAction={selectedAction} />
|
render: () => <AttachmentsTab model={model} selectedAction={selectedAction} revealedAttachment={revealedAttachment} />
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs: TabbedPaneTabModel[] = [
|
const tabs: TabbedPaneTabModel[] = [
|
||||||
|
|
@ -296,7 +298,10 @@ export const Workbench: React.FunctionComponent<{
|
||||||
setSelectedTime={setSelectedTime}
|
setSelectedTime={setSelectedTime}
|
||||||
onSelected={onActionSelected}
|
onSelected={onActionSelected}
|
||||||
onHighlighted={setHighlightedAction}
|
onHighlighted={setHighlightedAction}
|
||||||
revealAttachments={() => selectPropertiesTab('attachments')}
|
revealAttachment={attachment => {
|
||||||
|
selectPropertiesTab('attachments');
|
||||||
|
setRevealedAttachment(attachment);
|
||||||
|
}}
|
||||||
revealConsole={() => selectPropertiesTab('console')}
|
revealConsole={() => selectPropertiesTab('console')}
|
||||||
isLive={isLive}
|
isLive={isLive}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -167,9 +167,9 @@ test('should link from attachment step to attachments view', async ({ runUITest
|
||||||
|
|
||||||
const panel = page.getByRole('tabpanel', { name: 'Attachments' });
|
const panel = page.getByRole('tabpanel', { name: 'Attachments' });
|
||||||
const attachment = panel.getByLabel('my-attachment');
|
const attachment = panel.getByLabel('my-attachment');
|
||||||
await page.getByText('attach "spacer-1"').click();
|
await page.getByRole('treeitem', { name: 'attach "spacer-1"' }).getByLabel('Open Attachment').click();
|
||||||
await expect(attachment).not.toBeInViewport();
|
await expect(attachment).not.toBeInViewport();
|
||||||
await page.getByText('attach "my-attachment"').click();
|
await page.getByRole('treeitem', { name: 'attach "my-attachment"' }).getByLabel('Open Attachment').click();
|
||||||
await expect(attachment).toBeInViewport();
|
await expect(attachment).toBeInViewport();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue