Introduce button to copy request / response body to clipboard

Summary: Changelog: [Network] Introduce a copy button for request / response bodies

Reviewed By: passy

Differential Revision: D28222152

fbshipit-source-id: db33c8e91cbbe733502b32df75de14290e6f3d95
This commit is contained in:
Michel Weststrate
2021-05-06 04:26:41 -07:00
committed by Facebook GitHub Bot
parent 72e34bbd0d
commit 9c5967caf9
3 changed files with 53 additions and 5 deletions

View File

@@ -28,6 +28,7 @@ export const Panel: React.FC<{
collapsed?: boolean; collapsed?: boolean;
pad?: Spacing; pad?: Spacing;
gap?: Spacing; gap?: Spacing;
extraActions?: React.ReactElement | null;
}> = (props) => { }> = (props) => {
const [collapsed, setCollapsed] = useLocalStorageState( const [collapsed, setCollapsed] = useLocalStorageState(
`panel:${props.title}:collapsed`, `panel:${props.title}:collapsed`,
@@ -49,7 +50,16 @@ export const Panel: React.FC<{
onChange={toggle}> onChange={toggle}>
<Collapse.Panel <Collapse.Panel
key={props.title} key={props.title}
header={props.title} header={
props.extraActions ? (
<Layout.Right center>
<span>{props.title}</span>
{props.extraActions}
</Layout.Right>
) : (
props.title
)
}
showArrow={props.collapsible !== false}> showArrow={props.collapsible !== false}>
<Layout.Container pad={props.pad} gap={props.pad}> <Layout.Container pad={props.pad} gap={props.pad}>
{props.children} {props.children}

View File

@@ -23,11 +23,18 @@ import {
} from 'flipper-plugin'; } from 'flipper-plugin';
import {Select, Typography} from 'antd'; import {Select, Typography} from 'antd';
import {bodyAsBinary, bodyAsString, formatBytes, getHeaderValue} from './utils'; import {
bodyAsBinary,
bodyAsString,
formatBytes,
getHeaderValue,
isTextual,
} from './utils';
import {Request, Header, Insights, RetryInsights} from './types'; import {Request, Header, Insights, RetryInsights} from './types';
import {BodyOptions} from './index'; import {BodyOptions} from './index';
import {ProtobufDefinitionsRepository} from './ProtobufDefinitionsRepository'; import {ProtobufDefinitionsRepository} from './ProtobufDefinitionsRepository';
import {KeyValueItem, KeyValueTable} from './KeyValueTable'; import {KeyValueItem, KeyValueTable} from './KeyValueTable';
import {CopyOutlined} from '@ant-design/icons';
const {Text} = Typography; const {Text} = Typography;
@@ -35,6 +42,7 @@ type RequestDetailsProps = {
request: Request; request: Request;
bodyFormat: string; bodyFormat: string;
onSelectFormat: (bodyFormat: string) => void; onSelectFormat: (bodyFormat: string) => void;
onCopyText(test: string): void;
}; };
export default class RequestDetails extends Component<RequestDetailsProps> { export default class RequestDetails extends Component<RequestDetailsProps> {
urlColumns = (url: URL) => { urlColumns = (url: URL) => {
@@ -59,7 +67,7 @@ export default class RequestDetails extends Component<RequestDetailsProps> {
}; };
render() { render() {
const {request, bodyFormat, onSelectFormat} = this.props; const {request, bodyFormat, onSelectFormat, onCopyText} = this.props;
const url = new URL(request.url); const url = new URL(request.url);
const formattedText = bodyFormat == 'formatted'; const formattedText = bodyFormat == 'formatted';
@@ -83,7 +91,21 @@ export default class RequestDetails extends Component<RequestDetailsProps> {
) : null} ) : null}
{request.requestData != null ? ( {request.requestData != null ? (
<Panel key="requestData" title={'Request Body'} pad> <Panel
key="requestData"
title={'Request Body'}
extraActions={
isTextual(request.requestHeaders) ? (
<CopyOutlined
title="Copy request body"
onClick={(e) => {
e.stopPropagation();
onCopyText(request.requestData as string);
}}
/>
) : null
}
pad>
<RequestBodyInspector <RequestBodyInspector
formattedText={formattedText} formattedText={formattedText}
request={request} request={request}
@@ -106,6 +128,17 @@ export default class RequestDetails extends Component<RequestDetailsProps> {
title={`Response Body${ title={`Response Body${
request.responseIsMock ? ' (Mocked)' : '' request.responseIsMock ? ' (Mocked)' : ''
}`} }`}
extraActions={
isTextual(request.responseHeaders) && request.responseData ? (
<CopyOutlined
title="Copy response body"
onClick={(e) => {
e.stopPropagation();
onCopyText(request.responseData as string);
}}
/>
) : null
}
pad> pad>
<ResponseBodyInspector <ResponseBodyInspector
formattedText={formattedText} formattedText={formattedText}

View File

@@ -8,7 +8,7 @@
*/ */
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import {Button, Menu, Modal, Typography} from 'antd'; import {Button, Menu, message, Modal, Typography} from 'antd';
import { import {
Layout, Layout,
@@ -308,6 +308,10 @@ export function plugin(client: PluginClient<Events, Methods>) {
</Menu.Item> </Menu.Item>
); );
}, },
onCopyText(text: string) {
client.writeTextToClipboard(text);
message.success('Text copied to clipboard');
},
}; };
} }
@@ -418,6 +422,7 @@ function Sidebar() {
request={request} request={request}
bodyFormat={detailBodyFormat} bodyFormat={detailBodyFormat}
onSelectFormat={instance.onSelectFormat} onSelectFormat={instance.onSelectFormat}
onCopyText={instance.onCopyText}
/> />
); );
} }