Skip to content

Commit

Permalink
[APM] ML anomaly detection integration: Displaying anomaly job result…
Browse files Browse the repository at this point in the history
…s in the Transaction duration chart is not as intended (#84415) (#84524)

* adding anomalies to transaction duration chart

* removing extra :

* fixing test

* addressing pr comments

* addressing pr comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
cauemarcondes and kibanamachine authored Dec 1, 2020
1 parent c511477 commit 941b3e7
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
niceTimeFormatter,
Placement,
Position,
RectAnnotation,
ScaleType,
Settings,
YDomainRange,
Expand All @@ -27,12 +28,13 @@ import React, { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { useChartTheme } from '../../../../../observability/public';
import { asAbsoluteDateTime } from '../../../../common/utils/formatters';
import { TimeSeries } from '../../../../typings/timeseries';
import { RectCoordinate, TimeSeries } from '../../../../typings/timeseries';
import { FETCH_STATUS } from '../../../hooks/useFetcher';
import { useTheme } from '../../../hooks/useTheme';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useAnnotations } from '../../../hooks/use_annotations';
import { useChartPointerEvent } from '../../../hooks/use_chart_pointer_event';
import { AnomalySeries } from '../../../selectors/chart_selectors';
import { unit } from '../../../style/variables';
import { ChartContainer } from './chart_container';
import { onBrushEnd } from './helper/helper';
Expand All @@ -53,6 +55,7 @@ interface Props {
yTickFormat?: (y: number) => string;
showAnnotations?: boolean;
yDomain?: YDomainRange;
anomalySeries?: AnomalySeries;
}

export function TimeseriesChart({
Expand All @@ -65,6 +68,7 @@ export function TimeseriesChart({
yTickFormat,
showAnnotations = true,
yDomain,
anomalySeries,
}: Props) {
const history = useHistory();
const chartRef = React.createRef<Chart>();
Expand Down Expand Up @@ -102,7 +106,12 @@ export function TimeseriesChart({
<Chart ref={chartRef} id={id}>
<Settings
onBrushEnd={({ x }) => onBrushEnd({ x, history })}
theme={chartTheme}
theme={{
...chartTheme,
areaSeriesStyle: {
line: { visible: false },
},
}}
onPointerUpdate={setPointerEvent}
externalPointerEvents={{
tooltip: { visible: true, placement: Placement.Bottom },
Expand Down Expand Up @@ -169,6 +178,36 @@ export function TimeseriesChart({
/>
);
})}

{anomalySeries?.bounderies && (
<AreaSeries
key={anomalySeries.bounderies.title}
id={anomalySeries.bounderies.title}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
y0Accessors={['y0']}
data={anomalySeries.bounderies.data}
color={anomalySeries.bounderies.color}
curve={CurveType.CURVE_MONOTONE_X}
hideInLegend
filterSeriesInTooltip={() => false}
/>
)}

{anomalySeries?.scores && (
<RectAnnotation
key={anomalySeries.scores.title}
id="score_anomalies"
dataValues={(anomalySeries.scores.data as RectCoordinate[]).map(
({ x0, x: x1 }) => ({
coordinates: { x0, x1 },
})
)}
style={{ fill: anomalySeries.scores.color }}
/>
)}
</Chart>
</ChartContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function TransactionCharts({
}: TransactionChartProps) {
const { transactionType } = urlParams;

const { responseTimeSeries, tpmSeries } = charts;
const { responseTimeSeries, tpmSeries, anomalySeries } = charts;

const { formatter, toggleSerie } = useFormatter(responseTimeSeries);

Expand Down Expand Up @@ -79,6 +79,7 @@ export function TransactionCharts({
id="transactionDuration"
timeseries={responseTimeSeries || []}
yLabelFormat={getResponseTimeTickFormatter(formatter)}
anomalySeries={anomalySeries}
onToggleLegend={(serie) => {
if (serie) {
toggleSerie(serie);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function MLHeader({ hasValidMlLicense, mlJobId }: Props) {
transactionType={transactionType}
>
{i18n.translate('xpack.apm.metrics.transactionChart.viewJob', {
defaultMessage: 'View Job:',
defaultMessage: 'View Job',
})}
</MLJobLink>
</ShiftedEuiText>
Expand Down
16 changes: 4 additions & 12 deletions x-pack/plugins/apm/public/selectors/chart_selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@ describe('chart selectors', () => {
it('should return anomalyScoreSeries', () => {
const data = [{ x0: 0, x: 10 }];
expect(getAnomalyScoreSeries(data)).toEqual({
areaColor: 'rgba(231,102,76,0.1)',
color: 'none',
color: '#e7664c',
data: [{ x0: 0, x: 10 }],
hideLegend: true,
hideTooltipValue: true,
title: 'Anomaly score',
type: 'areaMaxHeight',
type: 'rectAnnotation',
});
});
});
Expand All @@ -55,9 +52,7 @@ describe('chart selectors', () => {
};

it('should produce correct series', () => {
expect(
getResponseTimeSeries({ apmTimeseries, anomalyTimeseries: undefined })
).toEqual([
expect(getResponseTimeSeries({ apmTimeseries })).toEqual([
{
color: '#6092c0',
data: [
Expand Down Expand Up @@ -92,10 +87,7 @@ describe('chart selectors', () => {
});

it('should return 3 series', () => {
expect(
getResponseTimeSeries({ apmTimeseries, anomalyTimeseries: undefined })
.length
).toBe(3);
expect(getResponseTimeSeries({ apmTimeseries }).length).toBe(3);
});
});

Expand Down
51 changes: 30 additions & 21 deletions x-pack/plugins/apm/public/selectors/chart_selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@ export interface ITpmBucket {
color: string;
}

export interface AnomalySeries {
scores: TimeSeries;
bounderies: TimeSeries;
}

export interface ITransactionChartData {
tpmSeries?: ITpmBucket[];
responseTimeSeries?: TimeSeries[];
mlJobId: string | undefined;
anomalySeries?: AnomalySeries;
}

const INITIAL_DATA: Partial<TimeSeriesAPIResponse> = {
Expand All @@ -58,16 +64,35 @@ export function getTransactionCharts(

transactionCharts.responseTimeSeries = getResponseTimeSeries({
apmTimeseries,
});

transactionCharts.anomalySeries = getResponseTimeAnnomalySeries({
anomalyTimeseries,
});
}
return transactionCharts;
}

function getResponseTimeAnnomalySeries({
anomalyTimeseries,
}: {
anomalyTimeseries: TimeSeriesAPIResponse['anomalyTimeseries'];
}): AnomalySeries | undefined {
if (anomalyTimeseries) {
return {
bounderies: getAnomalyBoundariesSeries(
anomalyTimeseries.anomalyBoundaries
),
scores: getAnomalyScoreSeries(anomalyTimeseries.anomalyScore),
};
}
}

export function getResponseTimeSeries({
apmTimeseries,
anomalyTimeseries,
}: TimeSeriesAPIResponse) {
}: {
apmTimeseries: TimeSeriesAPIResponse['apmTimeseries'];
}) {
const { overallAvgDuration } = apmTimeseries;
const { avg, p95, p99 } = apmTimeseries.responseTimes;

Expand Down Expand Up @@ -107,16 +132,6 @@ export function getResponseTimeSeries({
},
];

if (anomalyTimeseries) {
// insert after Avg. series
series.splice(
1,
0,
getAnomalyBoundariesSeries(anomalyTimeseries.anomalyBoundaries),
getAnomalyScoreSeries(anomalyTimeseries.anomalyScore)
);
}

return series;
}

Expand All @@ -125,12 +140,9 @@ export function getAnomalyScoreSeries(data: RectCoordinate[]) {
title: i18n.translate('xpack.apm.transactions.chart.anomalyScoreLabel', {
defaultMessage: 'Anomaly score',
}),
hideLegend: true,
hideTooltipValue: true,
data,
type: 'areaMaxHeight',
color: 'none',
areaColor: rgba(theme.euiColorVis9, 0.1),
type: 'rectAnnotation',
color: theme.euiColorVis9,
};
}

Expand All @@ -142,12 +154,9 @@ function getAnomalyBoundariesSeries(data: Coordinate[]) {
defaultMessage: 'Anomaly Boundaries',
}
),
hideLegend: true,
hideTooltipValue: true,
data,
type: 'area',
color: 'none',
areaColor: rgba(theme.euiColorVis1, 0.1),
color: rgba(theme.euiColorVis1, 0.5),
};
}

Expand Down

0 comments on commit 941b3e7

Please sign in to comment.