feat(trace-viewer): Move copy request buttons to toolbar (#35366)
This commit is contained in:
parent
3d603d1e5c
commit
aa278d3aed
|
@ -49,12 +49,6 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-request-details-copy {
|
|
||||||
display: flex;
|
|
||||||
margin: 8px 10px;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-font-preview {
|
.network-font-preview {
|
||||||
font-family: font-preview;
|
font-family: font-preview;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
|
@ -85,6 +79,31 @@
|
||||||
font-weight: bold;
|
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,
|
.green-circle::before,
|
||||||
.red-circle::before,
|
.red-circle::before,
|
||||||
.yellow-circle::before {
|
.yellow-circle::before {
|
||||||
|
|
|
@ -24,7 +24,11 @@ import { generateCurlCommand, generateFetchCall } from '../third_party/devtools'
|
||||||
import { CopyToClipboardTextButton } from './copyToClipboard';
|
import { CopyToClipboardTextButton } from './copyToClipboard';
|
||||||
import { getAPIRequestCodeGen } from './codegen';
|
import { getAPIRequestCodeGen } from './codegen';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
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<{
|
export const NetworkResourceDetails: React.FunctionComponent<{
|
||||||
resource: ResourceSnapshot;
|
resource: ResourceSnapshot;
|
||||||
|
@ -34,14 +38,30 @@ export const NetworkResourceDetails: React.FunctionComponent<{
|
||||||
}> = ({ resource, sdkLanguage, startTimeOffset, onClose }) => {
|
}> = ({ resource, sdkLanguage, startTimeOffset, onClose }) => {
|
||||||
const [selectedTab, setSelectedTab] = React.useState('request');
|
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
|
return <TabbedPane
|
||||||
dataTestId='network-request-details'
|
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={[
|
tabs={[
|
||||||
{
|
{
|
||||||
id: 'request',
|
id: 'request',
|
||||||
title: 'Request',
|
title: 'Request',
|
||||||
render: () => <RequestTab resource={resource} sdkLanguage={sdkLanguage} startTimeOffset={startTimeOffset} />,
|
render: () => <RequestTab resource={resource} startTimeOffset={startTimeOffset} requestBody={requestBody} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'response',
|
id: 'response',
|
||||||
|
@ -58,31 +78,36 @@ export const NetworkResourceDetails: React.FunctionComponent<{
|
||||||
setSelectedTab={setSelectedTab} />;
|
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<{
|
const RequestTab: React.FunctionComponent<{
|
||||||
resource: ResourceSnapshot;
|
resource: ResourceSnapshot;
|
||||||
sdkLanguage: Language;
|
|
||||||
startTimeOffset: number;
|
startTimeOffset: number;
|
||||||
}> = ({ resource, sdkLanguage, startTimeOffset }) => {
|
requestBody: RequestBody,
|
||||||
const [requestBody, setRequestBody] = React.useState<{ text: string, mimeType?: string } | null>(null);
|
}> = ({ resource, startTimeOffset, requestBody }) => {
|
||||||
|
|
||||||
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]);
|
|
||||||
|
|
||||||
return <div className='network-request-details-tab'>
|
return <div className='network-request-details-tab'>
|
||||||
<div className='network-request-details-header'>General</div>
|
<div className='network-request-details-header'>General</div>
|
||||||
<div className='network-request-details-url'>{`URL: ${resource.request.url}`}</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'>{`Start: ${msToString(startTimeOffset)}`}</div>
|
||||||
<div className='network-request-details-general'>{`Duration: ${msToString(resource.time)}`}</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 && <div className='network-request-details-header'>Request Body</div>}
|
||||||
{requestBody && <CodeMirrorWrapper text={requestBody.text} mimeType={requestBody.mimeType} readOnly lineNumbers={true}/>}
|
{requestBody && <CodeMirrorWrapper text={requestBody.text} mimeType={requestBody.mimeType} readOnly lineNumbers={true}/>}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -20,11 +20,11 @@ import * as React from 'react';
|
||||||
import { clsx } from '../uiUtils';
|
import { clsx } from '../uiUtils';
|
||||||
|
|
||||||
export interface ToolbarButtonProps {
|
export interface ToolbarButtonProps {
|
||||||
title: string,
|
title?: string,
|
||||||
icon?: string,
|
icon?: string,
|
||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
toggled?: boolean,
|
toggled?: boolean,
|
||||||
onClick: (e: React.MouseEvent) => void,
|
onClick?: (e: React.MouseEvent) => void,
|
||||||
style?: React.CSSProperties,
|
style?: React.CSSProperties,
|
||||||
testId?: string,
|
testId?: string,
|
||||||
className?: string,
|
className?: string,
|
||||||
|
|
Loading…
Reference in New Issue