Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Actionable Observability] - Add latency alert history chart on the Alert details page for APM #148011

Merged
merged 51 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
df3845d
WIP
fkanout Dec 22, 2022
14f0479
Add the alert triggered and the avg time to recover
fkanout Dec 23, 2022
85f9fd2
Add new hook to fetch triggered alert history
fkanout Dec 27, 2022
a5cdacd
import the new hook and use it
fkanout Dec 28, 2022
9af2eb6
Merge branch 'main' into 147932-latency-alert-history-chart
fkanout Jan 10, 2023
9e3bfd1
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jan 10, 2023
88fefb0
Merge branch 'main' into 147932-latency-alert-history-chart
fkanout Jan 10, 2023
5fef281
github issue
fkanout Jan 10, 2023
974239a
github
fkanout Jan 10, 2023
7f069c8
fix format
fkanout Jan 10, 2023
6d8c450
fix format
fkanout Jan 10, 2023
af617d4
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jan 10, 2023
9cd4977
Add annotation
fkanout Jan 11, 2023
1f29c83
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Jan 11, 2023
918fa23
Working annotation with alert number on the top of the marker
fkanout Jan 12, 2023
24e586f
Fix import
fkanout Jan 12, 2023
f759f2f
Fix style
fkanout Jan 12, 2023
2937e31
Add script to the query to calculate the avg recovered time
fkanout Jan 12, 2023
efc5ec0
Add date to the alert annotation
fkanout Jan 13, 2023
727aeae
DEL Logs
fkanout Jan 13, 2023
30fc534
Add key to annotation
fkanout Jan 13, 2023
88d7a2f
increase package limit
fkanout Jan 13, 2023
1f40dab
fix
fkanout Jan 13, 2023
24aa28d
fix lint
fkanout Jan 13, 2023
731a954
fix lint
fkanout Jan 13, 2023
e149baf
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jan 13, 2023
973b842
Merge branch 'main' into 147932-latency-alert-history-chart
fkanout Jan 13, 2023
a915154
Update limit
fkanout Jan 13, 2023
a951d02
Update limit
fkanout Jan 16, 2023
3c81c91
code review - use transaction type from the alert
fkanout Jan 16, 2023
d6340ef
use - when there is no value
fkanout Jan 16, 2023
d88e530
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Jan 16, 2023
6460159
fix formatting
fkanout Jan 16, 2023
09250d3
Updated bucketAggsSchemas to accept filter inside the aggs
fkanout Jan 16, 2023
7765438
Merge branch 'main' into 147932-latency-alert-history-chart
fkanout Jan 16, 2023
b24a081
update var name
fkanout Jan 17, 2023
d227c53
update limit
fkanout Jan 17, 2023
9989f48
fix all errors and move the hook to apm
fkanout Jan 17, 2023
8c91323
add radix
fkanout Jan 17, 2023
cb63134
fix lint
fkanout Jan 17, 2023
63712af
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jan 17, 2023
d95a8f8
use duration formatter from observability
fkanout Jan 17, 2023
eefaa4c
Merge branch 'main' into 147932-latency-alert-history-chart
fkanout Jan 17, 2023
2412cf4
fix service name typo
fkanout Jan 17, 2023
fbe3293
fix type and query
fkanout Jan 18, 2023
c1a5647
Merge branch 'main' into 147932-latency-alert-history-chart
fkanout Jan 18, 2023
ef6d66d
Merge branch 'main' into 147932-latency-alert-history-chart
fkanout Jan 19, 2023
b8d79d9
fix failing test
fkanout Jan 19, 2023
ef7431d
remove unused import
fkanout Jan 19, 2023
f852882
unified the red color
fkanout Jan 19, 2023
67ec170
simplify validation after a lot of failures
XavierM Jan 19, 2023
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 @@ -15,6 +15,7 @@ import { EuiIconTip } from '@elastic/eui';
import {
ALERT_DURATION,
ALERT_END,
ALERT_RULE_UUID,
ALERT_EVALUATION_THRESHOLD,
ALERT_RULE_TYPE_ID,
} from '@kbn/rule-data-utils';
Expand Down Expand Up @@ -48,6 +49,7 @@ import {
import { getAggsTypeFromRule, isLatencyThresholdRuleType } from './helpers';
import { filterNil } from '../../../shared/charts/latency_chart';
import { errorRateI18n } from '../../../shared/charts/failed_transaction_rate_chart';
import { LatencyAlertsHistoryChart } from './latency_alerts_history_chart';
import {
AlertActiveRect,
AlertAnnotation,
Expand Down Expand Up @@ -443,6 +445,18 @@ export function AlertDetailsAppSection({
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
fkanout marked this conversation as resolved.
Show resolved Hide resolved
<LatencyAlertsHistoryChart
ruleId={alert.fields[ALERT_RULE_UUID]}
serviceName={serviceName}
start={start}
end={end}
transactionType={transactionType}
latencyAggregationType={latencyAggregationType}
environment={environment}
timeZone={timeZone}
/>
</EuiFlexItem>
</ChartPointerEventContextProvider>
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useMemo } from 'react';
import moment from 'moment';
import { EuiPanel, EuiSpacer } from '@elastic/eui';
import { EuiFlexGroup } from '@elastic/eui';
import { EuiFlexItem } from '@elastic/eui';
import { EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EuiText } from '@elastic/eui';
import { useFetchTriggeredAlertsHistory } from '@kbn/observability-plugin/public';
import { getDurationFormatter } from '../../../../../common/utils/formatters';
import { getLatencyChartSelector } from '../../../../selectors/latency_chart_selectors';
import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { TimeseriesChart } from '../../../shared/charts/timeseries_chart';
import { filterNil } from '../../../shared/charts/latency_chart';
import {
getMaxY,
getResponseTimeTickFormatter,
} from '../../../shared/charts/transaction_charts/helper';

interface LatencyAlertsHistoryChartProps {
serviceName: string;
start: string;
end: string;
transactionType?: string;
latencyAggregationType: LatencyAggregationType;
environment: string;
timeZone: string;
ruleId: string;
}
export function LatencyAlertsHistoryChart({
serviceName,
start,
end,
transactionType,
latencyAggregationType,
environment,
timeZone,
ruleId,
}: LatencyAlertsHistoryChartProps) {
const { data, status } = useFetcher(
(callApmApi) => {
if (
serviceName &&
start &&
end &&
transactionType &&
latencyAggregationType
) {
return callApmApi(
`GET /internal/apm/services/{serviceName}/transactions/charts/latency`,
{
params: {
path: { serviceName },
query: {
environment,
kuery: '',
start: moment().subtract(30, 'days').toISOString(),
end: moment().toISOString(),
transactionType,
transactionName: undefined,
latencyAggregationType,
},
},
}
);
}
},
[
end,
environment,
latencyAggregationType,
serviceName,
start,
transactionType,
]
);
const memoizedData = useMemo(
() =>
getLatencyChartSelector({
latencyChart: data,
latencyAggregationType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't you want to rerun the function when latencyAggregationType changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CoenWarmer For this part, I'm just following how APM has implemented it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, maybe @sqren knows?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previousPeriodLabel: '',
}),
// It should only update when the data has changed
// eslint-disable-next-line react-hooks/exhaustive-deps
[data]
);
const { currentPeriod, previousPeriod } = memoizedData;
const timeseriesLatency = [currentPeriod, previousPeriod].filter(filterNil);
const latencyMaxY = getMaxY(timeseriesLatency);
const latencyFormatter = getDurationFormatter(latencyMaxY);
const { triggeredAlertsData } = useFetchTriggeredAlertsHistory({
features: 'apm',
ruleId,
});
return (
<EuiPanel hasBorder={true}>
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h2>
{i18n.translate('xpack.apm.latencyChartHistory.chartTitle', {
defaultMessage: 'Kibana latency alerts history',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.apm.latencyChartHistory.last30days', {
defaultMessage: 'Last 30 days',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />

<EuiFlexGroup gutterSize="l">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText color="danger">
<EuiTitle size="s">
<h3>{triggeredAlertsData?.totalTriggeredAlerts}</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate(
'xpack.apm.latencyChartHistory.alertsTriggered',
{
defaultMessage: 'Alerts triggered',
}
)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText>
<EuiTitle size="s">
<h3>45 minutes</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate(
'xpack.apm.latencyChartHistory.avgTimeToRecover',
{
defaultMessage: 'Avg time to recover',
}
)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
<EuiSpacer size="s" />

<TimeseriesChart
id="latencyChart"
height={200}
comparisonEnabled={false}
offset={''}
fetchStatus={status}
timeseries={timeseriesLatency}
yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)}
timeZone={timeZone}
/>
</EuiPanel>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* 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 { AsApiContract } from '@kbn/actions-plugin/common';
import { HttpSetup } from '@kbn/core/public';
import { ALERT_RULE_UUID, ALERT_START, ALERT_TIME_RANGE } from '@kbn/rule-data-utils';
import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useKibana } from '../utils/kibana_react';

interface UseFetchTriggeredAlertsHistoryProps {
features: string;
ruleId: string;
}
interface FetchTriggeredAlertsHistory {
totalTriggeredAlerts: number;
histogramTriggeredAlerts: Array<{
key_as_string: string;
key: number;
doc_count: number;
}>;
error?: string;
}

interface TriggeredAlertsHistory {
isLoadingTriggeredAlertHistory: boolean;
errorTriggeredAlertHistory?: string;
triggeredAlertsData?: FetchTriggeredAlertsHistory;
}
export function useFetchTriggeredAlertsHistory({
features,
ruleId,
}: UseFetchTriggeredAlertsHistoryProps) {
const { http } = useKibana().services;
const [triggeredAlertsHistory, setTriggeredAlertsHistory] = useState<TriggeredAlertsHistory>({
isLoadingTriggeredAlertHistory: true,
});
const isCancelledRef = useRef(false);
const abortCtrlRef = useRef(new AbortController());
const loadRuleAlertsAgg = useCallback(async () => {
isCancelledRef.current = false;
abortCtrlRef.current.abort();
abortCtrlRef.current = new AbortController();

try {
if (!features) return;
const { index } = await fetchIndexNameAPI({
http,
features,
});

const { totalTriggeredAlerts, histogramTriggeredAlerts, error } =
await fetchTriggeredAlertsHistory({
http,
index,
ruleId,
signal: abortCtrlRef.current.signal,
});

if (error) throw error;
if (!isCancelledRef.current) {
setTriggeredAlertsHistory((oldState: TriggeredAlertsHistory) => ({
...oldState,
triggeredAlertsData: { totalTriggeredAlerts, histogramTriggeredAlerts },
isLoadingRuleAlertsAggs: false,
}));
}
} catch (error) {
if (!isCancelledRef.current) {
if (error.name !== 'AbortError') {
setTriggeredAlertsHistory((oldState: TriggeredAlertsHistory) => ({
...oldState,
isLoadingRuleAlertsAggs: false,
errorTriggeredAlertHistory: error,
triggeredAlertsData: undefined,
}));
}
}
}
}, [features, http, ruleId]);
useEffect(() => {
loadRuleAlertsAgg();
}, [loadRuleAlertsAgg]);

return triggeredAlertsHistory;
}

interface IndexName {
index: string;
}

export async function fetchIndexNameAPI({
http,
features,
}: {
http: HttpSetup;
features: string;
}): Promise<IndexName> {
const res = await http.get<{ index_name: string[] }>(`${BASE_RAC_ALERTS_API_PATH}/index`, {
query: { features },
});
return {
index: res.index_name[0],
};
}

export async function fetchTriggeredAlertsHistory({
http,
index,
ruleId,
signal,
}: {
http: HttpSetup;
index: string;
ruleId: string;
signal: AbortSignal;
}): Promise<FetchTriggeredAlertsHistory> {
try {
const res = await http.post<AsApiContract<any>>(`${BASE_RAC_ALERTS_API_PATH}/find`, {
signal,
body: JSON.stringify({
index,
size: 0,
query: {
bool: {
must: [
{
term: {
[ALERT_RULE_UUID]: ruleId,
},
},
{
range: {
[ALERT_TIME_RANGE]: {
gte: 'now-30d',
lt: 'now',
},
},
},
],
},
},
aggs: {
histogramTriggeredAlerts: {
date_histogram: {
field: ALERT_START,
fixed_interval: '1d',
extended_bounds: {
min: 'now-30d',
max: 'now',
},
},
},
},
}),
});
const totalTriggeredAlerts = res?.hits.total.value;
const histogramTriggeredAlerts = res?.aggregations?.histogramTriggeredAlerts.buckets;
return {
totalTriggeredAlerts,
histogramTriggeredAlerts,
};
} catch (error) {
console.error(error);
return {
error,
totalTriggeredAlerts: 0,
histogramTriggeredAlerts: [],
};
}
}
1 change: 1 addition & 0 deletions x-pack/plugins/observability/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,4 @@ export { ExploratoryViewContextProvider } from './components/shared/exploratory_
export { fromQuery, toQuery } from './utils/url';

export type { NavigationSection } from './services/navigation_registry';
export { useFetchTriggeredAlertsHistory } from './hooks/use_fetch_triggered_alert_history';
Loading