Skip to content

Commit

Permalink
[RAC] Adds stats about rules to Alerts page (elastic#119750)
Browse files Browse the repository at this point in the history
* Adds EuiStat to alerts page template

* Adds functional tests for stat counters

* Whoops - no exclusive tests

* Uses triggers_actions_ui API

* Toaster on error

* Early review feedback

* Uses new rule aggregation API

* Makes tests pass

* Adds logging

* Whoops forgot an await

* Limits triggers actions UI exports to loadAlertAggregations

* Creates rules via API for functional tests

* Extracts common methods to create rules via API

* Removes unnecessary template strings

* Cleanup

* Reuses common dummy alert plugin fixture

* Removes unnecessary config

* Removes unnecessary config
  • Loading branch information
claudiopro authored Dec 2, 2021
1 parent f3ea125 commit 964f092
Show file tree
Hide file tree
Showing 7 changed files with 432 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
* 2.0.
*/

import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui';

import { IndexPatternBase } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import React, { useCallback, useRef, useState, useEffect } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { AlertStatus } from '@kbn/rule-data-utils/alerts_as_data_status';
import { ALERT_STATUS } from '@kbn/rule-data-utils/technical_field_names';

import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
import { loadAlertAggregations as loadRuleAggregations } from '../../../../../../../plugins/triggers_actions_ui/public';
import { AlertStatusFilterButton } from '../../../../../common/typings';
import { ParsedTechnicalFields } from '../../../../../../rule_registry/common/parse_technical_fields';
import { ExperimentalBadge } from '../../../../components/shared/experimental_badge';
Expand All @@ -34,13 +36,25 @@ import {
import './styles.scss';
import { AlertsStatusFilter, AlertsDisclaimer, AlertsSearchBar } from '../../components';

interface RuleStatsState {
total: number;
disabled: number;
muted: number;
error: number;
}
export interface TopAlert {
fields: ParsedTechnicalFields;
start: number;
reason: string;
link?: string;
active: boolean;
}

const Divider = euiStyled.div`
border-right: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
height: 100%;
`;

const regExpEscape = (str: string) => str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
const NO_INDEX_NAMES: string[] = [];
const NO_INDEX_PATTERNS: IndexPatternBase[] = [];
Expand All @@ -60,6 +74,17 @@ function AlertsPage() {
const timefilterService = useTimefilterService();
const { rangeFrom, setRangeFrom, rangeTo, setRangeTo, kuery, setKuery, workflowStatus } =
useAlertsPageStateContainer();
const {
http,
notifications: { toasts },
} = core;
const [ruleStatsLoading, setRuleStatsLoading] = useState<boolean>(false);
const [ruleStats, setRuleStats] = useState<RuleStatsState>({
total: 0,
disabled: 0,
muted: 0,
error: 0,
});

useEffect(() => {
syncAlertStatusFilterStatus(kuery as string);
Expand All @@ -73,6 +98,48 @@ function AlertsPage() {
},
]);

async function loadRuleStats() {
setRuleStatsLoading(true);
try {
const response = await loadRuleAggregations({
http,
});
// Note that the API uses the semantics of 'alerts' instead of 'rules'
const { alertExecutionStatus, ruleMutedStatus, ruleEnabledStatus } = response;
if (alertExecutionStatus && ruleMutedStatus && ruleEnabledStatus) {
const total = Object.entries(alertExecutionStatus).reduce((acc, [key, value]) => {
if (key !== 'error') {
acc = acc + value;
}
return acc;
}, 0);
const { error } = alertExecutionStatus;
const { muted } = ruleMutedStatus;
const { disabled } = ruleEnabledStatus;
setRuleStats({
...ruleStats,
total,
disabled,
muted,
error,
});
}
setRuleStatsLoading(false);
} catch (_e) {
toasts.addDanger({
title: i18n.translate('xpack.observability.alerts.ruleStats.loadError', {
defaultMessage: 'Unable to load rule stats',
}),
});
setRuleStatsLoading(false);
}
}

useEffect(() => {
loadRuleStats();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// In a future milestone we'll have a page dedicated to rule management in
// observability. For now link to the settings page.
const manageRulesHref = prepend('/app/management/insightsAndAlerting/triggersActions/alerts');
Expand Down Expand Up @@ -198,12 +265,53 @@ function AlertsPage() {
</>
),
rightSideItems: [
<EuiStat
title={ruleStats.total}
description={i18n.translate('xpack.observability.alerts.ruleStats.ruleCount', {
defaultMessage: 'Rule count',
})}
color="primary"
titleSize="xs"
isLoading={ruleStatsLoading}
data-test-subj="statRuleCount"
/>,
<EuiStat
title={ruleStats.disabled}
description={i18n.translate('xpack.observability.alerts.ruleStats.disabled', {
defaultMessage: 'Disabled',
})}
color="primary"
titleSize="xs"
isLoading={ruleStatsLoading}
data-test-subj="statDisabled"
/>,
<EuiStat
title={ruleStats.muted}
description={i18n.translate('xpack.observability.alerts.ruleStats.muted', {
defaultMessage: 'Muted',
})}
color="primary"
titleSize="xs"
isLoading={ruleStatsLoading}
data-test-subj="statMuted"
/>,
<EuiStat
title={ruleStats.error}
description={i18n.translate('xpack.observability.alerts.ruleStats.errors', {
defaultMessage: 'Errors',
})}
color="primary"
titleSize="xs"
isLoading={ruleStatsLoading}
data-test-subj="statErrors"
/>,
<Divider />,
<EuiButtonEmpty href={manageRulesHref}>
{i18n.translate('xpack.observability.alerts.manageRulesButtonLabel', {
defaultMessage: 'Manage Rules',
})}
</EuiButtonEmpty>,
],
].reverse(),
}}
>
<EuiFlexGroup direction="column" gutterSize="s">
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/triggers_actions_ui/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function plugin() {

export { Plugin };
export * from './plugin';
export { loadAlertAggregations } from './application/lib/alert_api/aggregate';

export { loadActionTypes } from './application/lib/action_connector_api/connector_types';

Expand Down
11 changes: 11 additions & 0 deletions x-pack/test/functional/services/observability/alerts/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import expect from '@kbn/expect';
import { chunk } from 'lodash';
import {
ALERT_STATUS_ACTIVE,
Expand Down Expand Up @@ -270,6 +271,15 @@ export function ObservabilityAlertsCommonProvider({
return actionsOverflowButtons[index] || null;
};

const getAlertStatValue = async (testSubj: string) => {
const stat = await testSubjects.find(testSubj);
const title = await stat.findByCssSelector('.euiStat__title');
const count = await title.getVisibleText();
const value = Number.parseInt(count, 10);
expect(Number.isNaN(value)).to.be(false);
return value;
};

return {
getQueryBar,
clearQueryBar,
Expand Down Expand Up @@ -307,5 +317,6 @@ export function ObservabilityAlertsCommonProvider({
viewRuleDetailsButtonClick,
viewRuleDetailsLinkClick,
getAlertsFlyoutViewRuleDetailsLinkOrFail,
getAlertStatValue,
};
}
Loading

0 comments on commit 964f092

Please sign in to comment.