diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.css b/packages/trace-viewer/src/ui/networkResourceDetails.css index 13582d40cf..b3f7a5c04f 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.css +++ b/packages/trace-viewer/src/ui/networkResourceDetails.css @@ -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 { diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.tsx b/packages/trace-viewer/src/ui/networkResourceDetails.tsx index aaa78d1786..5c87cb8d75 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.tsx +++ b/packages/trace-viewer/src/ui/networkResourceDetails.tsx @@ -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(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 ]} + leftToolbar={[]} + rightToolbar={[]} tabs={[ { id: 'request', title: 'Request', - render: () => , + render: () => , }, { 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 = <> Copied ; + const copyAsPlaywright = async () => getAPIRequestCodeGen(sdkLanguage).generatePlaywrightRequestCall(resource.request, requestBody?.text); + return ( +
+ + + Copy request + + + +
+ generateCurlCommand(resource)}/> + generateFetchCall(resource)}/> + +
+
+ ); +}; + 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
General
{`URL: ${resource.request.url}`}
@@ -103,12 +128,6 @@ const RequestTab: React.FunctionComponent<{
{`Start: ${msToString(startTimeOffset)}`}
{`Duration: ${msToString(resource.time)}`}
-
- generateCurlCommand(resource)} /> - generateFetchCall(resource)} /> - getAPIRequestCodeGen(sdkLanguage).generatePlaywrightRequestCall(resource.request, requestBody?.text)} /> -
- {requestBody &&
Request Body
} {requestBody && }
; diff --git a/packages/web/src/components/toolbarButton.tsx b/packages/web/src/components/toolbarButton.tsx index fbdfb738ab..c7d0230b98 100644 --- a/packages/web/src/components/toolbarButton.tsx +++ b/packages/web/src/components/toolbarButton.tsx @@ -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,