Convert plugin UI to Sandy

Summary:
Changelog: Updated Network plugin to Sandy UI, including several UI improvements

Converted UI to Sandy, and some minor code cleanups

Moved all mock related logic to its own dir

Fixes https://github.com/facebook/flipper/issues/2267

Reviewed By: passy

Differential Revision: D27966606

fbshipit-source-id: a64e20276d7f0966ce7a95b22557762a32c184cd
This commit is contained in:
Michel Weststrate
2021-05-06 04:26:41 -07:00
committed by Facebook GitHub Bot
parent 84d65b1a77
commit fc4a08eb55
11 changed files with 977 additions and 1436 deletions

View File

@@ -7,51 +7,29 @@
* @format
*/
import {Request, Header, Insights, RetryInsights} from './types';
import {
Component,
FlexColumn,
ManagedTable,
ManagedDataInspector,
Text,
Panel,
Select,
styled,
colors,
SmallText,
} from 'flipper';
import {decodeBody, getHeaderValue} from './utils';
import {formatBytes, BodyOptions} from './index';
import React from 'react';
import {Component} from 'react';
import querystring from 'querystring';
import xmlBeautifier from 'xml-beautifier';
import {ProtobufDefinitionsRepository} from './ProtobufDefinitionsRepository';
import {Base64} from 'js-base64';
const WrappingText = styled(Text)({
wordWrap: 'break-word',
width: '100%',
lineHeight: '125%',
padding: '3px 0',
});
import {
DataInspector,
Layout,
Panel,
styled,
theme,
CodeBlock,
} from 'flipper-plugin';
import {Select, Typography} from 'antd';
const KeyValueColumnSizes = {
key: '30%',
value: 'flex',
};
import {formatBytes, decodeBody, getHeaderValue} from './utils';
import {Request, Header, Insights, RetryInsights} from './types';
import {BodyOptions} from './index';
import {ProtobufDefinitionsRepository} from './ProtobufDefinitionsRepository';
import {KeyValueItem, KeyValueTable} from './KeyValueTable';
const KeyValueColumns = {
key: {
value: 'Key',
resizable: false,
},
value: {
value: 'Value',
resizable: false,
},
};
const {Text} = Typography;
type RequestDetailsProps = {
request: Request;
@@ -59,52 +37,23 @@ type RequestDetailsProps = {
onSelectFormat: (bodyFormat: string) => void;
};
export default class RequestDetails extends Component<RequestDetailsProps> {
static Container = styled(FlexColumn)({
height: '100%',
overflow: 'auto',
});
urlColumns = (url: URL) => {
return [
{
columns: {
key: {value: <WrappingText>Full URL</WrappingText>},
value: {
value: <WrappingText>{url.href}</WrappingText>,
},
},
copyText: url.href,
key: 'url',
key: 'Full URL',
value: url.href,
},
{
columns: {
key: {value: <WrappingText>Host</WrappingText>},
value: {
value: <WrappingText>{url.host}</WrappingText>,
},
},
copyText: url.host,
key: 'host',
key: 'Host',
value: url.host,
},
{
columns: {
key: {value: <WrappingText>Path</WrappingText>},
value: {
value: <WrappingText>{url.pathname}</WrappingText>,
},
},
copyText: url.pathname,
key: 'path',
key: 'Path',
value: url.pathname,
},
{
columns: {
key: {value: <WrappingText>Query String</WrappingText>},
value: {
value: <WrappingText>{url.search}</WrappingText>,
},
},
copyText: url.search,
key: 'query',
key: 'Query String',
value: url.search,
},
];
};
@@ -113,51 +62,28 @@ export default class RequestDetails extends Component<RequestDetailsProps> {
const {request, bodyFormat, onSelectFormat} = this.props;
const url = new URL(request.url);
const formattedText = bodyFormat == BodyOptions.formatted;
const formattedText = bodyFormat == 'formatted';
return (
<RequestDetails.Container>
<Panel
key="request"
heading={'Request'}
floating={false}
padded={false}>
<ManagedTable
multiline={true}
columnSizes={KeyValueColumnSizes}
columns={KeyValueColumns}
rows={this.urlColumns(url)}
autoHeight={true}
floating={false}
zebra={false}
/>
<>
<Panel key="request" title={'Request'}>
<KeyValueTable items={this.urlColumns(url)} />
</Panel>
{url.search ? (
<Panel
heading={'Request Query Parameters'}
floating={false}
padded={false}>
<Panel title={'Request Query Parameters'}>
<QueryInspector queryParams={url.searchParams} />
</Panel>
) : null}
{request.requestHeaders.length > 0 ? (
<Panel
key="headers"
heading={'Request Headers'}
floating={false}
padded={false}>
<Panel key="headers" title={'Request Headers'}>
<HeaderInspector headers={request.requestHeaders} />
</Panel>
) : null}
{request.requestData != null ? (
<Panel
key="requestData"
heading={'Request Body'}
floating={false}
padded={!formattedText}>
<Panel key="requestData" title={'Request Body'} pad>
<RequestBodyInspector
formattedText={formattedText}
request={request}
@@ -169,21 +95,18 @@ export default class RequestDetails extends Component<RequestDetailsProps> {
{request.responseHeaders?.length ? (
<Panel
key={'responseheaders'}
heading={`Response Headers${
title={`Response Headers${
request.responseIsMock ? ' (Mocked)' : ''
}`}
floating={false}
padded={false}>
}`}>
<HeaderInspector headers={request.responseHeaders} />
</Panel>
) : null}
<Panel
key={'responsebody'}
heading={`Response Body${
title={`Response Body${
request.responseIsMock ? ' (Mocked)' : ''
}`}
floating={false}
padded={!formattedText}>
pad>
<ResponseBodyInspector
formattedText={formattedText}
request={request}
@@ -191,64 +114,34 @@ export default class RequestDetails extends Component<RequestDetailsProps> {
</Panel>
</>
) : null}
<Panel
key="options"
heading={'Options'}
floating={false}
collapsed={true}>
<Panel key="options" title={'Options'} collapsed pad>
<Text>Body formatting:</Text>
<Select
grow
label="Body"
selected={bodyFormat}
value={bodyFormat}
onChange={onSelectFormat}
options={BodyOptions}
/>
</Panel>
{request.insights ? (
<Panel
key="insights"
heading={'Insights'}
floating={false}
collapsed={true}>
<Panel key="insights" title={'Insights'} collapsed>
<InsightsInspector insights={request.insights} />
</Panel>
) : null}
</RequestDetails.Container>
</>
);
}
}
class QueryInspector extends Component<{queryParams: URLSearchParams}> {
render() {
const {queryParams} = this.props;
const rows: any = [];
queryParams.forEach((value: string, key: string) => {
const rows: KeyValueItem[] = [];
this.props.queryParams.forEach((value: string, key: string) => {
rows.push({
columns: {
key: {
value: <WrappingText>{key}</WrappingText>,
},
value: {
value: <WrappingText>{value}</WrappingText>,
},
},
copyText: value,
key: key,
key,
value,
});
});
return rows.length > 0 ? (
<ManagedTable
multiline={true}
columnSizes={KeyValueColumnSizes}
columns={KeyValueColumns}
rows={rows}
autoHeight={true}
floating={false}
zebra={false}
/>
) : null;
return rows.length > 0 ? <KeyValueTable items={rows} /> : null;
}
}
@@ -272,43 +165,15 @@ class HeaderInspector extends Component<
new Map(),
);
const rows: any = [];
Array.from(computedHeaders.entries())
const rows = Array.from(computedHeaders.entries())
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] == b[0] ? 0 : 1))
.forEach(([key, value]) => {
rows.push({
columns: {
key: {
value: <WrappingText>{key}</WrappingText>,
},
value: {
value: <WrappingText>{value}</WrappingText>,
},
},
copyText: value,
key,
});
});
.map(([key, value]) => ({key, value}));
return rows.length > 0 ? (
<ManagedTable
multiline={true}
columnSizes={KeyValueColumnSizes}
columns={KeyValueColumns}
rows={rows}
autoHeight={true}
floating={false}
zebra={false}
/>
<KeyValueTable items={this.props.headers} />
) : null;
}
}
const BodyContainer = styled.div({
paddingTop: 10,
paddingBottom: 20,
});
type BodyFormatter = {
formatRequest?: (request: Request) => any;
formatResponse?: (request: Request) => any;
@@ -330,12 +195,12 @@ class RequestBodyInspector extends Component<{
const component = formatter.formatRequest(request);
if (component) {
return (
<BodyContainer>
<Layout.Container gap>
{component}
<FormattedBy>
Formatted by {formatter.constructor.name}
</FormattedBy>
</BodyContainer>
</Layout.Container>
);
}
} catch (e) {
@@ -366,12 +231,12 @@ class ResponseBodyInspector extends Component<{
const component = formatter.formatResponse(request);
if (component) {
return (
<BodyContainer>
<Layout.Container gap>
{component}
<FormattedBy>
Formatted by {formatter.constructor.name}
</FormattedBy>
</BodyContainer>
</Layout.Container>
);
}
} catch (e) {
@@ -386,17 +251,18 @@ class ResponseBodyInspector extends Component<{
}
}
const FormattedBy = styled(SmallText)({
const FormattedBy = styled(Text)({
marginTop: 8,
fontSize: '0.7em',
textAlign: 'center',
display: 'block',
color: theme.disabledColor,
});
const Empty = () => (
<BodyContainer>
<Layout.Container pad>
<Text>(empty)</Text>
</BodyContainer>
</Layout.Container>
);
function getRequestData(request: Request) {
@@ -420,29 +286,19 @@ function renderRawBody(request: Request, mode: 'request' | 'response') {
mode === 'request' ? getRequestData(request) : getResponseData(request),
);
return (
<BodyContainer>
<Layout.Container gap>
{decoded ? (
<Text selectable wordWrap="break-word">
{decoded}
</Text>
<CodeBlock>{decoded}</CodeBlock>
) : (
<>
<FormattedBy>(Failed to decode)</FormattedBy>
<Text selectable wordWrap="break-word">
{data}
</Text>
<CodeBlock>{data}</CodeBlock>
</>
)}
</BodyContainer>
</Layout.Container>
);
}
const MediaContainer = styled(FlexColumn)({
alignItems: 'center',
justifyContent: 'center',
width: '100%',
});
type ImageWithSizeProps = {
src: string;
};
@@ -459,13 +315,8 @@ class ImageWithSize extends Component<ImageWithSizeProps, ImageWithSizeState> {
marginBottom: 10,
});
static Text = styled(Text)({
color: colors.dark70,
fontSize: 14,
});
constructor(props: ImageWithSizeProps, context: any) {
super(props, context);
constructor(props: ImageWithSizeProps) {
super(props);
this.state = {
width: 0,
height: 0,
@@ -487,12 +338,12 @@ class ImageWithSize extends Component<ImageWithSizeProps, ImageWithSizeState> {
render() {
return (
<MediaContainer>
<Layout.Container center>
<ImageWithSize.Image src={this.props.src} />
<ImageWithSize.Text>
<Text type="secondary">
{this.state.width} x {this.state.height}
</ImageWithSize.Text>
</MediaContainer>
</Text>
</Layout.Container>
);
}
}
@@ -528,44 +379,36 @@ class VideoFormatter {
const contentType = getHeaderValue(request.responseHeaders, 'content-type');
if (contentType.startsWith('video/')) {
return (
<MediaContainer>
<Layout.Container center>
<VideoFormatter.Video controls={true}>
<source src={request.url} type={contentType} />
</VideoFormatter.Video>
</MediaContainer>
</Layout.Container>
);
}
};
}
class JSONText extends Component<{children: any}> {
static NoScrollbarText = styled(Text)({
overflowY: 'hidden',
});
render() {
const jsonObject = this.props.children;
return (
<JSONText.NoScrollbarText code whiteSpace="pre" selectable>
<CodeBlock>
{JSON.stringify(jsonObject, null, 2)}
{'\n'}
</JSONText.NoScrollbarText>
</CodeBlock>
);
}
}
class XMLText extends Component<{body: any}> {
static NoScrollbarText = styled(Text)({
overflowY: 'hidden',
});
render() {
const xmlPretty = xmlBeautifier(this.props.body);
return (
<XMLText.NoScrollbarText code whiteSpace="pre" selectable>
<CodeBlock>
{xmlPretty}
{'\n'}
</XMLText.NoScrollbarText>
</CodeBlock>
);
}
}
@@ -652,20 +495,14 @@ class JSONFormatter {
) {
try {
const data = JSON.parse(body);
return (
<ManagedDataInspector
collapsed={true}
expandRoot={true}
data={data}
/>
);
return <DataInspector collapsed expandRoot data={data} />;
} catch (SyntaxError) {
// Multiple top level JSON roots, map them one by one
const roots = body.split('\n');
return (
<ManagedDataInspector
collapsed={true}
expandRoot={true}
<DataInspector
collapsed
expandRoot
data={roots.map((json) => JSON.parse(json))}
/>
);
@@ -681,7 +518,7 @@ class LogEventFormatter {
if (typeof data.message === 'string') {
data.message = JSON.parse(data.message);
}
return <ManagedDataInspector expandRoot={true} data={data} />;
return <DataInspector expandRoot data={data} />;
}
}
}
@@ -693,7 +530,7 @@ class GraphQLBatchFormatter {
if (typeof data.queries === 'string') {
data.queries = JSON.parse(data.queries);
}
return <ManagedDataInspector expandRoot={true} data={data} />;
return <DataInspector expandRoot data={data} />;
}
}
}
@@ -717,10 +554,10 @@ class GraphQLFormatter {
const requestStartMs = serverMetadata['request_start_time_ms'];
const timeAtFlushMs = serverMetadata['time_at_flush_ms'];
return (
<WrappingText>
<Text>
{'Server wall time for initial response (ms): ' +
(timeAtFlushMs - requestStartMs)}
</WrappingText>
</Text>
);
}
formatRequest(request: Request) {
@@ -736,7 +573,7 @@ class GraphQLFormatter {
if (typeof data.query_params === 'string') {
data.query_params = JSON.parse(data.query_params);
}
return <ManagedDataInspector expandRoot={true} data={data} />;
return <DataInspector expandRoot data={data} />;
}
}
@@ -760,11 +597,7 @@ class GraphQLFormatter {
return (
<div>
{this.parsedServerTimeForFirstFlush(data)}
<ManagedDataInspector
collapsed={true}
expandRoot={true}
data={data}
/>
<DataInspector collapsed expandRoot data={data} />
</div>
);
} catch (SyntaxError) {
@@ -776,11 +609,7 @@ class GraphQLFormatter {
return (
<div>
{this.parsedServerTimeForFirstFlush(parsedResponses)}
<ManagedDataInspector
collapsed={true}
expandRoot={true}
data={parsedResponses}
/>
<DataInspector collapsed expandRoot data={parsedResponses} />
</div>
);
}
@@ -796,12 +625,7 @@ class FormUrlencodedFormatter {
if (!decoded) {
return undefined;
}
return (
<ManagedDataInspector
expandRoot={true}
data={querystring.parse(decoded)}
/>
);
return <DataInspector expandRoot data={querystring.parse(decoded)} />;
}
};
}
@@ -921,13 +745,13 @@ class InsightsInspector extends Component<{insights: Insights}> {
return `${formatBytes(value)}/sec`;
}
formatRetries(retry: RetryInsights): string {
formatRetries = (retry: RetryInsights): string => {
const timesWord = retry.limit === 1 ? 'time' : 'times';
return `${this.formatTime(retry.timeSpent)} (${
retry.count
} ${timesWord} out of ${retry.limit})`;
}
};
buildRow<T>(
name: string,
@@ -936,16 +760,8 @@ class InsightsInspector extends Component<{insights: Insights}> {
): any {
return value
? {
columns: {
key: {
value: <WrappingText>{name}</WrappingText>,
},
value: {
value: <WrappingText>{formatter(value)}</WrappingText>,
},
},
copyText: () => `${name}: ${formatter(value)}`,
key: name,
value: formatter(value),
}
: null;
}
@@ -955,7 +771,7 @@ class InsightsInspector extends Component<{insights: Insights}> {
const {buildRow, formatTime, formatSpeed, formatRetries} = this;
const rows = [
buildRow('Retries', insights.retries, formatRetries.bind(this)),
buildRow('Retries', insights.retries, formatRetries),
buildRow('DNS lookup time', insights.dnsLookupTime, formatTime),
buildRow('Connect time', insights.connectTime, formatTime),
buildRow('SSL handshake time', insights.sslHandshakeTime, formatTime),
@@ -968,16 +784,6 @@ class InsightsInspector extends Component<{insights: Insights}> {
buildRow('Transfer speed', insights.transferSpeed, formatSpeed),
].filter((r) => r != null);
return rows.length > 0 ? (
<ManagedTable
multiline={true}
columnSizes={KeyValueColumnSizes}
columns={KeyValueColumns}
rows={rows}
autoHeight={true}
floating={false}
zebra={false}
/>
) : null;
return rows.length > 0 ? <KeyValueTable items={rows} /> : null;
}
}