feat(ui): add error prompt button
This commit is contained in:
parent
2f8d448dbb
commit
5e3f175c54
|
|
@ -36,6 +36,7 @@ test.describe('New Todo', () => {
|
||||||
await expect(page.getByTestId('todo-title')).toHaveText([
|
await expect(page.getByTestId('todo-title')).toHaveText([
|
||||||
TODO_ITEMS[0],
|
TODO_ITEMS[0],
|
||||||
TODO_ITEMS[1],
|
TODO_ITEMS[1],
|
||||||
|
"faux"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 2);
|
await checkNumberOfTodosInLocalStorage(page, 2);
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ function isEqualAttachment(a: Attachment, b: AfterActionTraceEventAttachment): b
|
||||||
return a.name === b.name && a.path === b.path && a.sha1 === b.sha1;
|
return a.name === b.name && a.path === b.path && a.sha1 === b.sha1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachmentURL(attachment: Attachment, queryParams: Record<string, string> = {}) {
|
export function attachmentURL(attachment: Attachment, queryParams: Record<string, string> = {}) {
|
||||||
const params = new URLSearchParams(queryParams);
|
const params = new URLSearchParams(queryParams);
|
||||||
if (attachment.sha1) {
|
if (attachment.sha1) {
|
||||||
params.set('trace', attachment.traceUrl);
|
params.set('trace', attachment.traceUrl);
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ import './copyToClipboard.css';
|
||||||
export const CopyToClipboard: React.FunctionComponent<{
|
export const CopyToClipboard: React.FunctionComponent<{
|
||||||
value: string | (() => Promise<string>),
|
value: string | (() => Promise<string>),
|
||||||
description?: string,
|
description?: string,
|
||||||
}> = ({ value, description }) => {
|
copyIcon?: string;
|
||||||
const [icon, setIcon] = React.useState('copy');
|
}> = ({ value, description, copyIcon = 'copy' }) => {
|
||||||
|
const [icon, setIcon] = React.useState(copyIcon);
|
||||||
|
|
||||||
const handleCopy = React.useCallback(() => {
|
const handleCopy = React.useCallback(() => {
|
||||||
const valuePromise = typeof value === 'function' ? value() : Promise.resolve(value);
|
const valuePromise = typeof value === 'function' ? value() : Promise.resolve(value);
|
||||||
|
|
@ -30,7 +31,7 @@ export const CopyToClipboard: React.FunctionComponent<{
|
||||||
navigator.clipboard.writeText(value).then(() => {
|
navigator.clipboard.writeText(value).then(() => {
|
||||||
setIcon('check');
|
setIcon('check');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIcon('copy');
|
setIcon(copyIcon);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}, () => {
|
}, () => {
|
||||||
setIcon('close');
|
setIcon('close');
|
||||||
|
|
@ -39,8 +40,8 @@ export const CopyToClipboard: React.FunctionComponent<{
|
||||||
setIcon('close');
|
setIcon('close');
|
||||||
});
|
});
|
||||||
|
|
||||||
}, [value]);
|
}, [value, copyIcon]);
|
||||||
return <ToolbarButton title={description ? description : 'Copy'} icon={icon} onClick={handleCopy}/>;
|
return <ToolbarButton title={description ?? 'Copy'} icon={icon} onClick={handleCopy}/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CopyToClipboardTextButton: React.FunctionComponent<{
|
export const CopyToClipboardTextButton: React.FunctionComponent<{
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,33 @@ import { PlaceholderPanel } from './placeholderPanel';
|
||||||
import { renderAction } from './actionList';
|
import { renderAction } from './actionList';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
import type { StackFrame } from '@protocol/channels';
|
import type { StackFrame } from '@protocol/channels';
|
||||||
|
import { CopyToClipboard } from './copyToClipboard';
|
||||||
|
import { attachmentURL } from './attachmentsTab';
|
||||||
|
import { fixTestPrompt } from '@web/components/prompts';
|
||||||
|
|
||||||
|
const PromptButton: React.FC<{
|
||||||
|
error: string;
|
||||||
|
actions: modelUtil.ActionTraceEventInContext[];
|
||||||
|
}> = ({ error, actions }) => {
|
||||||
|
const [pageSnapshot, setPageSnapshot] = React.useState<string>();
|
||||||
|
|
||||||
|
React.useEffect(( )=> {
|
||||||
|
for (const action of actions) {
|
||||||
|
for (const attachment of action.attachments ?? []) {
|
||||||
|
if (attachment.name === 'pageSnapshot') {
|
||||||
|
fetch(attachmentURL({ ...attachment, traceUrl: action.context.traceUrl })).then(async response => {
|
||||||
|
setPageSnapshot(await response.text());
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [actions]);
|
||||||
|
|
||||||
|
const prompt = React.useMemo(() => fixTestPrompt(error, undefined, pageSnapshot), [error, pageSnapshot]);
|
||||||
|
|
||||||
|
return <CopyToClipboard value={prompt} copyIcon='copilot' description="Copy prompt to clipboard" />;
|
||||||
|
};
|
||||||
|
|
||||||
export type ErrorDescription = {
|
export type ErrorDescription = {
|
||||||
action?: modelUtil.ActionTraceEventInContext;
|
action?: modelUtil.ActionTraceEventInContext;
|
||||||
|
|
@ -44,9 +71,10 @@ export function useErrorsTabModel(model: modelUtil.MultiTraceModel | undefined):
|
||||||
|
|
||||||
export const ErrorsTab: React.FunctionComponent<{
|
export const ErrorsTab: React.FunctionComponent<{
|
||||||
errorsModel: ErrorsTabModel,
|
errorsModel: ErrorsTabModel,
|
||||||
|
actions: modelUtil.ActionTraceEventInContext[],
|
||||||
sdkLanguage: Language,
|
sdkLanguage: Language,
|
||||||
revealInSource: (error: ErrorDescription) => void,
|
revealInSource: (error: ErrorDescription) => void,
|
||||||
}> = ({ errorsModel, sdkLanguage, revealInSource }) => {
|
}> = ({ errorsModel, sdkLanguage, revealInSource, actions }) => {
|
||||||
if (!errorsModel.errors.size)
|
if (!errorsModel.errors.size)
|
||||||
return <PlaceholderPanel text='No errors' />;
|
return <PlaceholderPanel text='No errors' />;
|
||||||
|
|
||||||
|
|
@ -72,7 +100,11 @@ export const ErrorsTab: React.FunctionComponent<{
|
||||||
{location && <div className='action-location'>
|
{location && <div className='action-location'>
|
||||||
@ <span title={longLocation} onClick={() => revealInSource(error)}>{location}</span>
|
@ <span title={longLocation} onClick={() => revealInSource(error)}>{location}</span>
|
||||||
</div>}
|
</div>}
|
||||||
|
<span style={{ position: 'absolute', right: '20px' }}>
|
||||||
|
<PromptButton error={message} actions={actions} />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ErrorMessage error={message} />
|
<ErrorMessage error={message} />
|
||||||
</div>;
|
</div>;
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
else
|
else
|
||||||
setRevealedError(error);
|
setRevealedError(error);
|
||||||
selectPropertiesTab('source');
|
selectPropertiesTab('source');
|
||||||
}} />
|
}} actions={model?.actions ?? []} />
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fallback location w/o action stands for file / test.
|
// Fallback location w/o action stands for file / test.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue