feat(trace-viewer): Move copy request buttons to toolbar (#35366)

This commit is contained in:
Chris 2025-03-31 15:21:57 +02:00 committed by GitHub
parent 3d603d1e5c
commit aa278d3aed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 39 deletions

View File

@ -49,12 +49,6 @@
overflow: hidden;
}
.network-request-details-copy {
display: flex;
margin: 8px 10px;
gap: 8px;
}
.network-font-preview {
font-family: font-preview;
font-size: 30px;
@ -85,6 +79,31 @@
font-weight: bold;
}
.copy-request-dropdown {
.copy-request-dropdown-toggle {
margin-right: 14px;
width: 135px;
}
&:not(:hover) .copy-request-dropdown-menu {
display: none;
}
.copy-request-dropdown-menu {
position: absolute;
display: flex;
flex-direction: column;
width: 135px;
z-index: 10;
background-color: var(--vscode-list-dropBackground);
button {
padding: 8px;
text-align: left;
}
}
}
.green-circle::before,
.red-circle::before,
.yellow-circle::before {

View File

@ -24,7 +24,11 @@ import { generateCurlCommand, generateFetchCall } from '../third_party/devtools'
import { CopyToClipboardTextButton } from './copyToClipboard';
import { getAPIRequestCodeGen } from './codegen';
import type { Language } from '@isomorphic/locatorGenerators';
import { msToString } from '@web/uiUtils';
import { msToString, useAsyncMemo } from '@web/uiUtils';
import type { Entry } from '@trace/har';
type RequestBody = { text: string, mimeType?: string } | null;
export const NetworkResourceDetails: React.FunctionComponent<{
resource: ResourceSnapshot;
@ -34,14 +38,30 @@ export const NetworkResourceDetails: React.FunctionComponent<{
}> = ({ resource, sdkLanguage, startTimeOffset, onClose }) => {
const [selectedTab, setSelectedTab] = React.useState('request');
const requestBody = useAsyncMemo<RequestBody>(async () => {
if (resource.request.postData) {
const requestContentTypeHeader = resource.request.headers.find(q => q.name.toLowerCase() === 'content-type');
const requestContentType = requestContentTypeHeader ? requestContentTypeHeader.value : '';
if (resource.request.postData._sha1) {
const response = await fetch(`sha1/${resource.request.postData._sha1}`);
return { text: formatBody(await response.text(), requestContentType), mimeType: requestContentType };
} else {
return { text: formatBody(resource.request.postData.text, requestContentType), mimeType: requestContentType };
}
} else {
return null;
}
}, [resource], null);
return <TabbedPane
dataTestId='network-request-details'
leftToolbar={[<ToolbarButton key='close' icon='close' title='Close' onClick={onClose}></ToolbarButton>]}
leftToolbar={[<ToolbarButton key='close' icon='close' title='Close' onClick={onClose} />]}
rightToolbar={[<CopyDropdown key='dropdown' requestBody={requestBody} resource={resource} sdkLanguage={sdkLanguage} />]}
tabs={[
{
id: 'request',
title: 'Request',
render: () => <RequestTab resource={resource} sdkLanguage={sdkLanguage} startTimeOffset={startTimeOffset} />,
render: () => <RequestTab resource={resource} startTimeOffset={startTimeOffset} requestBody={requestBody} />,
},
{
id: 'response',
@ -58,31 +78,36 @@ export const NetworkResourceDetails: React.FunctionComponent<{
setSelectedTab={setSelectedTab} />;
};
const CopyDropdown: React.FC<{
resource: Entry,
sdkLanguage: Language,
requestBody: RequestBody,
}> = ({ resource, sdkLanguage, requestBody }) => {
const copiedDescription = <><span className='codicon codicon-check' style={{ marginRight: '5px' }} /> Copied </>;
const copyAsPlaywright = async () => getAPIRequestCodeGen(sdkLanguage).generatePlaywrightRequestCall(resource.request, requestBody?.text);
return (
<div className='copy-request-dropdown'>
<ToolbarButton className='copy-request-dropdown-toggle'>
<span className='codicon codicon-copy' style={{ marginRight: '5px' }}/>
Copy request
<span className='codicon codicon-chevron-down' style={{ marginLeft: '5px' }}/>
</ToolbarButton>
<div className='copy-request-dropdown-menu'>
<CopyToClipboardTextButton description='Copy as cURL' copiedDescription={copiedDescription} value={() => generateCurlCommand(resource)}/>
<CopyToClipboardTextButton description='Copy as Fetch' copiedDescription={copiedDescription} value={() => generateFetchCall(resource)}/>
<CopyToClipboardTextButton description='Copy as Playwright' copiedDescription={copiedDescription} value={copyAsPlaywright}/>
</div>
</div>
);
};
const RequestTab: React.FunctionComponent<{
resource: ResourceSnapshot;
sdkLanguage: Language;
startTimeOffset: number;
}> = ({ resource, sdkLanguage, startTimeOffset }) => {
const [requestBody, setRequestBody] = React.useState<{ text: string, mimeType?: string } | null>(null);
React.useEffect(() => {
const readResources = async () => {
if (resource.request.postData) {
const requestContentTypeHeader = resource.request.headers.find(q => q.name.toLowerCase() === 'content-type');
const requestContentType = requestContentTypeHeader ? requestContentTypeHeader.value : '';
if (resource.request.postData._sha1) {
const response = await fetch(`sha1/${resource.request.postData._sha1}`);
setRequestBody({ text: formatBody(await response.text(), requestContentType), mimeType: requestContentType });
} else {
setRequestBody({ text: formatBody(resource.request.postData.text, requestContentType), mimeType: requestContentType });
}
} else {
setRequestBody(null);
}
};
readResources();
}, [resource]);
requestBody: RequestBody,
}> = ({ resource, startTimeOffset, requestBody }) => {
return <div className='network-request-details-tab'>
<div className='network-request-details-header'>General</div>
<div className='network-request-details-url'>{`URL: ${resource.request.url}`}</div>
@ -103,12 +128,6 @@ const RequestTab: React.FunctionComponent<{
<div className='network-request-details-general'>{`Start: ${msToString(startTimeOffset)}`}</div>
<div className='network-request-details-general'>{`Duration: ${msToString(resource.time)}`}</div>
<div className='network-request-details-copy'>
<CopyToClipboardTextButton description='Copy as cURL' value={() => generateCurlCommand(resource)} />
<CopyToClipboardTextButton description='Copy as Fetch' value={() => generateFetchCall(resource)} />
<CopyToClipboardTextButton description='Copy as Playwright' value={async () => getAPIRequestCodeGen(sdkLanguage).generatePlaywrightRequestCall(resource.request, requestBody?.text)} />
</div>
{requestBody && <div className='network-request-details-header'>Request Body</div>}
{requestBody && <CodeMirrorWrapper text={requestBody.text} mimeType={requestBody.mimeType} readOnly lineNumbers={true}/>}
</div>;

View File

@ -20,11 +20,11 @@ import * as React from 'react';
import { clsx } from '../uiUtils';
export interface ToolbarButtonProps {
title: string,
title?: string,
icon?: string,
disabled?: boolean,
toggled?: boolean,
onClick: (e: React.MouseEvent) => void,
onClick?: (e: React.MouseEvent) => void,
style?: React.CSSProperties,
testId?: string,
className?: string,