[APM] Add ML expected model bounds as an option to Comparison controls (#132456)

* [ML] Add bounds options

* [ML] Renable anomalyChartTimeseries boundaries

* [ML] Make it into string

* [ML] Make it into string

* Match colors

* [ML] Add comparisonEnabledRt

* [ML] Fix types, tests

* [ML] Revert json bucket span change

* [ML] Add bounds options

* [ML] Renable anomalyChartTimeseries boundaries

* [ML] Make it into string

* [ML] Make it into string

* Match colors

* [ML] Add comparisonEnabledRt

* [ML] Fix types, tests

* [ML] Revert json bucket span change

* Refactor to use offset with TimeRangeComparisonEnum.ExpectedBounds

* Change comparisonColor to be part of anomalySeries

* Fix i18n

* Add Comparison text to replace 'Previous period'

* Fix expected bounds legend default to first

* Hide values that are N/A in the tooltips

* Fix i18n, color, and null values in tooltips

* Refactor to use preferredEnvironment

* Don't disable expected bounds by default

* Match color of expected bounds with time comparison color

* Fix tests

* Use bucket_span as minBucketSize for anomaly results

* Fix previousPeriodColor undefined for latency chart

* Remove 'Comparison:' in legend

* Change anomalyTimeseriesColor to use currentPeriod to match stuff

* Fix type error

* Fix lower model bounds

* Add comments

* Remove fit

* Remove comparisonEnabledRt

* Change text

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Quynh Nguyen 2022-05-24 18:01:47 -05:00 committed by GitHub
parent 561c774e3b
commit c1d0ec5a0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 524 additions and 226 deletions

View file

@ -14,4 +14,5 @@ export interface ApmMlJob {
jobState?: JOB_STATE;
datafeedId?: string;
datafeedState?: DATAFEED_STATE;
bucketSpan?: string;
}

View file

@ -62,17 +62,14 @@ describe('getPreferredServiceAnomalyTimeseries', () => {
];
describe('with one environment', () => {
const environments = [PROD];
describe('and all being selected', () => {
const environment = ENVIRONMENT_ALL.value;
const preferredEnvironment = PROD;
it('returns the series for prod', () => {
expect(
getPreferredServiceAnomalyTimeseries({
allAnomalyTimeseries,
detectorType: ApmMlDetectorType.txLatency,
environment,
environments,
preferredEnvironment,
fallbackToTransactions: false,
})?.environment
).toBe(PROD);
@ -81,18 +78,15 @@ describe('getPreferredServiceAnomalyTimeseries', () => {
});
describe('with multiple environments', () => {
const environments = [PROD, DEV];
describe('and all being selected', () => {
const environment = ENVIRONMENT_ALL.value;
const preferredEnvironment = ENVIRONMENT_ALL.value;
it('returns no series', () => {
expect(
getPreferredServiceAnomalyTimeseries({
allAnomalyTimeseries,
detectorType: ApmMlDetectorType.txLatency,
environment,
environments,
preferredEnvironment,
fallbackToTransactions: false,
})
).toBeUndefined();
@ -101,8 +95,7 @@ describe('getPreferredServiceAnomalyTimeseries', () => {
getPreferredServiceAnomalyTimeseries({
allAnomalyTimeseries,
detectorType: ApmMlDetectorType.txLatency,
environment,
environments,
preferredEnvironment,
fallbackToTransactions: true,
})
).toBeUndefined();
@ -110,14 +103,13 @@ describe('getPreferredServiceAnomalyTimeseries', () => {
});
describe('and production being selected', () => {
const environment = PROD;
const preferredEnvironment = PROD;
it('returns the series for production', () => {
const series = getPreferredServiceAnomalyTimeseries({
allAnomalyTimeseries,
detectorType: ApmMlDetectorType.txFailureRate,
environment,
environments,
preferredEnvironment,
fallbackToTransactions: false,
});
@ -143,15 +135,13 @@ describe('getPreferredServiceAnomalyTimeseries', () => {
}),
];
const environments = [PROD];
const environment = ENVIRONMENT_ALL.value;
const preferredEnvironment = PROD;
it('selects the most recent version when transaction metrics are being used', () => {
const series = getPreferredServiceAnomalyTimeseries({
allAnomalyTimeseries,
detectorType: ApmMlDetectorType.txLatency,
environment,
environments,
preferredEnvironment,
fallbackToTransactions: false,
});
@ -162,8 +152,7 @@ describe('getPreferredServiceAnomalyTimeseries', () => {
const series = getPreferredServiceAnomalyTimeseries({
allAnomalyTimeseries,
detectorType: ApmMlDetectorType.txLatency,
environment,
environments,
preferredEnvironment,
fallbackToTransactions: true,
});

View file

@ -5,20 +5,17 @@
* 2.0.
*/
import { ENVIRONMENT_ALL } from '../environment_filter_values';
import { Environment } from '../environment_rt';
import { ApmMlDetectorType } from './apm_ml_detectors';
import { ServiceAnomalyTimeseries } from './service_anomaly_timeseries';
export function getPreferredServiceAnomalyTimeseries({
environment,
environments,
preferredEnvironment,
detectorType,
allAnomalyTimeseries,
fallbackToTransactions,
}: {
environment: Environment;
environments: Environment[];
preferredEnvironment: Environment;
detectorType: ApmMlDetectorType;
allAnomalyTimeseries: ServiceAnomalyTimeseries[];
fallbackToTransactions: boolean;
@ -27,11 +24,6 @@ export function getPreferredServiceAnomalyTimeseries({
(serie) => serie.type === detectorType
);
const preferredEnvironment =
environment === ENVIRONMENT_ALL.value && environments.length === 1
? environments[0]
: environment;
return seriesForType.find(
(serie) =>
serie.environment === preferredEnvironment &&

View file

@ -7,4 +7,6 @@
import * as t from 'io-ts';
export const offsetRt = t.partial({ offset: t.string });
export const offsetRt = t.partial({
offset: t.string,
});

View file

@ -203,7 +203,7 @@ describe.skip('Service overview: Time Comparison', () => {
cy.get('[data-test-subj="throughput"]')
.get('#echHighlighterClipPath__throughput')
.realHover({ position: 'center' });
cy.contains('Previous period');
cy.contains('Week before');
cy.contains('0 tpm');
cy.contains('Throughput');

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { getNodeName, NodeType } from '../../../../common/connections';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useFetcher } from '../../../hooks/use_fetcher';
@ -43,7 +44,10 @@ export function BackendDetailDependenciesTable() {
end,
environment,
numBuckets: 20,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
kuery,
},
},

View file

@ -6,6 +6,8 @@
*/
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { usePreviousPeriodLabel } from '../../../hooks/use_previous_period_text';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { asPercent } from '../../../../common/utils/formatters';
import { useFetcher } from '../../../hooks/use_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
@ -55,7 +57,10 @@ export function BackendFailedTransactionRateChart({
backendName,
start,
end,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
kuery,
environment,
},
@ -68,6 +73,8 @@ export function BackendFailedTransactionRateChart({
const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor(
ChartType.FAILED_TRANSACTION_RATE
);
const previousPeriodLabel = usePreviousPeriodLabel();
const timeseries = useMemo(() => {
const specs: Array<TimeSeries<Coordinate>> = [];
@ -87,15 +94,12 @@ export function BackendFailedTransactionRateChart({
data: data.comparisonTimeseries,
type: 'area',
color: previousPeriodColor,
title: i18n.translate(
'xpack.apm.backendErrorRateChart.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
title: previousPeriodLabel,
});
}
return specs;
}, [data, currentPeriodColor, previousPeriodColor]);
}, [data, currentPeriodColor, previousPeriodColor, previousPeriodLabel]);
return (
<TimeseriesChart

View file

@ -6,6 +6,8 @@
*/
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { usePreviousPeriodLabel } from '../../../hooks/use_previous_period_text';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { getDurationFormatter } from '../../../../common/utils/formatters';
import { useFetcher } from '../../../hooks/use_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
@ -51,7 +53,10 @@ export function BackendLatencyChart({ height }: { height: number }) {
backendName,
start,
end,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
kuery,
environment,
},
@ -65,6 +70,8 @@ export function BackendLatencyChart({ height }: { height: number }) {
ChartType.LATENCY_AVG
);
const previousPeriodLabel = usePreviousPeriodLabel();
const timeseries = useMemo(() => {
const specs: Array<TimeSeries<Coordinate>> = [];
@ -84,15 +91,12 @@ export function BackendLatencyChart({ height }: { height: number }) {
data: data.comparisonTimeseries,
type: 'area',
color: previousPeriodColor,
title: i18n.translate(
'xpack.apm.backendLatencyChart.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
title: previousPeriodLabel,
});
}
return specs;
}, [data, currentPeriodColor, previousPeriodColor]);
}, [data, currentPeriodColor, previousPeriodColor, previousPeriodLabel]);
const maxY = getMaxY(timeseries);
const latencyFormatter = getDurationFormatter(maxY);

View file

@ -6,6 +6,8 @@
*/
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { usePreviousPeriodLabel } from '../../../hooks/use_previous_period_text';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { asTransactionRate } from '../../../../common/utils/formatters';
import { useFetcher } from '../../../hooks/use_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
@ -47,7 +49,10 @@ export function BackendThroughputChart({ height }: { height: number }) {
backendName,
start,
end,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
kuery,
environment,
},
@ -61,6 +66,8 @@ export function BackendThroughputChart({ height }: { height: number }) {
ChartType.THROUGHPUT
);
const previousPeriodLabel = usePreviousPeriodLabel();
const timeseries = useMemo(() => {
const specs: Array<TimeSeries<Coordinate>> = [];
@ -80,15 +87,12 @@ export function BackendThroughputChart({ height }: { height: number }) {
data: data.comparisonTimeseries,
type: 'area',
color: previousPeriodColor,
title: i18n.translate(
'xpack.apm.backendThroughputChart.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
title: previousPeriodLabel,
});
}
return specs;
}, [data, currentPeriodColor, previousPeriodColor]);
}, [data, currentPeriodColor, previousPeriodColor, previousPeriodLabel]);
return (
<TimeseriesChart

View file

@ -9,6 +9,7 @@ import { METRIC_TYPE } from '@kbn/analytics';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useUiTracker } from '@kbn/observability-plugin/public';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { getNodeName, NodeType } from '../../../../../common/connections';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { useFetcher } from '../../../../hooks/use_fetcher';
@ -45,7 +46,10 @@ export function BackendInventoryDependenciesTable() {
end,
environment,
numBuckets: 20,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
kuery,
},
},

View file

@ -17,6 +17,7 @@ import {
import { EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { usePreviousPeriodLabel } from '../../../../hooks/use_previous_period_text';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
@ -41,6 +42,7 @@ export function ErrorDistribution({ distribution, title, fetchStatus }: Props) {
const { urlParams } = useLegacyUrlParams();
const { comparisonEnabled } = urlParams;
const previousPeriodLabel = usePreviousPeriodLabel();
const timeseries = [
{
data: distribution.currentPeriod,
@ -54,10 +56,7 @@ export function ErrorDistribution({ distribution, title, fetchStatus }: Props) {
{
data: distribution.previousPeriod,
color: theme.eui.euiColorMediumShade,
title: i18n.translate(
'xpack.apm.errorGroup.chart.ocurrences.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
title: previousPeriodLabel,
},
]
: []),

View file

@ -14,6 +14,7 @@ import {
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { asInteger } from '../../../../../common/utils/formatters';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
@ -72,7 +73,7 @@ function ErrorGroupList({
comparisonEnabled,
}: Props) {
const { query } = useApmParams('/services/{serviceName}/errors');
const { offset } = query;
const columns = useMemo(() => {
return [
{
@ -224,7 +225,9 @@ function ErrorGroupList({
}
)}
comparisonSeries={
comparisonEnabled ? previousPeriodTimeseries : undefined
comparisonEnabled && isTimeComparison(offset)
? previousPeriodTimeseries
: undefined
}
comparisonSeriesColor={previousPeriodColor}
/>
@ -238,6 +241,7 @@ function ErrorGroupList({
detailedStatistics,
comparisonEnabled,
detailedStatisticsLoading,
offset,
]);
return (

View file

@ -15,6 +15,7 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import uuid from 'uuid';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
import { useApmParams } from '../../../hooks/use_apm_params';
@ -126,7 +127,10 @@ export function ErrorGroupOverview() {
groupIds: JSON.stringify(
errorGroupMainStatistics.map(({ groupId }) => groupId).sort()
),
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}

View file

@ -11,6 +11,7 @@ import React from 'react';
import uuid from 'uuid';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { apmServiceInventoryOptimizedSorting } from '@kbn/observability-plugin/common';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
import { useLocalStorage } from '../../../hooks/use_local_storage';
import { useApmParams } from '../../../hooks/use_apm_params';
@ -110,7 +111,10 @@ function useServicesFetcher() {
// Service name is sorted to guarantee the same order every time this API is called so the result can be cached.
.sort()
),
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
});

View file

@ -12,6 +12,7 @@ import { METRIC_TYPE } from '@kbn/analytics';
import React from 'react';
import { useUiTracker } from '@kbn/observability-plugin/public';
import { NodeDataDefinition } from 'cytoscape';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { ContentsProps } from '.';
import { useAnyOfApmParams } from '../../../../hooks/use_apm_params';
import { useApmRouter } from '../../../../hooks/use_apm_router';
@ -56,7 +57,10 @@ export function BackendContents({
environment,
start,
end,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
});

View file

@ -16,6 +16,7 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { NodeDataDefinition } from 'cytoscape';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { useApmParams } from '../../../../hooks/use_apm_params';
import type { ContentsProps } from '.';
import { useApmRouter } from '../../../../hooks/use_apm_router';
@ -71,7 +72,10 @@ export function ServiceContents({
environment,
start,
end,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}

View file

@ -10,6 +10,7 @@ import { METRIC_TYPE } from '@kbn/analytics';
import { i18n } from '@kbn/i18n';
import React, { ReactNode } from 'react';
import { useUiTracker } from '@kbn/observability-plugin/public';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { getNodeName, NodeType } from '../../../../../common/connections';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useApmParams } from '../../../../hooks/use_apm_params';
@ -67,7 +68,10 @@ export function ServiceOverviewDependenciesTable({
end,
environment,
numBuckets: 20,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}

View file

@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import { TypeOf } from '@kbn/typed-react-router-config';
import React from 'react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { asInteger } from '../../../../../common/utils/formatters';
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
import { truncate } from '../../../../utils/style';
@ -46,6 +47,7 @@ export function getColumns({
comparisonEnabled?: boolean;
query: TypeOf<ApmRoutes, '/services/{serviceName}/errors'>['query'];
}): Array<EuiBasicTableColumn<ErrorGroupMainStatistics['errorGroups'][0]>> {
const { offset } = query;
return [
{
name: i18n.translate('xpack.apm.errorsTable.typeColumnLabel', {
@ -143,7 +145,9 @@ export function getColumns({
}
)}
comparisonSeries={
comparisonEnabled ? previousPeriodTimeseries : undefined
comparisonEnabled && isTimeComparison(offset)
? previousPeriodTimeseries
: undefined
}
comparisonSeriesColor={previousPeriodColor}
/>

View file

@ -15,6 +15,7 @@ import { i18n } from '@kbn/i18n';
import { orderBy } from 'lodash';
import React, { useState } from 'react';
import uuid from 'uuid';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
import { ErrorOverviewLink } from '../../../shared/links/apm/error_overview_link';
@ -149,7 +150,10 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
groupIds: JSON.stringify(
items.map(({ groupId: groupId }) => groupId).sort()
),
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}

View file

@ -9,6 +9,7 @@ import { EuiFlexItem, EuiPanel } from '@elastic/eui';
import { orderBy } from 'lodash';
import React, { useState } from 'react';
import uuid from 'uuid';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useApmParams } from '../../../hooks/use_apm_params';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
@ -109,7 +110,10 @@ export function ServiceOverviewInstancesChartAndTable({
start,
end,
transactionType,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}
@ -197,7 +201,10 @@ export function ServiceOverviewInstancesChartAndTable({
serviceNodeIds: JSON.stringify(
currentPeriodOrderedItems.map((item) => item.serviceNodeName)
),
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}

View file

@ -13,6 +13,7 @@ import {
import { i18n } from '@kbn/i18n';
import React, { ReactNode } from 'react';
import { ActionMenu } from '@kbn/observability-plugin/public';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { isJavaAgentName } from '../../../../../common/agent_name';
import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types';
import {
@ -55,6 +56,7 @@ export function getColumns({
itemIdToExpandedRowMap,
toggleRowActionMenu,
itemIdToOpenActionMenuRowMap,
offset,
shouldShowSparkPlots = true,
}: {
serviceName: string;
@ -64,6 +66,7 @@ export function getColumns({
detailedStatsLoading: boolean;
detailedStatsData?: ServiceInstanceDetailedStatistics;
comparisonEnabled?: boolean;
offset?: string;
toggleRowDetails: (selectedServiceNodeName: string) => void;
itemIdToExpandedRowMap: Record<string, ReactNode>;
toggleRowActionMenu: (selectedServiceNodeName: string) => void;
@ -130,7 +133,9 @@ export function getColumns({
isLoading={detailedStatsLoading}
series={currentPeriodTimestamp}
comparisonSeries={
comparisonEnabled ? previousPeriodTimestamp : undefined
comparisonEnabled && isTimeComparison(offset)
? previousPeriodTimestamp
: undefined
}
comparisonSeriesColor={previousPeriodColor}
/>
@ -164,7 +169,9 @@ export function getColumns({
isLoading={detailedStatsLoading}
series={currentPeriodTimestamp}
comparisonSeries={
comparisonEnabled ? previousPeriodTimestamp : undefined
comparisonEnabled && isTimeComparison(offset)
? previousPeriodTimestamp
: undefined
}
comparisonSeriesColor={previousPeriodColor}
/>
@ -198,7 +205,9 @@ export function getColumns({
isLoading={detailedStatsLoading}
series={currentPeriodTimestamp}
comparisonSeries={
comparisonEnabled ? previousPeriodTimestamp : undefined
comparisonEnabled && isTimeComparison(offset)
? previousPeriodTimestamp
: undefined
}
comparisonSeriesColor={previousPeriodColor}
/>
@ -232,7 +241,9 @@ export function getColumns({
isLoading={detailedStatsLoading}
series={currentPeriodTimestamp}
comparisonSeries={
comparisonEnabled ? previousPeriodTimestamp : undefined
comparisonEnabled && isTimeComparison(offset)
? previousPeriodTimestamp
: undefined
}
comparisonSeriesColor={previousPeriodColor}
/>
@ -266,7 +277,9 @@ export function getColumns({
isLoading={detailedStatsLoading}
series={currentPeriodTimestamp}
comparisonSeries={
comparisonEnabled ? previousPeriodTimestamp : undefined
comparisonEnabled && isTimeComparison(offset)
? previousPeriodTimestamp
: undefined
}
comparisonSeriesColor={previousPeriodColor}
/>

View file

@ -73,7 +73,7 @@ export function ServiceOverviewInstancesTable({
const { agentName } = useApmServiceContext();
const {
query: { kuery, latencyAggregationType, comparisonEnabled },
query: { kuery, latencyAggregationType, comparisonEnabled, offset },
} = useApmParams('/services/{serviceName}');
const [itemIdToOpenActionMenuRowMap, setItemIdToOpenActionMenuRowMap] =
@ -134,6 +134,7 @@ export function ServiceOverviewInstancesTable({
toggleRowActionMenu,
itemIdToOpenActionMenuRowMap,
shouldShowSparkPlots,
offset,
});
const pagination = {

View file

@ -14,6 +14,8 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { usePreviousPeriodLabel } from '../../../hooks/use_previous_period_text';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { ApmMlDetectorType } from '../../../../common/anomaly_detection/apm_ml_detectors';
import { asExactTransactionRate } from '../../../../common/utils/formatters';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
@ -75,7 +77,10 @@ export function ServiceOverviewThroughputChart({
start,
end,
transactionType,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
transactionName,
},
},
@ -100,6 +105,7 @@ export function ServiceOverviewThroughputChart({
ChartType.THROUGHPUT
);
const previousPeriodLabel = usePreviousPeriodLabel();
const timeseries = [
{
data: data.currentPeriod,
@ -115,10 +121,7 @@ export function ServiceOverviewThroughputChart({
data: data.previousPeriod,
type: 'area',
color: previousPeriodColor,
title: i18n.translate(
'xpack.apm.serviceOverview.throughtputChart.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
title: previousPeriodLabel,
},
]
: []),
@ -157,7 +160,14 @@ export function ServiceOverviewThroughputChart({
timeseries={timeseries}
yLabelFormat={asExactTransactionRate}
customTheme={comparisonChartTheme}
anomalyTimeseries={preferredAnomalyTimeseries}
anomalyTimeseries={
preferredAnomalyTimeseries
? {
...preferredAnomalyTimeseries,
color: previousPeriodColor,
}
: undefined
}
/>
</EuiPanel>
);

View file

@ -19,7 +19,7 @@ import { settings } from './settings';
import { ApmMainTemplate } from './templates/apm_main_template';
import { ServiceGroupsList } from '../app/service_groups';
import { ServiceGroupsRedirect } from './service_groups_redirect';
import { offsetRt } from '../../../common/offset_rt';
import { offsetRt } from '../../../common/comparison_rt';
const ServiceGroupsTitle = i18n.translate(
'xpack.apm.views.serviceGroups.title',

View file

@ -25,7 +25,7 @@ import { RedirectToBackendOverviewRouteView } from './redirect_to_backend_overvi
import { ServiceGroupTemplate } from '../templates/service_group_template';
import { ServiceGroupsRedirect } from '../service_groups_redirect';
import { RedirectTo } from '../redirect_to';
import { offsetRt } from '../../../../common/offset_rt';
import { offsetRt } from '../../../../common/comparison_rt';
import { TransactionTab } from '../../app/transaction_details/waterfall_with_summary/transaction_tabs';
function page<

View file

@ -28,7 +28,7 @@ import { ServiceDependencies } from '../../app/service_dependencies';
import { ServiceLogs } from '../../app/service_logs';
import { InfraOverview } from '../../app/infra_overview';
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
import { offsetRt } from '../../../../common/offset_rt';
import { offsetRt } from '../../../../common/comparison_rt';
function page({
title,

View file

@ -53,7 +53,7 @@ type Tab = NonNullable<EuiPageHeaderProps['tabs']>[0] & {
interface Props {
title: string;
children: React.ReactNode;
children: React.ReactChild;
selectedTab: Tab['key'];
searchBarOptions?: React.ComponentProps<typeof SearchBar>;
}
@ -61,9 +61,7 @@ interface Props {
export function ApmServiceTemplate(props: Props) {
return (
<ApmServiceContextProvider>
<ServiceAnomalyTimeseriesContextProvider>
<TemplateWithContext {...props} />
</ServiceAnomalyTimeseriesContextProvider>
<TemplateWithContext {...props} />
</ApmServiceContextProvider>
);
}
@ -127,8 +125,9 @@ function TemplateWithContext({
}}
>
<SearchBar {...searchBarOptions} />
{children}
<ServiceAnomalyTimeseriesContextProvider>
{children}
</ServiceAnomalyTimeseriesContextProvider>
</ApmMainTemplate>
);
}

View file

@ -9,6 +9,8 @@ import { EuiPanel, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import { usePreviousPeriodLabel } from '../../../../hooks/use_previous_period_text';
import { isTimeComparison } from '../../time_comparison/get_comparison_options';
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
import { asPercent } from '../../../../../common/utils/formatters';
import { useFetcher } from '../../../../hooks/use_fetcher';
@ -89,7 +91,10 @@ export function FailedTransactionRateChart({
end,
transactionType,
transactionName,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}
@ -113,6 +118,7 @@ export function FailedTransactionRateChart({
ChartType.FAILED_TRANSACTION_RATE
);
const previousPeriodLabel = usePreviousPeriodLabel();
const timeseries = [
{
data: data.currentPeriod.timeseries,
@ -128,10 +134,7 @@ export function FailedTransactionRateChart({
data: data.previousPeriod.timeseries,
type: 'area',
color: previousPeriodColor,
title: i18n.translate(
'xpack.apm.errorRate.chart.errorRate.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
title: previousPeriodLabel,
},
]
: []),
@ -170,7 +173,14 @@ export function FailedTransactionRateChart({
yLabelFormat={yLabelFormat}
yDomain={{ min: 0, max: 1 }}
customTheme={comparisonChartTheme}
anomalyTimeseries={preferredAnomalyTimeseries}
anomalyTimeseries={
preferredAnomalyTimeseries
? {
...preferredAnomalyTimeseries,
color: previousPeriodColor,
}
: undefined
}
/>
</EuiPanel>
);

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { Fit } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { rgba } from 'polished';
import { EuiTheme } from '@kbn/kibana-react-plugin/common';
@ -18,12 +17,20 @@ import {
import { ServiceAnomalyTimeseries } from '../../../../../common/anomaly_detection/service_anomaly_timeseries';
import { APMChartSpec } from '../../../../../typings/timeseries';
export const expectedBoundsTitle = i18n.translate(
'xpack.apm.comparison.expectedBoundsTitle',
{
defaultMessage: 'Expected bounds',
}
);
export function getChartAnomalyTimeseries({
anomalyTimeseries,
theme,
anomalyTimeseriesColor,
}: {
anomalyTimeseries?: ServiceAnomalyTimeseries;
theme: EuiTheme;
anomalyTimeseriesColor?: string;
}):
| {
boundaries: APMChartSpec[];
@ -36,21 +43,20 @@ export function getChartAnomalyTimeseries({
const boundaries = [
{
title: 'model plot',
title: expectedBoundsTitle,
type: 'area',
fit: Fit.Lookahead,
hideLegend: true,
hideLegend: false,
hideTooltipValue: true,
areaSeriesStyle: {
point: {
opacity: 0,
},
},
color: rgba(theme.eui.euiColorVis1, 0.5),
stackAccessors: ['x'],
yAccessors: ['y0'],
y0Accessors: ['y1'],
color: anomalyTimeseriesColor ?? rgba(theme.eui.euiColorVis1, 0.5),
yAccessors: ['y1'],
y0Accessors: ['y0'],
data: anomalyTimeseries.bounds,
key: 'expected_bounds',
},
];

View file

@ -23,6 +23,7 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { useChartTheme } from '@kbn/observability-plugin/public';
import { usePreviousPeriodLabel } from '../../../../hooks/use_previous_period_text';
import { SERVICE_NODE_NAME } from '../../../../../common/elasticsearch_fieldnames';
import {
asTransactionRate,
@ -104,6 +105,7 @@ export function InstancesLatencyDistributionChart({
min: 0,
max: Math.max(maxThroughput, maxComparisonThroughput),
};
const previousPeriodLabel = usePreviousPeriodLabel();
return (
<EuiPanel hasBorder={true}>
@ -152,10 +154,7 @@ export function InstancesLatencyDistributionChart({
{!!comparisonItems.length && (
<BubbleSeries
data={comparisonItems}
id={i18n.translate(
'xpack.apm.instancesLatencyDistributionChartLegend.previousPeriod',
{ defaultMessage: 'Previous period' }
)}
id={previousPeriodLabel}
xAccessor={(item) => item.throughput}
xScaleType={ScaleType.Linear}
yAccessors={[(item) => item.latency]}

View file

@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSelect, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { isTimeComparison } from '../../time_comparison/get_comparison_options';
import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types';
import { getDurationFormatter } from '../../../../../common/utils/formatters';
import { useLicenseContext } from '../../../../context/license/use_license_context';
@ -48,7 +49,7 @@ export function LatencyChart({ height, kuery }: Props) {
const license = useLicenseContext();
const {
query: { comparisonEnabled, latencyAggregationType },
query: { comparisonEnabled, latencyAggregationType, offset },
} = useAnyOfApmParams(
'/services/{serviceName}/overview',
'/services/{serviceName}/transactions',
@ -68,10 +69,11 @@ export function LatencyChart({ height, kuery }: Props) {
const preferredAnomalyTimeseries = usePreferredServiceAnomalyTimeseries(
ApmMlDetectorType.txLatency
);
const anomalyTimeseriesColor = previousPeriod?.color as string;
const timeseries = [
currentPeriod,
comparisonEnabled ? previousPeriod : undefined,
comparisonEnabled && isTimeComparison(offset) ? previousPeriod : undefined,
].filter(filterNil);
const latencyMaxY = getMaxY(timeseries);
@ -131,7 +133,14 @@ export function LatencyChart({ height, kuery }: Props) {
customTheme={comparisonChartTheme}
timeseries={timeseries}
yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)}
anomalyTimeseries={preferredAnomalyTimeseries}
anomalyTimeseries={
preferredAnomalyTimeseries
? {
...preferredAnomalyTimeseries,
color: anomalyTimeseriesColor,
}
: undefined
}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -17,8 +17,10 @@ import {
niceTimeFormatter,
Position,
ScaleType,
SeriesIdentifier,
Settings,
XYBrushEvent,
XYChartSeriesIdentifier,
YDomainRange,
} from '@elastic/charts';
import { EuiIcon } from '@elastic/eui';
@ -26,6 +28,8 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { useChartTheme } from '@kbn/observability-plugin/public';
import { isExpectedBoundsComparison } from '../time_comparison/get_comparison_options';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
import { ServiceAnomalyTimeseries } from '../../../../common/anomaly_detection/service_anomaly_timeseries';
import { asAbsoluteDateTime } from '../../../../common/utils/formatters';
import { Coordinate, TimeSeries } from '../../../../typings/timeseries';
@ -36,10 +40,16 @@ import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { useTheme } from '../../../hooks/use_theme';
import { unit } from '../../../utils/style';
import { ChartContainer } from './chart_container';
import { getChartAnomalyTimeseries } from './helper/get_chart_anomaly_timeseries';
import {
expectedBoundsTitle,
getChartAnomalyTimeseries,
} from './helper/get_chart_anomaly_timeseries';
import { isTimeseriesEmpty, onBrushEnd } from './helper/helper';
import { getTimeZone } from './helper/timezone';
interface AnomalyTimeseries extends ServiceAnomalyTimeseries {
color?: string;
}
interface Props {
id: string;
fetchStatus: FETCH_STATUS;
@ -56,8 +66,9 @@ interface Props {
yTickFormat?: (y: number) => string;
showAnnotations?: boolean;
yDomain?: YDomainRange;
anomalyTimeseries?: ServiceAnomalyTimeseries;
anomalyTimeseries?: AnomalyTimeseries;
customTheme?: Record<string, unknown>;
anomalyTimeseriesColor?: string;
}
export function TimeseriesChart({
id,
@ -78,6 +89,9 @@ export function TimeseriesChart({
const { setPointerEvent, chartRef } = useChartPointerEventContext();
const theme = useTheme();
const chartTheme = useChartTheme();
const {
query: { comparisonEnabled, offset },
} = useAnyOfApmParams('/services', '/backends/*', '/services/{serviceName}');
const xValues = timeseries.flatMap(({ data }) => data.map(({ x }) => x));
@ -89,19 +103,34 @@ export function TimeseriesChart({
const anomalyChartTimeseries = getChartAnomalyTimeseries({
anomalyTimeseries,
theme,
anomalyTimeseriesColor: anomalyTimeseries?.color,
});
const xFormatter = niceTimeFormatter([min, max]);
const isEmpty = isTimeseriesEmpty(timeseries);
const annotationColor = theme.eui.euiColorSuccess;
const isComparingExpectedBounds =
comparisonEnabled && isExpectedBoundsComparison(offset);
const allSeries = [
...timeseries,
// TODO: re-enable anomaly boundaries when we have a fix for https://github.com/elastic/kibana/issues/100660
// ...(anomalyChartTimeseries?.boundaries ?? []),
...(isComparingExpectedBounds && anomalyChartTimeseries?.boundaries
? anomalyChartTimeseries?.boundaries
: []),
...(anomalyChartTimeseries?.scores ?? []),
];
const xDomain = isEmpty ? { min: 0, max: 1 } : { min, max };
const legendSort = (a: SeriesIdentifier, b: SeriesIdentifier) => {
// Using custom legendSort here when comparing expected bounds
// because by default elastic-charts will show legends for expected bounds first
// but for consistency, we are making `Expected bounds` last
if ((a as XYChartSeriesIdentifier)?.specId === expectedBoundsTitle)
return -1;
if ((b as XYChartSeriesIdentifier)?.specId === expectedBoundsTitle)
return -1;
return 1;
};
return (
<ChartContainer
hasData={!isEmpty}
@ -111,7 +140,7 @@ export function TimeseriesChart({
>
<Chart ref={chartRef} id={id}>
<Settings
tooltip={{ stickTo: 'top', showNullValues: true }}
tooltip={{ stickTo: 'top', showNullValues: false }}
onBrushEnd={(event) =>
onBrushEnd({ x: (event as XYBrushEvent).x, history })
}
@ -129,6 +158,7 @@ export function TimeseriesChart({
tooltip: { visible: true },
}}
showLegend
legendSort={isComparingExpectedBounds ? legendSort : undefined}
legendPosition={Position.Bottom}
xDomain={xDomain}
onLegendItemClick={(legend) => {

View file

@ -14,6 +14,8 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { usePreviousPeriodLabel } from '../../../../hooks/use_previous_period_text';
import { isTimeComparison } from '../../time_comparison/get_comparison_options';
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
import { asPercent } from '../../../../../common/utils/formatters';
import { useFetcher } from '../../../../hooks/use_fetcher';
@ -90,7 +92,10 @@ export function TransactionColdstartRateChart({
start,
end,
transactionType,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
...(transactionName ? { transactionName } : {}),
},
},
@ -110,6 +115,7 @@ export function TransactionColdstartRateChart({
comparisonEnabled,
]
);
const previousPeriodLabel = usePreviousPeriodLabel();
const timeseries = [
{
@ -126,10 +132,7 @@ export function TransactionColdstartRateChart({
data: data.previousPeriod.transactionColdstartRate,
type: 'area',
color: theme.eui.euiColorMediumShade,
title: i18n.translate(
'xpack.apm.coldstartRate.chart.coldstartRate.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
title: previousPeriodLabel,
},
]
: []),

View file

@ -7,14 +7,23 @@
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import { Environment } from '../../../../common/environment_rt';
import { AnomalyDetectionJobsContextValue } from '../../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms';
export enum TimeRangeComparisonEnum {
WeekBefore = 'week',
DayBefore = 'day',
PeriodBefore = 'period',
ExpectedBounds = 'expected_bounds',
}
export const isTimeComparison = (v: string | undefined) =>
v !== TimeRangeComparisonEnum.ExpectedBounds;
export const isExpectedBoundsComparison = (v: string | undefined) =>
v === TimeRangeComparisonEnum.ExpectedBounds;
export const dayAndWeekBeforeToOffset = {
[TimeRangeComparisonEnum.DayBefore]: '1d',
[TimeRangeComparisonEnum.WeekBefore]: '1w',
@ -41,6 +50,10 @@ function formatDate({
)} - ${previousPeriodEnd.format(dateFormat)}`;
}
function isDefined<T>(argument: T | undefined | null): argument is T {
return argument !== undefined && argument !== null;
}
function getSelectOptions({
comparisonTypes,
start,
@ -51,51 +64,61 @@ function getSelectOptions({
start: moment.Moment;
end: moment.Moment;
msDiff: number;
}) {
return comparisonTypes.map((value) => {
switch (value) {
case TimeRangeComparisonEnum.DayBefore: {
return {
value: dayAndWeekBeforeToOffset[TimeRangeComparisonEnum.DayBefore],
text: i18n.translate('xpack.apm.timeComparison.select.dayBefore', {
defaultMessage: 'Day before',
}),
};
}
case TimeRangeComparisonEnum.WeekBefore: {
return {
value: dayAndWeekBeforeToOffset[TimeRangeComparisonEnum.WeekBefore],
text: i18n.translate('xpack.apm.timeComparison.select.weekBefore', {
defaultMessage: 'Week before',
}),
};
}
case TimeRangeComparisonEnum.PeriodBefore: {
const offset = `${msDiff}ms`;
}): Array<{ value: string; text: string; disabled?: boolean }> {
return comparisonTypes
.map((value) => {
switch (value) {
case TimeRangeComparisonEnum.DayBefore: {
return {
value: dayAndWeekBeforeToOffset[TimeRangeComparisonEnum.DayBefore],
text: i18n.translate('xpack.apm.timeComparison.select.dayBefore', {
defaultMessage: 'Day before',
}),
};
}
case TimeRangeComparisonEnum.WeekBefore: {
return {
value: dayAndWeekBeforeToOffset[TimeRangeComparisonEnum.WeekBefore],
text: i18n.translate('xpack.apm.timeComparison.select.weekBefore', {
defaultMessage: 'Week before',
}),
};
}
case TimeRangeComparisonEnum.PeriodBefore: {
const offset = `${msDiff}ms`;
const { startWithOffset, endWithOffset } = getOffsetInMs({
start: start.valueOf(),
end: end.valueOf(),
offset,
});
const { startWithOffset, endWithOffset } = getOffsetInMs({
start: start.valueOf(),
end: end.valueOf(),
offset,
});
return {
value: offset,
text: formatDate({
currentPeriodEnd: end,
previousPeriodStart: moment(startWithOffset),
previousPeriodEnd: moment(endWithOffset),
}),
};
return {
value: offset,
text: formatDate({
currentPeriodEnd: end,
previousPeriodStart: moment(startWithOffset),
previousPeriodEnd: moment(endWithOffset),
}),
};
}
}
}
});
})
.filter(isDefined);
}
export function getComparisonOptions({
start,
end,
canGetJobs,
anomalyDetectionJobsStatus,
anomalyDetectionJobsData,
preferredEnvironment,
}: {
canGetJobs?: boolean;
anomalyDetectionJobsStatus?: AnomalyDetectionJobsContextValue['anomalyDetectionJobsStatus'];
anomalyDetectionJobsData?: AnomalyDetectionJobsContextValue['anomalyDetectionJobsData'];
preferredEnvironment?: Environment;
start?: string;
end?: string;
}) {
@ -120,10 +143,36 @@ export function getComparisonOptions({
comparisonTypes = [TimeRangeComparisonEnum.PeriodBefore];
}
return getSelectOptions({
const hasMLJobsMatchingEnv =
canGetJobs &&
Array.isArray(anomalyDetectionJobsData?.jobs) &&
anomalyDetectionJobsData?.jobs.some(
(j) => j.environment === preferredEnvironment
);
const comparisonOptions = getSelectOptions({
comparisonTypes,
start: momentStart,
end: momentEnd,
msDiff,
});
if (canGetJobs) {
const disabled =
anomalyDetectionJobsStatus === 'success' && !hasMLJobsMatchingEnv;
comparisonOptions.push({
value: TimeRangeComparisonEnum.ExpectedBounds,
text: disabled
? i18n.translate('xpack.apm.comparison.mlExpectedBoundsDisabledText', {
defaultMessage:
'Expected bounds (Anomaly detection must be enabled for env)',
})
: i18n.translate('xpack.apm.comparison.mlExpectedBoundsText', {
defaultMessage: 'Expected bounds',
}),
disabled,
});
}
return comparisonOptions;
}

View file

@ -7,15 +7,21 @@
import { EuiCheckbox, EuiSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import React, { useMemo } from 'react';
import { useHistory } from 'react-router-dom';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { useUiTracker } from '@kbn/observability-plugin/public';
import { useEnvironmentsContext } from '../../../context/environments_context/use_environments_context';
import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
import { useBreakpoints } from '../../../hooks/use_breakpoints';
import { useTimeRange } from '../../../hooks/use_time_range';
import * as urlHelpers from '../links/url_helpers';
import { getComparisonOptions } from './get_comparison_options';
import {
getComparisonOptions,
TimeRangeComparisonEnum,
} from './get_comparison_options';
const PrependContainer = euiStyled.div`
display: flex;
@ -34,16 +40,48 @@ export function TimeComparison() {
query: { rangeFrom, rangeTo, comparisonEnabled, offset },
} = useAnyOfApmParams('/services', '/backends/*', '/services/{serviceName}');
const { anomalyDetectionJobsStatus, anomalyDetectionJobsData } =
useAnomalyDetectionJobsContext();
const { core } = useApmPluginContext();
const { preferredEnvironment } = useEnvironmentsContext();
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const comparisonOptions = getComparisonOptions({ start, end });
const canGetJobs = !!core.application.capabilities.ml?.canGetJobs;
const comparisonOptions = useMemo(() => {
const timeComparisonOptions = getComparisonOptions({
start,
end,
canGetJobs,
anomalyDetectionJobsStatus,
anomalyDetectionJobsData,
preferredEnvironment,
});
return timeComparisonOptions;
}, [
canGetJobs,
anomalyDetectionJobsStatus,
anomalyDetectionJobsData,
start,
end,
preferredEnvironment,
]);
const isSelectedComparisonTypeAvailable = comparisonOptions.some(
({ value }) => value === offset
);
// Replaces type when current one is no longer available in the select options
if (comparisonOptions.length !== 0 && !isSelectedComparisonTypeAvailable) {
if (
(comparisonOptions.length !== 0 && !isSelectedComparisonTypeAvailable) ||
// If user changes environment and there's no ML jobs that match the new environment
// then also default to first comparison option as well
(offset === TimeRangeComparisonEnum.ExpectedBounds &&
comparisonOptions.find(
(d) => d.value === TimeRangeComparisonEnum.ExpectedBounds
)?.disabled === true)
) {
urlHelpers.replace(history, {
query: { offset: comparisonOptions[0].value },
});
@ -54,7 +92,7 @@ export function TimeComparison() {
<EuiSelect
fullWidth={isSmall}
data-test-subj="comparisonSelect"
disabled={!comparisonEnabled}
disabled={comparisonEnabled === false}
options={comparisonOptions}
value={offset}
prepend={

View file

@ -16,6 +16,7 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { ValuesType } from 'utility-types';
import { isTimeComparison } from '../time_comparison/get_comparison_options';
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
import {
asMillisecondDuration,
@ -110,7 +111,9 @@ export function getColumns({
isLoading={transactionGroupDetailedStatisticsLoading}
series={currentTimeseries}
comparisonSeries={
comparisonEnabled ? previousTimeseries : undefined
comparisonEnabled && isTimeComparison(offset)
? previousTimeseries
: undefined
}
valueLabel={asMillisecondDuration(latency)}
comparisonSeriesColor={previousPeriodColor}
@ -145,7 +148,9 @@ export function getColumns({
isLoading={transactionGroupDetailedStatisticsLoading}
series={currentTimeseries}
comparisonSeries={
comparisonEnabled ? previousTimeseries : undefined
comparisonEnabled && isTimeComparison(offset)
? previousTimeseries
: undefined
}
valueLabel={asTransactionRate(throughput)}
comparisonSeriesColor={previousPeriodColor}
@ -202,7 +207,9 @@ export function getColumns({
isLoading={transactionGroupDetailedStatisticsLoading}
series={currentTimeseries}
comparisonSeries={
comparisonEnabled ? previousTimeseries : undefined
comparisonEnabled && isTimeComparison(offset)
? previousTimeseries
: undefined
}
valueLabel={asPercent(errorRate, 1)}
comparisonSeriesColor={previousPeriodColor}

View file

@ -30,6 +30,7 @@ import { useBreakpoints } from '../../../hooks/use_breakpoints';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
import { fromQuery, toQuery } from '../links/url_helpers';
import { isTimeComparison } from '../time_comparison/get_comparison_options';
type ApiResponse =
APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>;
@ -221,7 +222,10 @@ export function TransactionsTable({
transactionNames: JSON.stringify(
transactionGroups.map(({ name }) => name).sort()
),
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}

View file

@ -16,10 +16,12 @@ export const EnvironmentsContext = React.createContext<{
environment: Environment;
environments: Environment[];
status: FETCH_STATUS;
preferredEnvironment: Environment;
}>({
environment: ENVIRONMENT_ALL.value,
environments: [],
status: FETCH_STATUS.NOT_INITIATED,
preferredEnvironment: ENVIRONMENT_ALL.value,
});
export function EnvironmentsContextProvider({
@ -44,6 +46,10 @@ export function EnvironmentsContextProvider({
start,
end,
});
const preferredEnvironment =
environment === ENVIRONMENT_ALL.value && environments.length === 1
? environments[0]
: environment;
return (
<EnvironmentsContext.Provider
@ -51,6 +57,7 @@ export function EnvironmentsContextProvider({
environment,
environments,
status,
preferredEnvironment,
}}
>
{children}

View file

@ -6,6 +6,7 @@
*/
import React from 'react';
import { useEnvironmentsContext } from '../environments_context/use_environments_context';
import { ServiceAnomalyTimeseries } from '../../../common/anomaly_detection/service_anomaly_timeseries';
import { useApmParams } from '../../hooks/use_apm_params';
import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher';
@ -46,13 +47,13 @@ export function ServiceAnomalyTimeseriesContextProvider({
} = useApmParams('/services/{serviceName}');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const { preferredEnvironment } = useEnvironmentsContext();
const { status, data } = useFetcher(
(callApmApi) => {
if (!transactionType || !canGetAnomalies) {
return;
}
return callApmApi(
'GET /internal/apm/services/{serviceName}/anomaly_charts',
{
@ -64,12 +65,20 @@ export function ServiceAnomalyTimeseriesContextProvider({
start,
end,
transactionType,
environment: preferredEnvironment,
},
},
}
);
},
[serviceName, canGetAnomalies, transactionType, start, end]
[
serviceName,
canGetAnomalies,
transactionType,
start,
end,
preferredEnvironment,
]
);
return (

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { isTimeComparison } from '../components/shared/time_comparison/get_comparison_options';
import { useApmParams } from './use_apm_params';
import { useFetcher } from './use_fetcher';
import { useTimeRange } from './use_time_range';
@ -38,7 +39,10 @@ export function useErrorGroupDistributionFetcher({
kuery,
start,
end,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
groupId,
},
},

View file

@ -16,13 +16,12 @@ export function usePreferredServiceAnomalyTimeseries(
) {
const { allAnomalyTimeseries } = useServiceAnomalyTimeseriesContext();
const { environment, environments } = useEnvironmentsContext();
const { preferredEnvironment } = useEnvironmentsContext();
const { fallbackToTransactions } = useApmServiceContext();
return getPreferredServiceAnomalyTimeseries({
environment,
environments,
preferredEnvironment,
fallbackToTransactions,
detectorType,
allAnomalyTimeseries,

View file

@ -0,0 +1,35 @@
/*
* 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 { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { useAnyOfApmParams } from './use_apm_params';
import { useTimeRange } from './use_time_range';
import { getComparisonOptions } from '../components/shared/time_comparison/get_comparison_options';
const fallbackPreviousPeriodText = i18n.translate(
'xpack.apm.chart.comparison.defaultPreviousPeriodLabel',
{ defaultMessage: 'Previous period' }
);
export const usePreviousPeriodLabel = () => {
const {
query: { rangeFrom, rangeTo, offset },
} = useAnyOfApmParams('/services', '/backends/*', '/services/{serviceName}');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const previousPeriodText = useMemo(() => {
const timeComparisonOptions = getComparisonOptions({ start, end });
const comparisonPeriodText =
timeComparisonOptions.find(
(d) => d.value === offset || d.value.endsWith('ms')
)?.text ?? fallbackPreviousPeriodText;
return comparisonPeriodText;
}, [start, end, offset]);
return previousPeriodText;
};

View file

@ -6,6 +6,8 @@
*/
import { useMemo } from 'react';
import { usePreviousPeriodLabel } from './use_previous_period_text';
import { isTimeComparison } from '../components/shared/time_comparison/get_comparison_options';
import { useFetcher } from './use_fetcher';
import { useLegacyUrlParams } from '../context/url_params_context/use_url_params';
import { useApmServiceContext } from '../context/apm_service/use_apm_service_context';
@ -53,7 +55,10 @@ export function useTransactionLatencyChartsFetcher({
transactionType,
transactionName,
latencyAggregationType,
offset: comparisonEnabled ? offset : undefined,
offset:
comparisonEnabled && isTimeComparison(offset)
? offset
: undefined,
},
},
}
@ -74,11 +79,13 @@ export function useTransactionLatencyChartsFetcher({
]
);
const previousPeriodLabel = usePreviousPeriodLabel();
const memoizedData = useMemo(
() =>
getLatencyChartSelector({
latencyChart: data,
latencyAggregationType,
previousPeriodLabel,
}),
// It should only update when the data has changed
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -35,7 +35,9 @@ describe('getLatencyChartSelector', () => {
describe('without anomaly', () => {
it('returns default values when data is undefined', () => {
const latencyChart = getLatencyChartSelector({});
const latencyChart = getLatencyChartSelector({
previousPeriodLabel: 'Day before',
});
expect(latencyChart).toEqual({
currentPeriod: undefined,
previousPeriod: undefined,
@ -46,6 +48,7 @@ describe('getLatencyChartSelector', () => {
const latencyTimeseries = getLatencyChartSelector({
latencyChart: latencyChartData,
latencyAggregationType: LatencyAggregationType.avg,
previousPeriodLabel: 'Week before',
});
expect(latencyTimeseries).toEqual({
currentPeriod: {
@ -60,7 +63,7 @@ describe('getLatencyChartSelector', () => {
color: 'black',
data: [{ x: 1, y: 10 }],
type: 'area',
title: 'Previous period',
title: 'Week before',
},
});
});
@ -69,6 +72,7 @@ describe('getLatencyChartSelector', () => {
const latencyTimeseries = getLatencyChartSelector({
latencyChart: latencyChartData,
latencyAggregationType: LatencyAggregationType.p95,
previousPeriodLabel: 'Day before',
});
expect(latencyTimeseries).toEqual({
currentPeriod: {
@ -82,7 +86,7 @@ describe('getLatencyChartSelector', () => {
data: [{ x: 1, y: 10 }],
type: 'area',
color: 'black',
title: 'Previous period',
title: 'Day before',
},
});
});
@ -91,6 +95,7 @@ describe('getLatencyChartSelector', () => {
const latencyTimeseries = getLatencyChartSelector({
latencyChart: latencyChartData,
latencyAggregationType: LatencyAggregationType.p99,
previousPeriodLabel: 'Day before',
});
expect(latencyTimeseries).toEqual({
@ -105,7 +110,7 @@ describe('getLatencyChartSelector', () => {
data: [{ x: 1, y: 10 }],
type: 'area',
color: 'black',
title: 'Previous period',
title: 'Day before',
},
});
});
@ -116,6 +121,7 @@ describe('getLatencyChartSelector', () => {
const latencyTimeseries = getLatencyChartSelector({
latencyChart: latencyChartData,
latencyAggregationType: LatencyAggregationType.p99,
previousPeriodLabel: 'Previous period',
});
expect(latencyTimeseries).toEqual({
currentPeriod: {

View file

@ -25,9 +25,11 @@ export interface LatencyChartData {
export function getLatencyChartSelector({
latencyChart,
latencyAggregationType,
previousPeriodLabel,
}: {
latencyChart?: LatencyChartsResponse;
latencyAggregationType?: string;
previousPeriodLabel: string;
}): Partial<LatencyChartData> {
if (
!latencyChart?.currentPeriod.latencyTimeseries ||
@ -43,6 +45,7 @@ export function getLatencyChartSelector({
previousPeriod: getPreviousPeriodTimeseries({
previousPeriod: latencyChart.previousPeriod,
latencyAggregationType,
previousPeriodLabel,
}),
};
}
@ -50,9 +53,11 @@ export function getLatencyChartSelector({
function getPreviousPeriodTimeseries({
previousPeriod,
latencyAggregationType,
previousPeriodLabel,
}: {
previousPeriod: LatencyChartsResponse['previousPeriod'];
latencyAggregationType: string;
previousPeriodLabel: string;
}) {
let chartType = ChartType.LATENCY_AVG;
if (latencyAggregationType === 'p95') {
@ -67,10 +72,7 @@ function getPreviousPeriodTimeseries({
data: previousPeriod.latencyTimeseries ?? [],
type: 'area',
color: previousPeriodColor,
title: i18n.translate(
'xpack.apm.serviceOverview.latencyChartTitle.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
title: previousPeriodLabel,
};
}

View file

@ -10,13 +10,16 @@ import { getBucketSize } from '../helpers/get_bucket_size';
export function getAnomalyResultBucketSize({
start,
end,
minBucketSize,
}: {
start: number;
end: number;
minBucketSize?: number;
}) {
return getBucketSize({
start,
end,
numBuckets: 100,
minBucketSize,
});
}

View file

@ -8,6 +8,8 @@
import type { Logger } from '@kbn/logging';
import { compact, keyBy } from 'lodash';
import { rangeQuery } from '@kbn/observability-plugin/server';
import { parseInterval } from '@kbn/data-plugin/common';
import { Environment } from '../../../common/environment_rt';
import { apmMlAnomalyQuery } from './apm_ml_anomaly_query';
import {
ApmMlDetectorType,
@ -29,11 +31,13 @@ export async function getAnomalyTimeseries({
end,
logger,
mlSetup,
environment: preferredEnvironment,
}: {
serviceName: string;
transactionType: string;
start: number;
end: number;
environment: Environment;
logger: Logger;
mlSetup: Required<Setup>['ml'];
}): Promise<ServiceAnomalyTimeseries[]> {
@ -41,17 +45,31 @@ export async function getAnomalyTimeseries({
return [];
}
const { intervalString } = getAnomalyResultBucketSize({
start,
end,
});
const mlJobs = await getMlJobsWithAPMGroup(mlSetup.anomalyDetectors);
if (!mlJobs.length) {
return [];
}
// If multiple ML jobs exist
// find the first job with valid running datafeed that matches the preferred environment
const preferredBucketSpan = mlJobs.find(
(j) =>
j.datafeedState !== undefined && j.environment === preferredEnvironment
)?.bucketSpan;
// If the calculated bucketSize is smaller than the bucket span interval,
// use the original job's bucket_span
const minBucketSize = preferredBucketSpan
? parseInterval(preferredBucketSpan)?.asSeconds()
: undefined;
const { intervalString } = getAnomalyResultBucketSize({
start,
end,
minBucketSize,
});
const anomaliesResponse = await anomalySearch(
mlSetup.mlSystem.mlAnomalySearch,
{

View file

@ -59,6 +59,7 @@ export function getMlJobsWithAPMGroup(
version: Number(job.custom_settings?.job_tags?.apm_ml_version ?? 1),
datafeedId: datafeedStats?.datafeed_id,
datafeedState: datafeedStats?.state as ApmMlJob['datafeedState'],
bucketSpan: job.analysis_config?.bucket_span,
};
});
} catch (e) {

View file

@ -17,7 +17,7 @@ import { getUpstreamServicesForBackend } from './get_upstream_services_for_backe
import { getThroughputChartsForBackend } from './get_throughput_charts_for_backend';
import { getErrorRateChartsForBackend } from './get_error_rate_charts_for_backend';
import { ConnectionStatsItemWithImpact } from '../../../common/connections';
import { offsetRt } from '../../../common/offset_rt';
import { offsetRt } from '../../../common/comparison_rt';
const topBackendsRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/backends/top_backends',

View file

@ -14,7 +14,7 @@ import { environmentRt, kueryRt, rangeRt } from '../default_api_types';
import { getErrorGroupMainStatistics } from './get_error_groups/get_error_group_main_statistics';
import { getErrorGroupPeriods } from './get_error_groups/get_error_group_detailed_statistics';
import { getErrorGroupSample } from './get_error_groups/get_error_group_sample';
import { offsetRt } from '../../../common/offset_rt';
import { offsetRt } from '../../../common/comparison_rt';
const errorsMainStatisticsRoute = createApmServerRoute({
endpoint:

View file

@ -18,7 +18,7 @@ import { getServiceMapServiceNodeInfo } from './get_service_map_service_node_inf
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { environmentRt, rangeRt } from '../default_api_types';
import { getServiceGroup } from '../service_groups/get_service_group';
import { offsetRt } from '../../../common/offset_rt';
import { offsetRt } from '../../../common/comparison_rt';
const serviceMapRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/service-map',

View file

@ -52,7 +52,7 @@ import { ConnectionStatsItemWithImpact } from '../../../common/connections';
import { getSortedAndFilteredServices } from './get_services/get_sorted_and_filtered_services';
import { ServiceHealthStatus } from '../../../common/service_health_status';
import { getServiceGroup } from '../service_groups/get_service_group';
import { offsetRt } from '../../../common/offset_rt';
import { offsetRt } from '../../../common/comparison_rt';
const servicesRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/services',
@ -1161,7 +1161,11 @@ const serviceAnomalyChartsRoute = createApmServerRoute({
path: t.type({
serviceName: t.string,
}),
query: t.intersection([rangeRt, t.type({ transactionType: t.string })]),
query: t.intersection([
rangeRt,
environmentRt,
t.type({ transactionType: t.string }),
]),
}),
options: {
tags: ['access:apm'],
@ -1181,7 +1185,7 @@ const serviceAnomalyChartsRoute = createApmServerRoute({
const {
path: { serviceName },
query: { start, end, transactionType },
query: { start, end, transactionType, environment },
} = resources.params;
try {
@ -1192,6 +1196,7 @@ const serviceAnomalyChartsRoute = createApmServerRoute({
end,
mlSetup: setup.ml,
logger: resources.logger,
environment,
});
return {

View file

@ -22,7 +22,7 @@ import { getFailedTransactionRatePeriods } from './get_failed_transaction_rate_p
import { getColdstartRatePeriods } from '../../lib/transaction_groups/get_coldstart_rate';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { environmentRt, kueryRt, rangeRt } from '../default_api_types';
import { offsetRt } from '../../../common/offset_rt';
import { offsetRt } from '../../../common/comparison_rt';
const transactionGroupsMainStatisticsRoute = createApmServerRoute({
endpoint:

View file

@ -5348,10 +5348,10 @@
"sharedUXComponents.noDataPage.elasticAgentCard.noPermission.description": "Cette intégration n'est pas encore activée. Votre administrateur possède les autorisations requises pour lactiver.",
"sharedUXComponents.noDataPage.elasticAgentCard.noPermission.title": "Contactez votre administrateur",
"sharedUXComponents.noDataPage.elasticAgentCard.title": "Ajouter Elastic Agent",
"sharedUXPackages.noDataViewsPrompt.learnMore": "Envie d'en savoir plus ?",
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "Lisez les documents",
"sharedUXComponents.pageTemplate.noDataCard.description": "Continuer sans collecter de données",
"sharedUXComponents.toolbar.buttons.addFromLibrary.libraryButtonLabel": "Ajouter depuis la bibliothèque",
"sharedUXPackages.noDataViewsPrompt.learnMore": "Envie d'en savoir plus ?",
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "Lisez les documents",
"telemetry.callout.appliesSettingTitle": "Les modifications apportées à ce paramètre s'appliquent dans {allOfKibanaText} et sont enregistrées automatiquement.",
"telemetry.callout.appliesSettingTitle.allOfKibanaText": "tout Kibana",
"telemetry.callout.clusterStatisticsDescription": "Voici un exemple des statistiques de cluster de base que nous collecterons. Cela comprend le nombre d'index, de partitions et de nœuds. Cela comprend également des statistiques d'utilisation de niveau élevé, comme l'état d'activation du monitoring.",
@ -7181,12 +7181,9 @@
"xpack.apm.backendDetailLatencyChartTitle": "Latence",
"xpack.apm.backendDetailThroughputChartTitle": "Rendement",
"xpack.apm.backendErrorRateChart.chartTitle": "Taux de transactions ayant échoué",
"xpack.apm.backendErrorRateChart.previousPeriodLabel": "Période précédente",
"xpack.apm.backendInventory.dependencyTableColumn": "Dépendance",
"xpack.apm.backendLatencyChart.chartTitle": "Latence",
"xpack.apm.backendLatencyChart.previousPeriodLabel": "Période précédente",
"xpack.apm.backendThroughputChart.chartTitle": "Rendement",
"xpack.apm.backendThroughputChart.previousPeriodLabel": "Période précédente",
"xpack.apm.chart.annotation.version": "Version",
"xpack.apm.chart.cpuSeries.processAverageLabel": "Moyenne de processus",
"xpack.apm.chart.cpuSeries.processMaxLabel": "Max de processus",
@ -7197,7 +7194,6 @@
"xpack.apm.chart.memorySeries.systemMaxLabel": "Max.",
"xpack.apm.coldstartRate": "Taux de démarrage à froid",
"xpack.apm.coldstartRate.chart.coldstartRate": "Taux de démarrage à froid (moy.)",
"xpack.apm.coldstartRate.chart.coldstartRate.previousPeriodLabel": "Période précédente",
"xpack.apm.compositeSpanCallsLabel": ", {count} appels, sur une moyenne de {duration}",
"xpack.apm.compositeSpanDurationLabel": "Durée moyenne",
"xpack.apm.correlations.cancelButtonTitle": "Annuler",
@ -7295,7 +7291,6 @@
"xpack.apm.errorCountAlert.name": "Seuil de nombre d'erreurs",
"xpack.apm.errorCountAlertTrigger.errors": " erreurs",
"xpack.apm.errorGroup.chart.ocurrences": "Occurrences",
"xpack.apm.errorGroup.chart.ocurrences.previousPeriodLabel": "Période précédente",
"xpack.apm.errorGroupDetails.culpritLabel": "Coupable",
"xpack.apm.errorGroupDetails.errorGroupTitle": "Groupe d'erreurs {errorGroupId}",
"xpack.apm.errorGroupDetails.errorOccurrenceTitle": "Occurrence d'erreur",
@ -7307,7 +7302,6 @@
"xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "Visualisez {occurrencesCount} {occurrencesCount, plural, one {occurrence} other {occurrences}} dans Discover.",
"xpack.apm.errorRate": "Taux de transactions ayant échoué",
"xpack.apm.errorRate.chart.errorRate": "Taux de transactions ayant échoué (moy.)",
"xpack.apm.errorRate.chart.errorRate.previousPeriodLabel": "Période précédente",
"xpack.apm.errorRate.tip": "Le pourcentage de transactions ayant échoué pour le service sélectionné. Les transactions du serveur HTTP avec un code du statut 4xx (erreur du client) ne sont pas considérées comme des échecs, car l'appelant, et non le serveur, a provoqué l'échec.",
"xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "Message d'erreur et coupable",
"xpack.apm.errorsTable.groupIdColumnDescription": "Hachage de la trace de pile. Regroupe les erreurs similaires, même lorsque le message d'erreur est différent en raison des paramètres dynamiques.",
@ -7480,7 +7474,6 @@
"xpack.apm.home.serviceMapTabLabel": "Carte des services",
"xpack.apm.inspectButtonText": "Inspecter",
"xpack.apm.instancesLatencyDistributionChartLegend": "Instances",
"xpack.apm.instancesLatencyDistributionChartLegend.previousPeriod": "Période précédente",
"xpack.apm.instancesLatencyDistributionChartTitle": "Distribution de la latence des instances",
"xpack.apm.instancesLatencyDistributionChartTooltipClickToFilterDescription": "Cliquer pour filtrer par instance",
"xpack.apm.instancesLatencyDistributionChartTooltipInstancesTitle": "{instancesCount} {instancesCount, plural, one {instance} other {instances}}",
@ -7727,14 +7720,12 @@
"xpack.apm.serviceOverview.instanceTable.details.serviceTitle": "Service",
"xpack.apm.serviceOverview.latencyChartTitle": "Latence",
"xpack.apm.serviceOverview.latencyChartTitle.prepend": "Indicateur",
"xpack.apm.serviceOverview.latencyChartTitle.previousPeriodLabel": "Période précédente",
"xpack.apm.serviceOverview.latencyColumnAvgLabel": "Latence (moy.)",
"xpack.apm.serviceOverview.latencyColumnDefaultLabel": "Latence",
"xpack.apm.serviceOverview.latencyColumnP95Label": "Latence (95e)",
"xpack.apm.serviceOverview.latencyColumnP99Label": "Latence (99e)",
"xpack.apm.serviceOverview.loadingText": "Chargement…",
"xpack.apm.serviceOverview.noResultsText": "Aucune instance trouvée",
"xpack.apm.serviceOverview.throughtputChart.previousPeriodLabel": "Période précédente",
"xpack.apm.serviceOverview.throughtputChartTitle": "Rendement",
"xpack.apm.serviceOverview.tpmHelp": "Le rendement est mesuré en transactions par minute (tpm).",
"xpack.apm.serviceOverview.transactionsTableColumnErrorRate": "Taux de transactions ayant échoué",

View file

@ -5450,10 +5450,10 @@
"sharedUXComponents.noDataPage.elasticAgentCard.noPermission.description": "この統合はまだ有効ではありません。管理者にはオンにするために必要なアクセス権があります。",
"sharedUXComponents.noDataPage.elasticAgentCard.noPermission.title": "管理者にお問い合わせください",
"sharedUXComponents.noDataPage.elasticAgentCard.title": "Elasticエージェントの追加",
"sharedUXPackages.noDataViewsPrompt.learnMore": "詳細について",
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "<b>ドキュメント</b><b>を読む</b>",
"sharedUXComponents.pageTemplate.noDataCard.description": "データを収集せずに続行",
"sharedUXComponents.toolbar.buttons.addFromLibrary.libraryButtonLabel": "ライブラリから追加",
"sharedUXPackages.noDataViewsPrompt.learnMore": "詳細について",
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "<b>ドキュメント</b><b>を読む</b>",
"telemetry.callout.appliesSettingTitle": "この設定に加えた変更は {allOfKibanaText} に適用され、自動的に保存されます。",
"telemetry.callout.appliesSettingTitle.allOfKibanaText": "Kibana のすべて",
"telemetry.callout.clusterStatisticsDescription": "これは収集される基本的なクラスター統計の例です。インデックス、シャード、ノードの数が含まれます。監視がオンになっているかどうかなどのハイレベルの使用統計も含まれます。",
@ -7277,12 +7277,9 @@
"xpack.apm.backendDetailLatencyChartTitle": "レイテンシ",
"xpack.apm.backendDetailThroughputChartTitle": "スループット",
"xpack.apm.backendErrorRateChart.chartTitle": "失敗したトランザクション率",
"xpack.apm.backendErrorRateChart.previousPeriodLabel": "前の期間",
"xpack.apm.backendInventory.dependencyTableColumn": "依存関係",
"xpack.apm.backendLatencyChart.chartTitle": "レイテンシ",
"xpack.apm.backendLatencyChart.previousPeriodLabel": "前の期間",
"xpack.apm.backendThroughputChart.chartTitle": "スループット",
"xpack.apm.backendThroughputChart.previousPeriodLabel": "前の期間",
"xpack.apm.chart.annotation.version": "バージョン",
"xpack.apm.chart.cpuSeries.processAverageLabel": "プロセス平均",
"xpack.apm.chart.cpuSeries.processMaxLabel": "プロセス最大",
@ -7293,7 +7290,6 @@
"xpack.apm.chart.memorySeries.systemMaxLabel": "最高",
"xpack.apm.coldstartRate": "コールドスタート率",
"xpack.apm.coldstartRate.chart.coldstartRate": "コールドスタート率(平均)",
"xpack.apm.coldstartRate.chart.coldstartRate.previousPeriodLabel": "前の期間",
"xpack.apm.compositeSpanCallsLabel": "、{count}件の呼び出し、平均{duration}",
"xpack.apm.compositeSpanDurationLabel": "平均時間",
"xpack.apm.correlations.cancelButtonTitle": "キャンセル",
@ -7391,7 +7387,6 @@
"xpack.apm.errorCountAlert.name": "エラー数しきい値",
"xpack.apm.errorCountAlertTrigger.errors": " エラー",
"xpack.apm.errorGroup.chart.ocurrences": "オカレンス",
"xpack.apm.errorGroup.chart.ocurrences.previousPeriodLabel": "前の期間",
"xpack.apm.errorGroupDetails.culpritLabel": "原因",
"xpack.apm.errorGroupDetails.errorGroupTitle": "エラーグループ {errorGroupId}",
"xpack.apm.errorGroupDetails.errorOccurrenceTitle": "エラーのオカレンス",
@ -7403,7 +7398,6 @@
"xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "Discover で {occurrencesCount} {occurrencesCount, plural, other {件の発生}} を表示",
"xpack.apm.errorRate": "失敗したトランザクション率",
"xpack.apm.errorRate.chart.errorRate": "失敗したトランザクション率(平均)",
"xpack.apm.errorRate.chart.errorRate.previousPeriodLabel": "前の期間",
"xpack.apm.errorRate.tip": "選択したサービスの失敗したトランザクションの割合。4xxステータスコードクライアントエラーのHTTPサーバートランザクションは、サーバーではなく呼び出し側が失敗の原因であるため、失敗と見なされません。",
"xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "エラーメッセージと原因",
"xpack.apm.errorsTable.groupIdColumnDescription": "スタックトレースのハッシュ。動的パラメータのため、エラーメッセージが異なる場合でも、類似したエラーをグループ化します。",
@ -7576,7 +7570,6 @@
"xpack.apm.home.serviceMapTabLabel": "サービスマップ",
"xpack.apm.inspectButtonText": "検査",
"xpack.apm.instancesLatencyDistributionChartLegend": "インスタンス",
"xpack.apm.instancesLatencyDistributionChartLegend.previousPeriod": "前の期間",
"xpack.apm.instancesLatencyDistributionChartTitle": "インスタンスのレイテンシ分布",
"xpack.apm.instancesLatencyDistributionChartTooltipClickToFilterDescription": "クリックすると、インスタンスでフィルタリングします",
"xpack.apm.instancesLatencyDistributionChartTooltipInstancesTitle": "{instancesCount} {instancesCount, plural, other {個のインスタンス}}",
@ -7821,14 +7814,12 @@
"xpack.apm.serviceOverview.instanceTable.details.serviceTitle": "サービス",
"xpack.apm.serviceOverview.latencyChartTitle": "レイテンシ",
"xpack.apm.serviceOverview.latencyChartTitle.prepend": "メトリック",
"xpack.apm.serviceOverview.latencyChartTitle.previousPeriodLabel": "前の期間",
"xpack.apm.serviceOverview.latencyColumnAvgLabel": "レイテンシ(平均)",
"xpack.apm.serviceOverview.latencyColumnDefaultLabel": "レイテンシ",
"xpack.apm.serviceOverview.latencyColumnP95Label": "レイテンシ95 番目)",
"xpack.apm.serviceOverview.latencyColumnP99Label": "レイテンシ99 番目)",
"xpack.apm.serviceOverview.loadingText": "読み込み中…",
"xpack.apm.serviceOverview.noResultsText": "インスタンスが見つかりません",
"xpack.apm.serviceOverview.throughtputChart.previousPeriodLabel": "前の期間",
"xpack.apm.serviceOverview.throughtputChartTitle": "スループット",
"xpack.apm.serviceOverview.tpmHelp": "スループットは1分あたりのトランザクション数tpmで測定されます。",
"xpack.apm.serviceOverview.transactionsTableColumnErrorRate": "失敗したトランザクション率",

View file

@ -5461,10 +5461,10 @@
"sharedUXComponents.noDataPage.elasticAgentCard.noPermission.description": "尚未启用此集成。您的管理员具有打开它所需的权限。",
"sharedUXComponents.noDataPage.elasticAgentCard.noPermission.title": "请联系您的管理员",
"sharedUXComponents.noDataPage.elasticAgentCard.title": "添加 Elastic 代理",
"sharedUXPackages.noDataViewsPrompt.learnMore": "希望了解详情?",
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "阅读文档",
"sharedUXComponents.pageTemplate.noDataCard.description": "继续,而不收集数据",
"sharedUXComponents.toolbar.buttons.addFromLibrary.libraryButtonLabel": "从库中添加",
"sharedUXPackages.noDataViewsPrompt.learnMore": "希望了解详情?",
"sharedUXPackages.noDataViewsPrompt.readDocumentation": "阅读文档",
"telemetry.callout.appliesSettingTitle": "对此设置的更改将应用到{allOfKibanaText} 且会自动保存。",
"telemetry.callout.appliesSettingTitle.allOfKibanaText": "整个 Kibana",
"telemetry.callout.clusterStatisticsDescription": "这是我们将收集的基本集群统计信息的示例。其包括索引、分片和节点的数目。还包括概括性的使用情况统计信息,例如监测是否打开。",
@ -7294,12 +7294,9 @@
"xpack.apm.backendDetailLatencyChartTitle": "延迟",
"xpack.apm.backendDetailThroughputChartTitle": "吞吐量",
"xpack.apm.backendErrorRateChart.chartTitle": "失败事务率",
"xpack.apm.backendErrorRateChart.previousPeriodLabel": "上一时段",
"xpack.apm.backendInventory.dependencyTableColumn": "依赖项",
"xpack.apm.backendLatencyChart.chartTitle": "延迟",
"xpack.apm.backendLatencyChart.previousPeriodLabel": "上一时段",
"xpack.apm.backendThroughputChart.chartTitle": "吞吐量",
"xpack.apm.backendThroughputChart.previousPeriodLabel": "上一时段",
"xpack.apm.chart.annotation.version": "版本",
"xpack.apm.chart.cpuSeries.processAverageLabel": "进程平均值",
"xpack.apm.chart.cpuSeries.processMaxLabel": "进程最大值",
@ -7310,7 +7307,6 @@
"xpack.apm.chart.memorySeries.systemMaxLabel": "最大值",
"xpack.apm.coldstartRate": "冷启动速率",
"xpack.apm.coldstartRate.chart.coldstartRate": "冷启动速率(平均)",
"xpack.apm.coldstartRate.chart.coldstartRate.previousPeriodLabel": "上一时段",
"xpack.apm.compositeSpanCallsLabel": "{count} 个调用,平均 {duration}",
"xpack.apm.compositeSpanDurationLabel": "平均持续时间",
"xpack.apm.correlations.cancelButtonTitle": "取消",
@ -7408,7 +7404,6 @@
"xpack.apm.errorCountAlert.name": "错误计数阈值",
"xpack.apm.errorCountAlertTrigger.errors": " 错误",
"xpack.apm.errorGroup.chart.ocurrences": "发生次数",
"xpack.apm.errorGroup.chart.ocurrences.previousPeriodLabel": "上一时段",
"xpack.apm.errorGroupDetails.culpritLabel": "原因",
"xpack.apm.errorGroupDetails.errorGroupTitle": "错误组 {errorGroupId}",
"xpack.apm.errorGroupDetails.errorOccurrenceTitle": "错误发生",
@ -7420,7 +7415,6 @@
"xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "在 Discover 中查看 {occurrencesCount} 次{occurrencesCount, plural, other {发生}}",
"xpack.apm.errorRate": "失败事务率",
"xpack.apm.errorRate.chart.errorRate": "失败事务率(平均值)",
"xpack.apm.errorRate.chart.errorRate.previousPeriodLabel": "上一时段",
"xpack.apm.errorRate.tip": "选定服务的失败事务百分比。状态代码为 4xx 的 HTTP 服务器事务(客户端错误)不会视为失败,因为是调用方而不是服务器造成了失败。",
"xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "错误消息和原因",
"xpack.apm.errorsTable.groupIdColumnDescription": "堆栈跟踪的哈希。将类似错误分组在一起,即使因动态参数造成错误消息不同。",
@ -7593,7 +7587,6 @@
"xpack.apm.home.serviceMapTabLabel": "服务地图",
"xpack.apm.inspectButtonText": "检查",
"xpack.apm.instancesLatencyDistributionChartLegend": "实例",
"xpack.apm.instancesLatencyDistributionChartLegend.previousPeriod": "上一时段",
"xpack.apm.instancesLatencyDistributionChartTitle": "实例延迟分布",
"xpack.apm.instancesLatencyDistributionChartTooltipClickToFilterDescription": "单击按实例筛选",
"xpack.apm.instancesLatencyDistributionChartTooltipInstancesTitle": "{instancesCount} 个{instancesCount, plural, other {实例}}",
@ -7840,14 +7833,12 @@
"xpack.apm.serviceOverview.instanceTable.details.serviceTitle": "服务",
"xpack.apm.serviceOverview.latencyChartTitle": "延迟",
"xpack.apm.serviceOverview.latencyChartTitle.prepend": "指标",
"xpack.apm.serviceOverview.latencyChartTitle.previousPeriodLabel": "上一时段",
"xpack.apm.serviceOverview.latencyColumnAvgLabel": "延迟(平均值)",
"xpack.apm.serviceOverview.latencyColumnDefaultLabel": "延迟",
"xpack.apm.serviceOverview.latencyColumnP95Label": "延迟(第 95 个)",
"xpack.apm.serviceOverview.latencyColumnP99Label": "延迟(第 99 个)",
"xpack.apm.serviceOverview.loadingText": "正在加载……",
"xpack.apm.serviceOverview.noResultsText": "未找到实例",
"xpack.apm.serviceOverview.throughtputChart.previousPeriodLabel": "上一时段",
"xpack.apm.serviceOverview.throughtputChartTitle": "吞吐量",
"xpack.apm.serviceOverview.tpmHelp": "吞吐量按每分钟事务数 (tpm) 来度量。",
"xpack.apm.serviceOverview.transactionsTableColumnErrorRate": "失败事务率",

View file

@ -10,6 +10,7 @@ import { range, omit } from 'lodash';
import { apm, timerange } from '@elastic/apm-synthtrace';
import { ServiceAnomalyTimeseries } from '@kbn/apm-plugin/common/anomaly_detection/service_anomaly_timeseries';
import { ApmMlDetectorType } from '@kbn/apm-plugin/common/anomaly_detection/apm_ml_detectors';
import { Environment } from '@kbn/apm-plugin/common/environment_rt';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { ApmApiError } from '../../common/apm_api_supertest';
import { createAndRunApmMlJob } from '../../common/utils/create_and_run_apm_ml_job';
@ -40,11 +41,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
end,
transactionType,
serviceName,
environment,
}: {
start: string;
end: string;
transactionType: string;
serviceName: string;
environment: Environment;
},
user = apmApiClient.readUser
) {
@ -58,6 +61,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
start,
end,
transactionType,
environment,
},
},
});
@ -74,6 +78,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
transactionType: 'request',
start: '2021-01-01T00:00:00.000Z',
end: '2021-01-01T00:15:00.000Z',
environment: 'ENVIRONMENT_ALL',
})
);
@ -141,6 +146,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
transactionType: 'request',
start,
end,
environment: 'ENVIRONMENT_ALL',
},
apmApiClient.noMlAccessUser
)
@ -156,6 +162,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
transactionType: 'request',
start,
end,
environment: 'ENVIRONMENT_ALL',
})
);
@ -182,6 +189,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
transactionType: 'request',
start,
end,
environment: 'ENVIRONMENT_ALL',
})
);
@ -202,6 +210,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
transactionType: 'request',
start,
end,
environment: 'ENVIRONMENT_ALL',
})
).body.allAnomalyTimeseries;