Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export function TransactionDurationRuleType(props: Props) {
interval,
start,
end,
groupBy: params.groupBy,
},
},
}
Expand All @@ -135,6 +136,7 @@ export function TransactionDurationRuleType(props: Props) {
params.transactionName,
params.windowSize,
params.windowUnit,
params.groupBy,
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
TickFormatter,
} from '@elastic/charts';
import { EuiSpacer } from '@elastic/eui';
import React, { useState } from 'react';
import React from 'react';
import { IUiSettingsClient } from '@kbn/core/public';
import { Coordinate } from '../../../../../typings/timeseries';
import { useTheme } from '../../../../hooks/use_theme';
Expand All @@ -39,21 +39,13 @@ export function ChartPreview({
uiSettings,
series,
}: ChartPreviewProps) {
const [yMax, setYMax] = useState(threshold);

const theme = useTheme();
const thresholdOpacity = 0.3;
const timestamps = series.flatMap(({ data }) => data.map(({ x }) => x));
const xMin = Math.min(...timestamps);
const xMax = Math.max(...timestamps);
const xFormatter = niceTimeFormatter([xMin, xMax]);

function updateYMax() {
// Make the maximum Y value either the actual max or 20% more than the threshold
const values = series.flatMap(({ data }) => data.map((d) => d.y ?? 0));
setYMax(Math.max(...values, threshold * 1.2));
}

const style = {
fill: theme.eui.euiColorVis2,
line: {
Expand All @@ -76,24 +68,35 @@ export function ChartPreview({
];

const timeZone = getTimeZone(uiSettings);
const legendSize = Math.ceil(series.length / 2) * 30;
const legendSize =
series.length > 1 ? Math.ceil(series.length / 2) * 30 : series.length * 35;
const chartSize = 150;

const domainYMax = () => {
// Make the maximum Y value either the actual max or 20% more than the threshold
const values = series.flatMap(({ data }) => data.map((d) => d.y ?? 0));
return Math.max(...values, threshold * 1.2);
};

const domain = {
max: domainYMax(),
min: 0,
};

return (
<>
<EuiSpacer size="m" />
<Chart
size={{
height: series.length > 1 ? chartSize + legendSize : chartSize,
height: chartSize + legendSize,
}}
data-test-subj="ChartPreview"
>
<Settings
tooltip="none"
showLegend={series.length > 1}
showLegend={true}
legendPosition={'bottom'}
legendSize={legendSize}
onLegendItemClick={updateYMax}
/>
<LineAnnotation
dataValues={[{ dataValue: threshold }]}
Expand All @@ -119,7 +122,7 @@ export function ChartPreview({
position={Position.Left}
tickFormat={yTickFormat}
ticks={5}
domain={{ max: yMax, min: 0 }}
domain={domain}
/>
{series.map(({ name, data }, index) => (
<BarSeries
Expand Down
4 changes: 3 additions & 1 deletion x-pack/plugins/apm/server/routes/alerts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const alertParamsRt = t.intersection([
t.type({
interval: t.string,
}),
t.partial({
groupBy: t.array(t.string),
}),
]);

export type AlertParams = t.TypeOf<typeof alertParamsRt>;
Expand Down Expand Up @@ -112,7 +115,6 @@ const transactionDurationChartPreview = createApmServerRoute({

export const alertsChartPreviewRouteRepository = {
...transactionErrorRateChartPreview,
...transactionDurationChartPreview,
...transactionErrorCountChartPreview,
...transactionDurationChartPreview,
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { rangeQuery, termQuery } from '@kbn/observability-plugin/server';
import { AggregationType } from '../../../../../common/rules/apm_rule_types';
import {
AggregationType,
ApmRuleType,
} from '../../../../../common/rules/apm_rule_types';
import {
SERVICE_NAME,
SERVICE_ENVIRONMENT,
TRANSACTION_TYPE,
TRANSACTION_NAME,
} from '../../../../../common/es_fields/apm';
Expand All @@ -23,12 +25,13 @@ import {
getProcessorEventForTransactions,
} from '../../../../lib/helpers/transactions';
import {
ENVIRONMENT_NOT_DEFINED,
getEnvironmentLabel,
} from '../../../../../common/environment_filter_values';
import { averageOrPercentileAgg } from './average_or_percentile_agg';
averageOrPercentileAgg,
getMultiTermsSortOrder,
} from './average_or_percentile_agg';
import { APMConfig } from '../../../..';
import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client';
import { getGroupByTerms } from '../utils/get_groupby_terms';
import { getAllGroupByFields } from '../utils/get_all_groupby_fields';

export type TransactionDurationChartPreviewResponse = Array<{
name: string;
Expand All @@ -53,6 +56,7 @@ export async function getTransactionDurationChartPreview({
interval,
start,
end,
groupBy,
} = alertParams;
const searchAggregatedTransactions = await getSearchTransactionsEvents({
config,
Expand All @@ -63,9 +67,15 @@ export async function getTransactionDurationChartPreview({
const query = {
bool: {
filter: [
...termQuery(SERVICE_NAME, serviceName),
...termQuery(TRANSACTION_TYPE, transactionType),
...termQuery(TRANSACTION_NAME, transactionName),
...termQuery(SERVICE_NAME, serviceName, {
queryEmptyString: false,
}),
...termQuery(TRANSACTION_TYPE, transactionType, {
queryEmptyString: false,
}),
...termQuery(TRANSACTION_NAME, transactionName, {
queryEmptyString: false,
}),
...rangeQuery(start, end),
...environmentQuery(environment),
...getDocumentTypeFilterForTransactions(searchAggregatedTransactions),
Expand All @@ -77,6 +87,11 @@ export async function getTransactionDurationChartPreview({
searchAggregatedTransactions
);

const allGroupByFields = getAllGroupByFields(
ApmRuleType.TransactionDuration,
groupBy
);

const aggs = {
timeseries: {
date_histogram: {
Expand All @@ -89,23 +104,18 @@ export async function getTransactionDurationChartPreview({
},
},
aggs: {
environment: {
terms: {
field: SERVICE_ENVIRONMENT,
missing: ENVIRONMENT_NOT_DEFINED.value,
size: 10,
order: {
[aggregationType === AggregationType.Avg
? 'avgLatency'
: `pctLatency.${
aggregationType === AggregationType.P95 ? 95 : 99
}`]: 'desc',
} as Record<string, 'desc'>,
series: {
multi_terms: {
terms: [...getGroupByTerms(allGroupByFields)],
size: 3,
...getMultiTermsSortOrder(aggregationType),
},
aggs: {
...averageOrPercentileAgg({
aggregationType,
transactionDurationField,
}),
},
aggs: averageOrPercentileAgg({
aggregationType,
transactionDurationField,
}),
},
},
},
Expand All @@ -125,19 +135,19 @@ export async function getTransactionDurationChartPreview({
return [];
}

const environmentDataMap = resp.aggregations.timeseries.buckets.reduce(
const seriesDataMap = resp.aggregations.timeseries.buckets.reduce(
(acc, bucket) => {
const x = bucket.key;
bucket.environment.buckets.forEach((environmentBucket) => {
const env = environmentBucket.key as string;
bucket.series.buckets.forEach((seriesBucket) => {
const bucketKey = seriesBucket.key.join('_');
const y =
'avgLatency' in environmentBucket
? environmentBucket.avgLatency.value
: environmentBucket.pctLatency.values[0].value;
if (acc[env]) {
acc[env].push({ x, y });
'avgLatency' in seriesBucket
? seriesBucket.avgLatency.value
: seriesBucket.pctLatency.values[0].value;
if (acc[bucketKey]) {
acc[bucketKey].push({ x, y });
} else {
acc[env] = [{ x, y }];
acc[bucketKey] = [{ x, y }];
}
});

Expand All @@ -146,8 +156,8 @@ export async function getTransactionDurationChartPreview({
{} as Record<string, Array<{ x: number; y: number | null }>>
);

return Object.keys(environmentDataMap).map((env) => ({
name: getEnvironmentLabel(env),
data: environmentDataMap[env],
return Object.keys(seriesDataMap).map((key) => ({
name: key,
data: seriesDataMap[key],
}));
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import {
getMultiTermsSortOrder,
} from './average_or_percentile_agg';
import { getGroupByActionVariables } from '../utils/get_groupby_action_variables';
import { getAllGroupByFields } from '../utils/get_all_groupby_fields';

const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.TransactionDuration];

Expand Down Expand Up @@ -103,14 +104,9 @@ export function registerTransactionDurationRuleType({
minimumLicenseRequired: 'basic',
isExportable: true,
executor: async ({ params: ruleParams, services, spaceId }) => {
const predefinedGroupby = [
SERVICE_NAME,
SERVICE_ENVIRONMENT,
TRANSACTION_TYPE,
];

const allGroupbyFields = Array.from(
new Set([...predefinedGroupby, ...(ruleParams.groupBy ?? [])])
const allGroupByFields = getAllGroupByFields(
ApmRuleType.TransactionDuration,
ruleParams.groupBy
);

const config = await firstValueFrom(config$);
Expand Down Expand Up @@ -172,7 +168,7 @@ export function registerTransactionDurationRuleType({
aggs: {
series: {
multi_terms: {
terms: [...getGroupByTerms(allGroupbyFields)],
terms: [...getGroupByTerms(allGroupByFields)],
size: 1000,
...getMultiTermsSortOrder(ruleParams.aggregationType),
},
Expand Down Expand Up @@ -205,7 +201,7 @@ export function registerTransactionDurationRuleType({
for (const bucket of response.aggregations.series.buckets) {
const groupByFields = bucket.key.reduce(
(obj, bucketKey, bucketIndex) => {
obj[allGroupbyFields[bucketIndex]] = bucketKey;
obj[allGroupByFields[bucketIndex]] = bucketKey;
return obj;
},
{} as Record<string, string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { union } from 'lodash';
import { ApmRuleType } from '../../../../../common/rules/apm_rule_types';
import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
TRANSACTION_TYPE,
} from '../../../../../common/es_fields/apm';

export const getAllGroupByFields = (
ruleType: string,
groupBy: string[] | undefined = []
) => {
const predefinedGroupBy =
ruleType === ApmRuleType.TransactionDuration ||
ruleType === ApmRuleType.TransactionErrorRate
? [SERVICE_NAME, SERVICE_ENVIRONMENT, TRANSACTION_TYPE]
: ruleType === ApmRuleType.ErrorCount
? [SERVICE_NAME, SERVICE_ENVIRONMENT]
: [];

return union(predefinedGroupBy, groupBy);
};
Loading