Add inspector panel for APM routes (#109696)
When the observability:enableInspectEsQueries advanced setting is enabled, show an inspector that includes all queries through useFetcher. Remove the callout.
This commit is contained in:
parent
475eaf2c76
commit
3bae4cdc06
|
@ -6,6 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { RequestStatistic, RequestStatistics, RequestStatus } from './types';
|
||||
export { Request, RequestStatistic, RequestStatistics, RequestStatus } from './types';
|
||||
export { RequestAdapter } from './request_adapter';
|
||||
export { RequestResponder } from './request_responder';
|
||||
|
|
|
@ -33,14 +33,19 @@ export class RequestAdapter extends EventEmitter {
|
|||
* {@link RequestResponder#error}.
|
||||
*
|
||||
* @param {string} name The name of this request as it should be shown in the UI.
|
||||
* @param {object} args Additional arguments for the request.
|
||||
* @param {RequestParams} params Additional arguments for the request.
|
||||
* @param {number} [startTime] Set an optional start time for the request
|
||||
* @return {RequestResponder} An instance to add information to the request and finish it.
|
||||
*/
|
||||
public start(name: string, params: RequestParams = {}): RequestResponder {
|
||||
public start(
|
||||
name: string,
|
||||
params: RequestParams = {},
|
||||
startTime: number = Date.now()
|
||||
): RequestResponder {
|
||||
const req: Request = {
|
||||
...params,
|
||||
name,
|
||||
startTime: Date.now(),
|
||||
startTime,
|
||||
status: RequestStatus.PENDING,
|
||||
id: params.id ?? uuid(),
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
export {
|
||||
Adapters,
|
||||
Request,
|
||||
RequestAdapter,
|
||||
RequestStatistic,
|
||||
RequestStatistics,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"embeddable",
|
||||
"features",
|
||||
"infra",
|
||||
"inspector",
|
||||
"licensing",
|
||||
"observability",
|
||||
"ruleRegistry",
|
||||
|
|
|
@ -48,6 +48,7 @@ export const renderApp = ({
|
|||
core: coreStart,
|
||||
plugins: pluginsSetup,
|
||||
data: pluginsStart.data,
|
||||
inspector: pluginsStart.inspector,
|
||||
observability: pluginsStart.observability,
|
||||
observabilityRuleTypeRegistry,
|
||||
};
|
||||
|
|
|
@ -91,7 +91,7 @@ export function UXAppRoot({
|
|||
core,
|
||||
deps,
|
||||
config,
|
||||
corePlugins: { embeddable, maps, observability, data },
|
||||
corePlugins: { embeddable, inspector, maps, observability, data },
|
||||
observabilityRuleTypeRegistry,
|
||||
}: {
|
||||
appMountParameters: AppMountParameters;
|
||||
|
@ -108,6 +108,7 @@ export function UXAppRoot({
|
|||
appMountParameters,
|
||||
config,
|
||||
core,
|
||||
inspector,
|
||||
plugins,
|
||||
observability,
|
||||
observabilityRuleTypeRegistry,
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from '../../context/apm_plugin/apm_plugin_context';
|
||||
import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { BreadcrumbsContextProvider } from '../../context/breadcrumbs/context';
|
||||
import { InspectorContextProvider } from '../../context/inspector/inspector_context';
|
||||
import { LicenseProvider } from '../../context/license/license_context';
|
||||
import { TimeRangeIdContextProvider } from '../../context/time_range_id/time_range_id_context';
|
||||
import { UrlParamsProvider } from '../../context/url_params_context/url_params_context';
|
||||
|
@ -62,12 +63,14 @@ export function ApmAppRoot({
|
|||
<UrlParamsProvider>
|
||||
<LicenseProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MountApmHeaderActionMenu />
|
||||
<InspectorContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MountApmHeaderActionMenu />
|
||||
|
||||
<Route component={ScrollToTopOnPathChange} />
|
||||
<RouteRenderer />
|
||||
</ApmThemeProvider>
|
||||
<Route component={ScrollToTopOnPathChange} />
|
||||
<RouteRenderer />
|
||||
</ApmThemeProvider>
|
||||
</InspectorContextProvider>
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</LicenseProvider>
|
||||
</UrlParamsProvider>
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_
|
|||
import { AlertingPopoverAndFlyout } from './alerting_popover_flyout';
|
||||
import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link';
|
||||
import { useServiceName } from '../../../hooks/use_service_name';
|
||||
import { InspectorHeaderLink } from './inspector_header_link';
|
||||
|
||||
export function ApmHeaderActionMenu() {
|
||||
const { core, plugins } = useApmPluginContext();
|
||||
|
@ -65,6 +66,7 @@ export function ApmHeaderActionMenu() {
|
|||
defaultMessage: 'Add data',
|
||||
})}
|
||||
</EuiHeaderLink>
|
||||
<InspectorHeaderLink />
|
||||
</EuiHeaderLinks>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiHeaderLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { enableInspectEsQueries } from '../../../../../observability/public';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useInspectorContext } from '../../../context/inspector/use_inspector_context';
|
||||
|
||||
export function InspectorHeaderLink() {
|
||||
const { inspector } = useApmPluginContext();
|
||||
const { inspectorAdapters } = useInspectorContext();
|
||||
const {
|
||||
services: { uiSettings },
|
||||
} = useKibana();
|
||||
const isInspectorEnabled = uiSettings?.get<boolean>(enableInspectEsQueries);
|
||||
|
||||
const inspect = () => {
|
||||
inspector.open(inspectorAdapters);
|
||||
};
|
||||
|
||||
if (!isInspectorEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiHeaderLink color="primary" onClick={inspect}>
|
||||
{i18n.translate('xpack.apm.inspectButtonText', {
|
||||
defaultMessage: 'Inspect',
|
||||
})}
|
||||
</EuiHeaderLink>
|
||||
);
|
||||
}
|
|
@ -7,19 +7,12 @@
|
|||
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiFlexGroupProps,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { enableInspectEsQueries } from '../../../../observability/public';
|
||||
import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useKibanaUrl } from '../../hooks/useKibanaUrl';
|
||||
import { useBreakPoints } from '../../hooks/use_break_points';
|
||||
import { DatePicker } from './DatePicker';
|
||||
import { KueryBar } from './kuery_bar';
|
||||
|
@ -35,52 +28,6 @@ interface Props {
|
|||
kueryBarBoolFilter?: QueryDslQueryContainer[];
|
||||
}
|
||||
|
||||
function DebugQueryCallout() {
|
||||
const { uiSettings } = useApmPluginContext().core;
|
||||
const advancedSettingsUrl = useKibanaUrl('/app/management/kibana/settings', {
|
||||
query: {
|
||||
query: 'category:(observability)',
|
||||
},
|
||||
});
|
||||
|
||||
if (!uiSettings.get(enableInspectEsQueries)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.apm.searchBar.inspectEsQueriesEnabled.callout.title',
|
||||
{
|
||||
defaultMessage:
|
||||
'Inspectable ES queries (`apm:enableInspectEsQueries`)',
|
||||
}
|
||||
)}
|
||||
iconType="beaker"
|
||||
color="warning"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.searchBar.inspectEsQueriesEnabled.callout.description"
|
||||
defaultMessage="You can now inspect every Elasticsearch query by opening your browser's Dev Tool and looking at the API responses. The setting can be disabled in Kibana's {advancedSettingsLink}"
|
||||
values={{
|
||||
advancedSettingsLink: (
|
||||
<EuiLink href={advancedSettingsUrl}>
|
||||
{i18n.translate(
|
||||
'xpack.apm.searchBar.inspectEsQueriesEnabled.callout.description.advancedSettings',
|
||||
{ defaultMessage: 'Advanced Settings' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export function SearchBar({
|
||||
hidden = false,
|
||||
showKueryBar = true,
|
||||
|
@ -100,7 +47,6 @@ export function SearchBar({
|
|||
|
||||
return (
|
||||
<>
|
||||
<DebugQueryCallout />
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
|
|
|
@ -12,11 +12,13 @@ import { ConfigSchema } from '../..';
|
|||
import { ApmPluginSetupDeps } from '../../plugin';
|
||||
import { MapsStartApi } from '../../../../maps/public';
|
||||
import { ObservabilityPublicStart } from '../../../../observability/public';
|
||||
import { Start as InspectorPluginStart } from '../../../../../../src/plugins/inspector/public';
|
||||
|
||||
export interface ApmPluginContextValue {
|
||||
appMountParameters: AppMountParameters;
|
||||
config: ConfigSchema;
|
||||
core: CoreStart;
|
||||
inspector: InspectorPluginStart;
|
||||
plugins: ApmPluginSetupDeps & { maps?: MapsStartApi };
|
||||
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
|
||||
observability: ObservabilityPublicStart;
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { createContext, ReactNode, useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { RequestAdapter } from '../../../../../../src/plugins/inspector/common';
|
||||
import { InspectResponse } from '../../../typings/common';
|
||||
import { FetcherResult } from '../../hooks/use_fetcher';
|
||||
|
||||
export interface InspectorContextValue {
|
||||
addInspectorRequest: <Data>(result: FetcherResult<Data>) => void;
|
||||
inspectorAdapters: { requests: RequestAdapter };
|
||||
}
|
||||
|
||||
const value: InspectorContextValue = {
|
||||
addInspectorRequest: () => {},
|
||||
inspectorAdapters: { requests: new RequestAdapter() },
|
||||
};
|
||||
|
||||
export const InspectorContext = createContext<InspectorContextValue>(value);
|
||||
|
||||
export function InspectorContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { inspectorAdapters } = value;
|
||||
|
||||
function addInspectorRequest(
|
||||
result: FetcherResult<{
|
||||
mainStatisticsData?: { _inspect?: InspectResponse };
|
||||
_inspect?: InspectResponse;
|
||||
}>
|
||||
) {
|
||||
const operations =
|
||||
result.data?._inspect ?? result.data?.mainStatisticsData?._inspect ?? [];
|
||||
|
||||
operations.forEach((operation) => {
|
||||
if (operation.response) {
|
||||
const { id, name } = operation;
|
||||
const requestParams = { id, name };
|
||||
|
||||
const requestResponder = inspectorAdapters.requests.start(
|
||||
id,
|
||||
requestParams,
|
||||
operation.startTime
|
||||
);
|
||||
|
||||
requestResponder.json(operation.json as object);
|
||||
|
||||
if (operation.stats) {
|
||||
requestResponder.stats(operation.stats);
|
||||
}
|
||||
|
||||
requestResponder.finish(operation.status, operation.response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const unregisterCallback = history.listen((newLocation) => {
|
||||
if (history.location.pathname !== newLocation.pathname) {
|
||||
inspectorAdapters.requests.reset();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unregisterCallback();
|
||||
};
|
||||
}, [history, inspectorAdapters]);
|
||||
|
||||
return (
|
||||
<InspectorContext.Provider value={{ ...value, addInspectorRequest }}>
|
||||
{children}
|
||||
</InspectorContext.Provider>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useContext } from 'react';
|
||||
import { InspectorContext } from './inspector_context';
|
||||
|
||||
export function useInspectorContext() {
|
||||
return useContext(InspectorContext);
|
||||
}
|
|
@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { IHttpFetchError } from 'src/core/public';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { useInspectorContext } from '../context/inspector/use_inspector_context';
|
||||
import { useTimeRangeId } from '../context/time_range_id/use_time_range_id';
|
||||
import {
|
||||
AutoAbortedAPMClient,
|
||||
|
@ -77,6 +78,7 @@ export function useFetcher<TReturn>(
|
|||
});
|
||||
const [counter, setCounter] = useState(0);
|
||||
const { timeRangeId } = useTimeRangeId();
|
||||
const { addInspectorRequest } = useInspectorContext();
|
||||
|
||||
useEffect(() => {
|
||||
let controller: AbortController = new AbortController();
|
||||
|
@ -165,6 +167,14 @@ export function useFetcher<TReturn>(
|
|||
/* eslint-enable react-hooks/exhaustive-deps */
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (result.error) {
|
||||
addInspectorRequest({ ...result, data: result.error.body?.attributes });
|
||||
} else {
|
||||
addInspectorRequest(result);
|
||||
}
|
||||
}, [addInspectorRequest, result]);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
...result,
|
||||
|
|
|
@ -23,13 +23,14 @@ import type {
|
|||
DataPublicPluginStart,
|
||||
} from '../../../../src/plugins/data/public';
|
||||
import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
|
||||
import type { FleetStart } from '../../fleet/public';
|
||||
import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
||||
import { Start as InspectorPluginStart } from '../../../../src/plugins/inspector/public';
|
||||
import type {
|
||||
PluginSetupContract as AlertingPluginPublicSetup,
|
||||
PluginStartContract as AlertingPluginPublicStart,
|
||||
} from '../../alerting/public';
|
||||
import type { FeaturesPluginSetup } from '../../features/public';
|
||||
import type { FleetStart } from '../../fleet/public';
|
||||
import type { LicensingPluginSetup } from '../../licensing/public';
|
||||
import type { MapsStartApi } from '../../maps/public';
|
||||
import type { MlPluginSetup, MlPluginStart } from '../../ml/public';
|
||||
|
@ -45,15 +46,14 @@ import type {
|
|||
TriggersAndActionsUIPublicPluginStart,
|
||||
} from '../../triggers_actions_ui/public';
|
||||
import { registerApmAlerts } from './components/alerting/register_apm_alerts';
|
||||
import { featureCatalogueEntry } from './featureCatalogueEntry';
|
||||
import {
|
||||
getApmEnrollmentFlyoutData,
|
||||
LazyApmCustomAssetsExtension,
|
||||
} from './components/fleet_integration';
|
||||
import { getLazyApmAgentsTabExtension } from './components/fleet_integration/lazy_apm_agents_tab_extension';
|
||||
import { getLazyAPMPolicyCreateExtension } from './components/fleet_integration/lazy_apm_policy_create_extension';
|
||||
import { getLazyAPMPolicyEditExtension } from './components/fleet_integration/lazy_apm_policy_edit_extension';
|
||||
import { getLazyApmAgentsTabExtension } from './components/fleet_integration/lazy_apm_agents_tab_extension';
|
||||
|
||||
import { featureCatalogueEntry } from './featureCatalogueEntry';
|
||||
export type ApmPluginSetup = ReturnType<ApmPlugin['setup']>;
|
||||
|
||||
export type ApmPluginStart = void;
|
||||
|
@ -74,6 +74,7 @@ export interface ApmPluginStartDeps {
|
|||
data: DataPublicPluginStart;
|
||||
embeddable: EmbeddableStart;
|
||||
home: void;
|
||||
inspector: InspectorPluginStart;
|
||||
licensing: void;
|
||||
maps?: MapsStartApi;
|
||||
ml?: MlPluginStart;
|
||||
|
|
|
@ -25,10 +25,10 @@ import { FetchOptions } from '../../../common/fetch_options';
|
|||
import { callApi } from './callApi';
|
||||
import type {
|
||||
APMServerRouteRepository,
|
||||
InspectResponse,
|
||||
APMRouteHandlerResources,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../server';
|
||||
import { InspectResponse } from '../../../typings/common';
|
||||
|
||||
export type APMClientOptions = Omit<
|
||||
FetchOptions,
|
||||
|
|
|
@ -131,6 +131,6 @@ export { APM_SERVER_FEATURE_ID } from '../common/alert_types';
|
|||
export { APMPlugin } from './plugin';
|
||||
export { APMPluginSetup } from './types';
|
||||
export { APMServerRouteRepository } from './routes/get_global_apm_server_route_repository';
|
||||
export { InspectResponse, APMRouteHandlerResources } from './routes/typings';
|
||||
export { APMRouteHandlerResources } from './routes/typings';
|
||||
|
||||
export type { ProcessorEvent } from '../common/processor_event';
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import chalk from 'chalk';
|
||||
import { KibanaRequest } from '../../../../../../../src/core/server';
|
||||
import { RequestStatus } from '../../../../../../../src/plugins/inspector';
|
||||
import { WrappedElasticsearchClientError } from '../../../../../observability/server';
|
||||
import { inspectableEsQueriesMap } from '../../../routes/register_routes';
|
||||
import { getInspectResponse } from './get_inspect_response';
|
||||
|
||||
function formatObj(obj: Record<string, any>) {
|
||||
return JSON.stringify(obj, null, 2);
|
||||
|
@ -39,20 +41,24 @@ export async function callAsyncWithDebug<T>({
|
|||
return cb();
|
||||
}
|
||||
|
||||
const startTime = process.hrtime();
|
||||
const hrStartTime = process.hrtime();
|
||||
const startTime = Date.now();
|
||||
|
||||
let res: any;
|
||||
let esError = null;
|
||||
let esError: WrappedElasticsearchClientError | null = null;
|
||||
let esRequestStatus: RequestStatus = RequestStatus.PENDING;
|
||||
try {
|
||||
res = await cb();
|
||||
esRequestStatus = RequestStatus.OK;
|
||||
} catch (e) {
|
||||
// catch error and throw after outputting debug info
|
||||
esError = e;
|
||||
esRequestStatus = RequestStatus.ERROR;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
const highlightColor = esError ? 'bgRed' : 'inverse';
|
||||
const diff = process.hrtime(startTime);
|
||||
const diff = process.hrtime(hrStartTime);
|
||||
const duration = Math.round(diff[0] * 1000 + diff[1] / 1e6); // duration in ms
|
||||
|
||||
const { title, body } = getDebugMessage();
|
||||
|
@ -66,14 +72,17 @@ export async function callAsyncWithDebug<T>({
|
|||
|
||||
const inspectableEsQueries = inspectableEsQueriesMap.get(request);
|
||||
if (!isCalledWithInternalUser && inspectableEsQueries) {
|
||||
inspectableEsQueries.push({
|
||||
operationName,
|
||||
response: res,
|
||||
duration,
|
||||
requestType,
|
||||
requestParams: omit(requestParams, 'headers'),
|
||||
esError: esError?.response ?? esError?.message,
|
||||
});
|
||||
inspectableEsQueries.push(
|
||||
getInspectResponse({
|
||||
esError,
|
||||
esRequestParams: requestParams,
|
||||
esRequestStatus,
|
||||
esResponse: res,
|
||||
kibanaRequest: request,
|
||||
operationName,
|
||||
startTime,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { KibanaRequest } from '../../../../../../../src/core/server';
|
||||
import type {
|
||||
RequestStatistics,
|
||||
RequestStatus,
|
||||
} from '../../../../../../../src/plugins/inspector';
|
||||
import { WrappedElasticsearchClientError } from '../../../../../observability/server';
|
||||
import type { InspectResponse } from '../../../../typings/common';
|
||||
|
||||
/**
|
||||
* Get statistics to show on inspector tab.
|
||||
*
|
||||
* If you're using searchSource (which we're not), this gets populated from
|
||||
* https://github.com/elastic/kibana/blob/c7d742cb8b8935f3812707a747a139806e4be203/src/plugins/data/common/search/search_source/inspect/inspector_stats.ts
|
||||
*
|
||||
* We do most of the same here, but not using searchSource.
|
||||
*/
|
||||
function getStats({
|
||||
esRequestParams,
|
||||
esResponse,
|
||||
kibanaRequest,
|
||||
}: {
|
||||
esRequestParams: Record<string, any>;
|
||||
esResponse: any;
|
||||
kibanaRequest: KibanaRequest;
|
||||
}) {
|
||||
const stats: RequestStatistics = {
|
||||
kibanaApiQueryParameters: {
|
||||
label: i18n.translate(
|
||||
'xpack.apm.inspector.stats.kibanaApiQueryParametersLabel',
|
||||
{
|
||||
defaultMessage: 'Kibana API query parameters',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.apm.inspector.stats.kibanaApiQueryParametersDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'The query parameters used in the Kibana API request that initiated the Elasticsearch request.',
|
||||
}
|
||||
),
|
||||
value: JSON.stringify(kibanaRequest.query, null, 2),
|
||||
},
|
||||
kibanaApiRoute: {
|
||||
label: i18n.translate('xpack.apm.inspector.stats.kibanaApiRouteLabel', {
|
||||
defaultMessage: 'Kibana API route',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.apm.inspector.stats.kibanaApiRouteDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'The route of the Kibana API request that initiated the Elasticsearch request.',
|
||||
}
|
||||
),
|
||||
value: `${kibanaRequest.route.method.toUpperCase()} ${
|
||||
kibanaRequest.route.path
|
||||
}`,
|
||||
},
|
||||
indexPattern: {
|
||||
label: i18n.translate('xpack.apm.inspector.stats.indexPatternLabel', {
|
||||
defaultMessage: 'Index pattern',
|
||||
}),
|
||||
value: esRequestParams.index,
|
||||
description: i18n.translate(
|
||||
'xpack.apm.inspector.stats.indexPatternDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'The index pattern that connected to the Elasticsearch indices.',
|
||||
}
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
if (esResponse?.hits) {
|
||||
stats.hits = {
|
||||
label: i18n.translate('xpack.apm.inspector.stats.hitsLabel', {
|
||||
defaultMessage: 'Hits',
|
||||
}),
|
||||
value: `${esResponse.hits.hits.length}`,
|
||||
description: i18n.translate('xpack.apm.inspector.stats.hitsDescription', {
|
||||
defaultMessage: 'The number of documents returned by the query.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (esResponse?.took) {
|
||||
stats.queryTime = {
|
||||
label: i18n.translate('xpack.apm.inspector.stats.queryTimeLabel', {
|
||||
defaultMessage: 'Query time',
|
||||
}),
|
||||
value: i18n.translate('xpack.apm.inspector.stats.queryTimeValue', {
|
||||
defaultMessage: '{queryTime}ms',
|
||||
values: { queryTime: esResponse.took },
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.apm.inspector.stats.queryTimeDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'The time it took to process the query. ' +
|
||||
'Does not include the time to send the request or parse it in the browser.',
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (esResponse?.hits?.total !== undefined) {
|
||||
const total = esResponse.hits.total as {
|
||||
relation: string;
|
||||
value: number;
|
||||
};
|
||||
const hitsTotalValue =
|
||||
total.relation === 'eq' ? `${total.value}` : `> ${total.value}`;
|
||||
|
||||
stats.hitsTotal = {
|
||||
label: i18n.translate('xpack.apm.inspector.stats.hitsTotalLabel', {
|
||||
defaultMessage: 'Hits (total)',
|
||||
}),
|
||||
value: hitsTotalValue,
|
||||
description: i18n.translate(
|
||||
'xpack.apm.inspector.stats.hitsTotalDescription',
|
||||
{
|
||||
defaultMessage: 'The number of documents that match the query.',
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a formatted response to be sent in the _inspect key for use in the
|
||||
* inspector.
|
||||
*/
|
||||
export function getInspectResponse({
|
||||
esError,
|
||||
esRequestParams,
|
||||
esRequestStatus,
|
||||
esResponse,
|
||||
kibanaRequest,
|
||||
operationName,
|
||||
startTime,
|
||||
}: {
|
||||
esError: WrappedElasticsearchClientError | null;
|
||||
esRequestParams: Record<string, any>;
|
||||
esRequestStatus: RequestStatus;
|
||||
esResponse: any;
|
||||
kibanaRequest: KibanaRequest;
|
||||
operationName: string;
|
||||
startTime: number;
|
||||
}): InspectResponse[0] {
|
||||
const id = `${operationName} (${kibanaRequest.route.path})`;
|
||||
|
||||
return {
|
||||
id,
|
||||
json: esRequestParams.body,
|
||||
name: id,
|
||||
response: {
|
||||
json: esError ? esError.originalError : esResponse,
|
||||
},
|
||||
startTime,
|
||||
stats: getStats({ esRequestParams, esResponse, kibanaRequest }),
|
||||
status: esRequestStatus,
|
||||
};
|
||||
}
|
|
@ -19,12 +19,9 @@ import {
|
|||
} from '@kbn/server-route-repository';
|
||||
import { mergeRt, jsonRt } from '@kbn/io-ts-utils';
|
||||
import { pickKeys } from '../../../common/utils/pick_keys';
|
||||
import {
|
||||
APMRouteHandlerResources,
|
||||
InspectResponse,
|
||||
TelemetryUsageCounter,
|
||||
} from '../typings';
|
||||
import { APMRouteHandlerResources, TelemetryUsageCounter } from '../typings';
|
||||
import type { ApmPluginRequestHandlerContext } from '../typings';
|
||||
import { InspectResponse } from '../../../typings/common';
|
||||
|
||||
const inspectRt = t.exact(
|
||||
t.partial({
|
||||
|
|
|
@ -26,15 +26,6 @@ export interface ApmPluginRequestHandlerContext extends RequestHandlerContext {
|
|||
rac: RacApiRequestHandlerContext;
|
||||
}
|
||||
|
||||
export type InspectResponse = Array<{
|
||||
response: any;
|
||||
duration: number;
|
||||
requestType: string;
|
||||
requestParams: Record<string, unknown>;
|
||||
esError: Error;
|
||||
operationName: string;
|
||||
}>;
|
||||
|
||||
export interface APMRouteCreateOptions {
|
||||
options: {
|
||||
tags: Array<
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
{ "path": "../../../src/plugins/embeddable/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/home/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/index_pattern_management/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/inspector/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/kibana_react/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/kibana_utils/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/usage_collection/tsconfig.json" },
|
||||
|
|
3
x-pack/plugins/apm/typings/common.d.ts
vendored
3
x-pack/plugins/apm/typings/common.d.ts
vendored
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { UnwrapPromise } from '@kbn/utility-types';
|
||||
import type { Request } from '../../../../src/plugins/inspector/common';
|
||||
import '../../../typings/rison_node';
|
||||
import '../../infra/types/eui';
|
||||
// EUIBasicTable
|
||||
|
@ -27,3 +28,5 @@ type AllowUnknownObjectProperties<T> = T extends object
|
|||
export type PromiseValueType<T extends Promise<any>> = UnwrapPromise<T>;
|
||||
|
||||
export type Maybe<T> = T | null | undefined;
|
||||
|
||||
export type InspectResponse = Request[];
|
||||
|
|
|
@ -5580,9 +5580,6 @@
|
|||
"xpack.apm.rum.visitorBreakdown.operatingSystem": "オペレーティングシステム",
|
||||
"xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration": "平均ページ読み込み時間",
|
||||
"xpack.apm.rum.visitorBreakdownMap.pageLoadDurationByRegion": "地域別ページ読み込み時間 (平均) ",
|
||||
"xpack.apm.searchBar.inspectEsQueriesEnabled.callout.description": "ブラウザーの開発者ツールを開き、API応答を確認すると、すべてのElasticsearchクエリを検査できます。この設定はKibanaの{advancedSettingsLink}で無効にでkます",
|
||||
"xpack.apm.searchBar.inspectEsQueriesEnabled.callout.description.advancedSettings": "高度な設定",
|
||||
"xpack.apm.searchBar.inspectEsQueriesEnabled.callout.title": "調査可能なESクエリ (`apm:enableInspectEsQueries`) ",
|
||||
"xpack.apm.searchInput.filter": "フィルター...",
|
||||
"xpack.apm.selectPlaceholder": "オプションを選択:",
|
||||
"xpack.apm.serviceDetails.errorsTabLabel": "エラー",
|
||||
|
|
|
@ -5608,9 +5608,6 @@
|
|||
"xpack.apm.rum.visitorBreakdown.operatingSystem": "操作系统",
|
||||
"xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration": "页面加载平均持续时间",
|
||||
"xpack.apm.rum.visitorBreakdownMap.pageLoadDurationByRegion": "按区域列出的页面加载持续时间(平均值)",
|
||||
"xpack.apm.searchBar.inspectEsQueriesEnabled.callout.description": "现在可以通过打开浏览器的开发工具和查看 API 响应,来检查各个 Elasticsearch 查询。该设置可以在 Kibana 的“{advancedSettingsLink}”中禁用",
|
||||
"xpack.apm.searchBar.inspectEsQueriesEnabled.callout.description.advancedSettings": "高级设置",
|
||||
"xpack.apm.searchBar.inspectEsQueriesEnabled.callout.title": "可检查的 ES 查询 (`apm:enableInspectEsQueries`)",
|
||||
"xpack.apm.searchInput.filter": "筛选...",
|
||||
"xpack.apm.selectPlaceholder": "选择选项:",
|
||||
"xpack.apm.serviceDetails.errorsTabLabel": "错误",
|
||||
|
|
|
@ -53,11 +53,13 @@ export default function customLinksTests({ getService }: FtrProviderContext) {
|
|||
|
||||
// @ts-expect-error
|
||||
expect(Object.keys(body._inspect[0])).to.eql([
|
||||
'operationName',
|
||||
'id',
|
||||
'json',
|
||||
'name',
|
||||
'response',
|
||||
'duration',
|
||||
'requestType',
|
||||
'requestParams',
|
||||
'startTime',
|
||||
'stats',
|
||||
'status',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue