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:
Nathan L Smith 2021-08-31 11:10:54 -05:00 committed by GitHub
parent 475eaf2c76
commit 3bae4cdc06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 383 additions and 108 deletions

View file

@ -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';

View file

@ -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(),
};

View file

@ -8,6 +8,7 @@
export {
Adapters,
Request,
RequestAdapter,
RequestStatistic,
RequestStatistics,

View file

@ -12,6 +12,7 @@
"embeddable",
"features",
"infra",
"inspector",
"licensing",
"observability",
"ruleRegistry",

View file

@ -48,6 +48,7 @@ export const renderApp = ({
core: coreStart,
plugins: pluginsSetup,
data: pluginsStart.data,
inspector: pluginsStart.inspector,
observability: pluginsStart.observability,
observabilityRuleTypeRegistry,
};

View file

@ -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,

View file

@ -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>

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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}

View file

@ -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;

View file

@ -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>
);
}

View file

@ -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);
}

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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';

View file

@ -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,
})
);
}
}

View file

@ -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,
};
}

View file

@ -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({

View file

@ -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<

View file

@ -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" },

View file

@ -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[];

View file

@ -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": "エラー",

View file

@ -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": "错误",

View file

@ -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',
]);
});
});