JSON.parse(json))}
/>
);
}
}
};
}
class LogEventFormatter {
formatRequest = (request: Request) => {
if (request.url.indexOf('logging_client_event') > 0) {
const data = querystring.parse(decodeBody(request));
if (typeof data.message === 'string') {
data.message = JSON.parse(data.message);
}
return ;
}
};
}
class GraphQLBatchFormatter {
formatRequest = (request: Request) => {
if (request.url.indexOf('graphqlbatch') > 0) {
const data = querystring.parse(decodeBody(request));
if (typeof data.queries === 'string') {
data.queries = JSON.parse(data.queries);
}
return ;
}
};
}
class GraphQLFormatter {
parsedServerTimeForFirstFlush = (data: any) => {
const firstResponse =
Array.isArray(data) && data.length > 0 ? data[0] : data;
if (!firstResponse) {
return null;
}
const extensions = firstResponse['extensions'];
if (!extensions) {
return null;
}
const serverMetadata = extensions['server_metadata'];
if (!serverMetadata) {
return null;
}
const requestStartMs = serverMetadata['request_start_time_ms'];
const timeAtFlushMs = serverMetadata['time_at_flush_ms'];
return (
{'Server wall time for initial response (ms): ' +
(timeAtFlushMs - requestStartMs)}
);
};
formatRequest = (request: Request) => {
if (request.url.indexOf('graphql') > 0) {
const decoded = decodeBody(request);
if (!decoded) {
return undefined;
}
const data = querystring.parse(decoded);
if (typeof data.variables === 'string') {
data.variables = JSON.parse(data.variables);
}
if (typeof data.query_params === 'string') {
data.query_params = JSON.parse(data.query_params);
}
return ;
}
};
formatResponse = (request: Request, response: Response) => {
return this.format(
decodeBody(response),
getHeaderValue(response.headers, 'content-type'),
);
};
format = (body: string, contentType: string) => {
if (
contentType.startsWith('application/json') ||
contentType.startsWith('application/hal+json') ||
contentType.startsWith('text/javascript') ||
contentType.startsWith('text/html') ||
contentType.startsWith('application/x-fb-flatbuffer')
) {
try {
const data = JSON.parse(body);
return (
{this.parsedServerTimeForFirstFlush(data)}
);
} catch (SyntaxError) {
// Multiple top level JSON roots, map them one by one
const parsedResponses = body
.replace(/}{/g, '}\r\n{')
.split('\n')
.map((json) => JSON.parse(json));
return (
{this.parsedServerTimeForFirstFlush(parsedResponses)}
);
}
}
};
}
class FormUrlencodedFormatter {
formatRequest = (request: Request) => {
const contentType = getHeaderValue(request.headers, 'content-type');
if (contentType.startsWith('application/x-www-form-urlencoded')) {
const decoded = decodeBody(request);
if (!decoded) {
return undefined;
}
return (
);
}
};
}
class BinaryFormatter {
formatRequest(request: Request) {
return this.format(request);
}
formatResponse(_request: Request, response: Response) {
return this.format(response);
}
format(container: Request | Response) {
if (
getHeaderValue(container.headers, 'content-type') ===
'application/octet-stream'
) {
return '(binary data)'; // we could offer a download button here?
}
return undefined;
}
}
const BodyFormatters: Array = [
new ImageFormatter(),
new VideoFormatter(),
new LogEventFormatter(),
new GraphQLBatchFormatter(),
new GraphQLFormatter(),
new JSONFormatter(),
new FormUrlencodedFormatter(),
new XMLTextFormatter(),
new BinaryFormatter(),
];
const TextBodyFormatters: Array = [new JSONTextFormatter()];
class InsightsInspector extends Component<{insights: Insights}> {
formatTime(value: number): string {
return `${value} ms`;
}
formatSpeed(value: number): string {
return `${formatBytes(value)}/sec`;
}
formatRetries(retry: RetryInsights): string {
const timesWord = retry.limit === 1 ? 'time' : 'times';
return `${this.formatTime(retry.timeSpent)} (${
retry.count
} ${timesWord} out of ${retry.limit})`;
}
buildRow(
name: string,
value: T | null | undefined,
formatter: (value: T) => string,
): any {
return value
? {
columns: {
key: {
value: {name},
},
value: {
value: {formatter(value)},
},
},
copyText: () => `${name}: ${formatter(value)}`,
key: name,
}
: null;
}
render() {
const insights = this.props.insights;
const {buildRow, formatTime, formatSpeed, formatRetries} = this;
const rows = [
buildRow('Retries', insights.retries, formatRetries.bind(this)),
buildRow('DNS lookup time', insights.dnsLookupTime, formatTime),
buildRow('Connect time', insights.connectTime, formatTime),
buildRow('SSL handshake time', insights.sslHandshakeTime, formatTime),
buildRow('Pretransfer time', insights.preTransferTime, formatTime),
buildRow('Redirect time', insights.redirectsTime, formatTime),
buildRow('First byte wait time', insights.timeToFirstByte, formatTime),
buildRow('Data transfer time', insights.transferTime, formatTime),
buildRow('Post processing time', insights.postProcessingTime, formatTime),
buildRow('Bytes transfered', insights.bytesTransfered, formatBytes),
buildRow('Transfer speed', insights.transferSpeed, formatSpeed),
].filter((r) => r != null);
return rows.length > 0 ? (
) : null;
}
}