Skip to content

Commit

Permalink
filtering series on server
Browse files Browse the repository at this point in the history
  • Loading branch information
benakansara committed Jun 15, 2023
1 parent d23a7f4 commit f40f335
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 65 deletions.
6 changes: 6 additions & 0 deletions x-pack/plugins/apm/common/rules/apm_rule_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export interface PreviewChartResponseItem {
data: Array<{ x: number; y: number | null }>;
}

export interface BarSeriesData {
x: number;
y: number | null;
group: string | undefined;
}

export const THRESHOLD_MET_GROUP_ID = 'threshold_met';
export type ThresholdMetActionGroupId = typeof THRESHOLD_MET_GROUP_ID;
const THRESHOLD_MET_GROUP: ActionGroup<ThresholdMetActionGroupId> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,22 +173,26 @@ export function ErrorCountRuleType(props: Props) {
/>,
];

const errorCountChartPreview = data?.errorCountChartPreview ?? [];

const hasData = errorCountChartPreview.length > 0;
const errorCountChartPreview = data?.errorCountChartPreview;
const series = errorCountChartPreview?.series ?? [];
const hasData = series.length > 0;
const displayedGroups = errorCountChartPreview?.displayedGroups ?? 0;
const totalGroups = errorCountChartPreview?.totalGroups ?? 0;

const chartPreview = isPending(status) ? (
<LoadingState />
) : !hasData ? (
<NoDataState />
) : status === FETCH_STATUS.SUCCESS ? (
<ChartPreview
series={errorCountChartPreview}
series={series}
threshold={params.threshold}
yTickFormat={asInteger}
uiSettings={services.uiSettings}
timeSize={params.windowSize}
timeUnit={params.windowUnit}
displayedGroups={displayedGroups}
totalGroups={totalGroups}
/>
) : (
<ErrorState />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,22 +180,26 @@ export function TransactionErrorRateRuleType(props: Props) {
/>,
];

const errorRateChartPreview = data?.errorRateChartPreview ?? [];

const hasData = errorRateChartPreview.length > 0;
const errorRateChartPreview = data?.errorRateChartPreview;
const series = errorRateChartPreview?.series ?? [];
const hasData = series.length > 0;
const displayedGroups = errorRateChartPreview?.displayedGroups ?? 0;
const totalGroups = errorRateChartPreview?.totalGroups ?? 0;

const chartPreview = isPending(status) ? (
<LoadingState />
) : !hasData ? (
<NoDataState />
) : status === FETCH_STATUS.SUCCESS ? (
<ChartPreview
series={errorRateChartPreview}
series={series}
yTickFormat={(d: number | null) => asPercent(d, 100)}
threshold={params.threshold}
uiSettings={services.uiSettings}
timeSize={params.windowSize}
timeUnit={params.windowUnit}
displayedGroups={displayedGroups}
totalGroups={totalGroups}
/>
) : (
<ErrorState />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { niceTimeFormatter } from '@elastic/charts';
import { min as getMin, max as getMax } from 'lodash';
import { Coordinate } from '../../../../../typings/timeseries';
import { BarSeriesData } from '../../../../../common/rules/apm_rule_types';

export const TIME_LABELS = {
s: i18n.translate('xpack.apm.alerts.timeLabels.seconds', {
Expand Down Expand Up @@ -40,17 +40,13 @@ export const useDateFormatter = (xMin?: number, xMax?: number) => {
return dateFormatter;
};

export const getDomain = (
series: Array<{ name?: string; data: Coordinate[] }>
) => {
export const getDomain = (series: BarSeriesData[]) => {
let min: number | null = null;
let max: number | null = null;
const valuesByTimestamp = series.reduce<{ [timestamp: number]: number[] }>(
(acc, serie) => {
serie.data.forEach((point) => {
const valuesForTimestamp = acc[point.x] || [];
acc[point.x] = [...valuesForTimestamp, point.y ?? 0];
});
const valuesForTimestamp = acc[serie.x] || [];
acc[serie.x] = [...valuesForTimestamp, serie.y ?? 0];
return acc;
},
{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ import {
TooltipProps,
} from '@elastic/charts';
import { EuiSpacer } from '@elastic/eui';
import React, { useMemo } from 'react';
import React from 'react';
import { IUiSettingsClient } from '@kbn/core/public';
import { TimeUnitChar } from '@kbn/observability-plugin/common';
import { UI_SETTINGS } from '@kbn/data-plugin/public';
import moment from 'moment';
import { Maybe } from '../../../../../typings/common';
import { Coordinate } from '../../../../../typings/timeseries';
import { useTheme } from '../../../../hooks/use_theme';
import { getTimeZone } from '../../../shared/charts/helper/timezone';
import {
Expand All @@ -36,14 +34,17 @@ import {
useDateFormatter,
} from './chart_preview_helper';
import { BUCKET_SIZE } from '../../utils/helper';
import { BarSeriesData } from '../../../../../common/rules/apm_rule_types';

interface ChartPreviewProps {
yTickFormat?: TickFormatter;
threshold: number;
uiSettings?: IUiSettingsClient;
series: Array<{ name?: string; data: Coordinate[] }>;
series: BarSeriesData[];
timeSize?: number;
timeUnit?: TimeUnitChar;
displayedGroups: number;
totalGroups: number;
}

export function ChartPreview({
Expand All @@ -53,6 +54,8 @@ export function ChartPreview({
series,
timeSize = 5,
timeUnit = 'm',
displayedGroups,
totalGroups,
}: ChartPreviewProps) {
const theme = useTheme();
const thresholdOpacity = 0.3;
Expand All @@ -67,7 +70,6 @@ export function ChartPreview({
opacity: thresholdOpacity,
};

const NUM_SERIES = 5;
const DEFAULT_DATE_FORMAT = 'Y-MM-DD HH:mm:ss';

const tooltipProps: TooltipProps = {
Expand All @@ -79,47 +81,21 @@ export function ChartPreview({
},
};

const filteredSeries = useMemo(() => {
const sortedSeries = series.sort((a, b) => {
const aMax = Math.max(...a.data.map((point) => point.y as number));
const bMax = Math.max(...b.data.map((point) => point.y as number));
return bMax - aMax;
});
return sortedSeries.slice(0, NUM_SERIES);
}, [series]);

const barSeries = useMemo(() => {
return filteredSeries.reduce<
Array<{ x: number; y: Maybe<number>; groupBy: string | undefined }>
>((acc, serie) => {
const barPoints = serie.data.reduce<
Array<{ x: number; y: Maybe<number>; groupBy: string | undefined }>
>((pointAcc, point) => {
return [...pointAcc, { ...point, groupBy: serie.name }];
}, []);
return [...acc, ...barPoints];
}, []);
}, [filteredSeries]);

const timeZone = getTimeZone(uiSettings);

const legendSize =
filteredSeries.length > 1
? Math.ceil(filteredSeries.length / 2) * 30
: filteredSeries.length * 35;
displayedGroups > 1
? Math.ceil(displayedGroups / 2) * 30
: displayedGroups * 35;

const chartSize = 150;

const { yMin, yMax, xMin, xMax } = getDomain(filteredSeries);
const { yMin, yMax, xMin, xMax } = getDomain(series);
const chartDomain = {
max: Math.max(yMax, threshold) * 1.1, // Add 10% headroom.
min: Math.min(yMin, threshold),
min: Math.min(yMin, threshold) * 0.9, // Allow some padding so the threshold annotation has better visibility
};

if (chartDomain.min === threshold) {
chartDomain.min = chartDomain.min * 0.9; // Allow some padding so the threshold annotation has better visibility
}

const dateFormatter = useDateFormatter(xMin, xMax);

const lookback = timeSize * BUCKET_SIZE;
Expand Down Expand Up @@ -183,8 +159,8 @@ export function ChartPreview({
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
splitSeriesAccessors={['groupBy']}
data={barSeries}
splitSeriesAccessors={['group']}
data={series}
barSeriesStyle={{
rectBorder: {
strokeWidth: 1,
Expand All @@ -197,12 +173,12 @@ export function ChartPreview({
timeZone={timeZone}
/>
</Chart>
{filteredSeries.length > 0 && (
{series.length > 0 && (
<TimeLabelForData
lookback={lookback}
timeLabel={timeLabel}
displayedGroups={filteredSeries.length}
totalGroups={series.length}
displayedGroups={displayedGroups}
totalGroups={totalGroups}
/>
)}
</>
Expand Down
9 changes: 7 additions & 2 deletions x-pack/plugins/apm/server/routes/alerts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { environmentRt, rangeRt } from '../default_api_types';
import {
AggregationType,
PreviewChartResponseItem,
BarSeriesData,
} from '../../../common/rules/apm_rule_types';
import { getApmEventClient } from '../../lib/helpers/get_apm_event_client';

Expand All @@ -42,7 +42,12 @@ const alertParamsRt = t.intersection([
}),
]);

export type PreviewChartResponse = PreviewChartResponseItem[];
export interface PreviewChartResponse {
series: BarSeriesData[];
displayedGroups: number;
totalGroups: number;
}

export type AlertParams = t.TypeOf<typeof alertParamsRt>;

const transactionErrorRateChartPreview = createApmServerRoute({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_
import { getGroupByTerms } from '../utils/get_groupby_terms';
import { getAllGroupByFields } from '../../../../../common/rules/get_all_groupby_fields';
import { ApmRuleType } from '../../../../../common/rules/apm_rule_types';
import {
BarSeriesDataMap,
getFilteredBarSeries,
} from '../utils/get_series_for_preview_chart';

export async function getTransactionErrorCountChartPreview({
apmEventClient,
Expand Down Expand Up @@ -90,7 +94,7 @@ export async function getTransactionErrorCountChartPreview({
);

if (!resp.aggregations) {
return [];
return { series: [], displayedGroups: 0, totalGroups: 0 };
}

const seriesDataMap = resp.aggregations.timeseries.buckets.reduce(
Expand All @@ -109,11 +113,13 @@ export async function getTransactionErrorCountChartPreview({

return acc;
},
{} as Record<string, Array<{ x: number; y: number | null }>>
{} as BarSeriesDataMap
);

return Object.keys(seriesDataMap).map((key) => ({
const series = Object.keys(seriesDataMap).map((key) => ({
name: key,
data: seriesDataMap[key],
}));

return getFilteredBarSeries(series);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_
import { EventOutcome } from '../../../../../common/event_outcome';
import { getGroupByTerms } from '../utils/get_groupby_terms';
import { getAllGroupByFields } from '../../../../../common/rules/get_all_groupby_fields';
import {
BarSeriesDataMap,
getFilteredBarSeries,
} from '../utils/get_series_for_preview_chart';

export async function getTransactionErrorRateChartPreview({
config,
Expand Down Expand Up @@ -126,7 +130,7 @@ export async function getTransactionErrorRateChartPreview({
);

if (!resp.aggregations) {
return [];
return { series: [], displayedGroups: 0, totalGroups: 0 };
}

const seriesDataMap = resp.aggregations.timeseries.buckets.reduce(
Expand All @@ -145,13 +149,15 @@ export async function getTransactionErrorRateChartPreview({

return acc;
},
{} as Record<string, Array<{ x: number; y: number | null }>>
{} as BarSeriesDataMap
);

return Object.keys(seriesDataMap).map((key) => ({
const series = Object.keys(seriesDataMap).map((key) => ({
name: key,
data: seriesDataMap[key],
}));

return getFilteredBarSeries(series);
}

const calculateErrorRate = (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 { BarSeriesData } from '../../../../../common/rules/apm_rule_types';

export type BarSeriesDataMap = Record<
string,
Array<{ x: number; y: number | null }>
>;

type BarSeries = Array<{
name: string;
data: Array<{
x: number;
y: number | null;
}>;
}>;

const NUM_SERIES = 5;

export const getFilteredBarSeries = (barSeries: BarSeries) => {
const sortedSeries = barSeries.sort((a, b) => {
const aMax = Math.max(...a.data.map((point) => point.y as number));
const bMax = Math.max(...b.data.map((point) => point.y as number));
return bMax - aMax;
});

const filteredSeries = sortedSeries.slice(0, NUM_SERIES);

const series = filteredSeries.reduce<BarSeriesData[]>((acc, serie) => {
const barPoints = serie.data.reduce<BarSeriesData[]>((pointAcc, point) => {
return [...pointAcc, { ...point, group: serie.name }];
}, []);
return [...acc, ...barPoints];
}, []);

return {
series,
displayedGroups: filteredSeries.length,
totalGroups: barSeries.length,
};
};

0 comments on commit f40f335

Please sign in to comment.