Skip to content

Commit

Permalink
[Inspector] Make Response tab faster (#180035)
Browse files Browse the repository at this point in the history
## Summary

This PR improves the speed of "Response" tab rendering in the Inspector
flyout. The issue was that the heavy logic of building links ("Open in
Console", "Open in Search Profiler") was executed for Response tab too
although links can appear only for Request tab.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
jughosta and kibanamachine committed Apr 5, 2024
1 parent 7be43f3 commit 7a00d2e
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,88 +9,26 @@
// We want to allow both right-clicking to open in a new tab and clicking through
// the "Open in Console" link. We could use `RedirectAppLinks` at the top level
// but that inserts a div which messes up the layout of the inspector.
/* eslint-disable @elastic/eui/href-or-on-click */

import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import type { ConnectionRequestParams } from '@elastic/transport';
import { i18n } from '@kbn/i18n';
import { XJsonLang } from '@kbn/monaco';
import { compressToEncodedURIComponent } from 'lz-string';
import React, { useCallback } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import React, { ReactNode } from 'react';
import { CodeEditor } from '@kbn/code-editor';
import { InspectorPluginStartDeps } from '../../../../plugin';

interface RequestCodeViewerProps {
indexPattern?: string;
requestParams?: ConnectionRequestParams;
json: string;
value: string;
actions?: Array<{ name: string; action: ReactNode }>;
}

const copyToClipboardLabel = i18n.translate('inspector.requests.copyToClipboardLabel', {
defaultMessage: 'Copy to clipboard',
});

const openInConsoleLabel = i18n.translate('inspector.requests.openInConsoleLabel', {
defaultMessage: 'Open in Console',
});

const openInSearchProfilerLabel = i18n.translate('inspector.requests.openInSearchProfilerLabel', {
defaultMessage: 'Open in Search Profiler',
});

/**
* @internal
*/
export const RequestCodeViewer = ({
indexPattern,
requestParams,
json,
}: RequestCodeViewerProps) => {
const { services } = useKibana<InspectorPluginStartDeps>();

const navigateToUrl = services.application?.navigateToUrl;

function getValue() {
if (!requestParams) {
return json;
}

const fullPath = requestParams.querystring
? `${requestParams.path}?${requestParams.querystring}`
: requestParams.path;

return `${requestParams.method} ${fullPath}\n${json}`;
}

const value = getValue();

const devToolsDataUri = compressToEncodedURIComponent(value);
const consoleHref = services.share.url.locators
.get('CONSOLE_APP_LOCATOR')
?.useUrl({ loadFrom: `data:text/plain,${devToolsDataUri}` });
// Check if both the Dev Tools UI and the Console UI are enabled.
const canShowDevTools =
services.application?.capabilities?.dev_tools.show && consoleHref !== undefined;
const shouldShowDevToolsLink = !!(requestParams && canShowDevTools);
const handleDevToolsLinkClick = useCallback(
() => consoleHref && navigateToUrl && navigateToUrl(consoleHref),
[consoleHref, navigateToUrl]
);

const searchProfilerDataUri = compressToEncodedURIComponent(json);
const searchProfilerHref = services.share.url.locators
.get('SEARCH_PROFILER_LOCATOR')
?.useUrl({ index: indexPattern, loadFrom: `data:text/plain,${searchProfilerDataUri}` });
// Check if both the Dev Tools UI and the SearchProfiler UI are enabled.
const canShowsearchProfiler =
services.application?.capabilities?.dev_tools.show && searchProfilerHref !== undefined;
const shouldShowsearchProfilerLink = !!(indexPattern && canShowsearchProfiler);
const handleSearchProfilerLinkClick = useCallback(
() => searchProfilerHref && navigateToUrl && navigateToUrl(searchProfilerHref),
[searchProfilerHref, navigateToUrl]
);

export const RequestCodeViewer = ({ value, actions }: RequestCodeViewerProps) => {
return (
<EuiFlexGroup
direction="column"
Expand All @@ -104,7 +42,7 @@ export const RequestCodeViewer = ({
<EuiFlexGroup justifyContent="flexEnd" gutterSize="m" wrap>
<EuiFlexItem grow={false}>
<div>
<EuiCopy textToCopy={json}>
<EuiCopy textToCopy={value}>
{(copy) => (
<EuiButtonEmpty
size="xs"
Expand All @@ -119,38 +57,12 @@ export const RequestCodeViewer = ({
</EuiCopy>
</div>
</EuiFlexItem>
{shouldShowDevToolsLink && (
<EuiFlexItem grow={false}>
<div>
<EuiButtonEmpty
size="xs"
flush="right"
iconType="wrench"
href={consoleHref}
onClick={handleDevToolsLinkClick}
data-test-subj="inspectorRequestOpenInConsoleButton"
>
{openInConsoleLabel}
</EuiButtonEmpty>
</div>
</EuiFlexItem>
)}
{shouldShowsearchProfilerLink && (
<EuiFlexItem grow={false}>
<div>
<EuiButtonEmpty
size="xs"
flush="right"
iconType="visBarHorizontal"
href={searchProfilerHref}
onClick={handleSearchProfilerLinkClick}
data-test-subj="inspectorRequestOpenInSearchProfilerButton"
>
{openInSearchProfilerLabel}
</EuiButtonEmpty>
</div>
</EuiFlexItem>
)}
{!!actions &&
actions.map((item) => (
<EuiFlexItem grow={false} key={item.name}>
<div>{item.action}</div>
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={true} data-test-subj="inspectorRequestCodeViewerContainer">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import React, { Component } from 'react';
import { Request } from '../../../../../common/adapters/request/types';
import { DetailViewProps } from './types';
import { RequestCodeViewer } from './req_code_viewer';
import { RequestDetailsRequestContent } from './req_details_request_content';

export class RequestDetailsRequest extends Component<DetailViewProps> {
static shouldShow = (request: Request) => Boolean(request && request.json);
Expand All @@ -22,7 +22,7 @@ export class RequestDetailsRequest extends Component<DetailViewProps> {
}

return (
<RequestCodeViewer
<RequestDetailsRequestContent
indexPattern={this.props.request.stats?.indexPattern?.value}
requestParams={this.props.request.response?.requestParams}
json={JSON.stringify(json, null, 2)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/* eslint-disable @elastic/eui/href-or-on-click */

import React, { useCallback, ReactNode } from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { compressToEncodedURIComponent } from 'lz-string';
import type { ConnectionRequestParams } from '@elastic/transport';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { RequestCodeViewer } from './req_code_viewer';
import type { InspectorKibanaServices } from '../types';

const openInConsoleLabel = i18n.translate('inspector.requests.openInConsoleLabel', {
defaultMessage: 'Open in Console',
});

const openInSearchProfilerLabel = i18n.translate('inspector.requests.openInSearchProfilerLabel', {
defaultMessage: 'Open in Search Profiler',
});

interface RequestDetailsRequestContentProps {
indexPattern?: string;
requestParams?: ConnectionRequestParams;
json: string;
}

export const RequestDetailsRequestContent: React.FC<RequestDetailsRequestContentProps> = ({
requestParams,
indexPattern,
json,
}) => {
const { services } = useKibana<InspectorKibanaServices>();

function getValue(): string {
if (!requestParams) {
return json;
}

const fullPath = requestParams.querystring
? `${requestParams.path}?${requestParams.querystring}`
: requestParams.path;

return `${requestParams.method} ${fullPath}\n${json}`;
}

const value = getValue();

const navigateToUrl = services.application?.navigateToUrl;

// "Open in Console" button
const devToolsDataUri = compressToEncodedURIComponent(value);
const consoleHref = services.share.url.locators
.get('CONSOLE_APP_LOCATOR')
?.useUrl({ loadFrom: `data:text/plain,${devToolsDataUri}` });
// Check if both the Dev Tools UI and the Console UI are enabled.
const canShowDevTools =
services.application?.capabilities?.dev_tools.show && consoleHref !== undefined;
const shouldShowDevToolsLink = !!(requestParams && canShowDevTools);
const handleDevToolsLinkClick = useCallback(
() => consoleHref && navigateToUrl && navigateToUrl(consoleHref),
[consoleHref, navigateToUrl]
);

// "Open in Search Profiler" button
const searchProfilerDataUri = compressToEncodedURIComponent(json);
const searchProfilerHref = services.share.url.locators
.get('SEARCH_PROFILER_LOCATOR')
?.useUrl({ index: indexPattern, loadFrom: `data:text/plain,${searchProfilerDataUri}` });
// Check if both the Dev Tools UI and the SearchProfiler UI are enabled.
const canShowsearchProfiler =
services.application?.capabilities?.dev_tools.show && searchProfilerHref !== undefined;
const shouldShowSearchProfilerLink = !!(indexPattern && canShowsearchProfiler);
const handleSearchProfilerLinkClick = useCallback(
() => searchProfilerHref && navigateToUrl && navigateToUrl(searchProfilerHref),
[searchProfilerHref, navigateToUrl]
);

const actions: Array<{ name: string; action: ReactNode }> = [];

if (shouldShowDevToolsLink) {
actions.push({
name: 'openInConsole',
action: (
<EuiButtonEmpty
size="xs"
flush="right"
iconType="wrench"
href={consoleHref}
onClick={handleDevToolsLinkClick}
data-test-subj="inspectorRequestOpenInConsoleButton"
>
{openInConsoleLabel}
</EuiButtonEmpty>
),
});
}

if (shouldShowSearchProfilerLink) {
actions.push({
name: 'openInSearchProfiler',
action: (
<EuiButtonEmpty
size="xs"
flush="right"
iconType="visBarHorizontal"
href={searchProfilerHref}
onClick={handleSearchProfilerLinkClick}
data-test-subj="inspectorRequestOpenInSearchProfilerButton"
>
{openInSearchProfilerLabel}
</EuiButtonEmpty>
),
});
}

return <RequestCodeViewer value={value} actions={actions} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ export class RequestDetailsResponse extends Component<DetailViewProps> {
return null;
}

return <RequestCodeViewer json={JSON.stringify(responseJSON, null, 2)} />;
return <RequestCodeViewer value={JSON.stringify(responseJSON, null, 2)} />;
}
}
12 changes: 12 additions & 0 deletions src/plugins/inspector/public/views/requests/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@
* Side Public License, v 1.
*/

import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { CoreStart } from '@kbn/core-lifecycle-browser';

export interface DetailViewData {
name: string;
label: string;
component: any;
}

export interface InspectorKibanaServices {
share: SharePluginStart;
application: CoreStart['application'];
http: CoreStart['http'];
uiSettings: CoreStart['uiSettings'];
settings: CoreStart['settings'];
theme: CoreStart['theme'];
}
3 changes: 2 additions & 1 deletion src/plugins/inspector/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"@kbn/core-ui-settings-browser",
"@kbn/std",
"@kbn/ui-theme",
"@kbn/code-editor"
"@kbn/code-editor",
"@kbn/core-lifecycle-browser"
],
"exclude": [
"target/**/*",
Expand Down

0 comments on commit 7a00d2e

Please sign in to comment.