diff --git a/public/app/features/alerting/unified/components/rules/RuleStats.test.tsx b/public/app/features/alerting/unified/components/rules/RuleStats.test.tsx new file mode 100644 index 00000000000..1292fdbd57a --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/RuleStats.test.tsx @@ -0,0 +1,69 @@ +import { totalFromStats } from './RuleStats'; + +describe('RuleStats', () => { + it('should count 0', () => { + expect( + totalFromStats({ + alerting: 0, + error: 0, + inactive: 0, + nodata: 0, + paused: 0, + pending: 0, + recording: 0, + }) + ).toBe(0); + }); + + it('should count rules', () => { + expect( + totalFromStats({ + alerting: 2, + error: 0, + inactive: 0, + nodata: 0, + paused: 0, + pending: 2, + recording: 2, + }) + ).toBe(6); + }); + + it('should not count rule health as a rule', () => { + expect( + totalFromStats({ + alerting: 0, + error: 1, + inactive: 1, + nodata: 0, + paused: 0, + pending: 0, + recording: 0, + }) + ).toBe(1); + + expect( + totalFromStats({ + alerting: 0, + error: 0, + inactive: 0, + nodata: 1, + paused: 0, + pending: 0, + recording: 1, + }) + ).toBe(1); + + expect( + totalFromStats({ + alerting: 0, + error: 0, + inactive: 1, + nodata: 0, + paused: 1, + pending: 0, + recording: 0, + }) + ).toBe(1); + }); +}); diff --git a/public/app/features/alerting/unified/components/rules/RuleStats.tsx b/public/app/features/alerting/unified/components/rules/RuleStats.tsx index f9d67ac072b..bde656c18b9 100644 --- a/public/app/features/alerting/unified/components/rules/RuleStats.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleStats.tsx @@ -1,4 +1,4 @@ -import { isUndefined, omitBy, sum } from 'lodash'; +import { isUndefined, omitBy, pick, sum } from 'lodash'; import pluralize from 'pluralize'; import React, { Fragment } from 'react'; @@ -27,24 +27,12 @@ const emptyStats: Required = { }; export const RuleStats = ({ namespaces }: Props) => { - const stats = { ...emptyStats }; - - // sum all totals for all namespaces - namespaces.forEach(({ groups }) => { - groups.forEach((group) => { - const groupTotals = omitBy(group.totals, isUndefined); - for (let key in groupTotals) { - // @ts-ignore - stats[key] += groupTotals[key]; - } - }); - }); + const stats = statsFromNamespaces(namespaces); + const total = totalFromStats(stats); const statsComponents = getComponentsFromStats(stats); const hasStats = Boolean(statsComponents.length); - const total = sum(Object.values(stats)); - statsComponents.unshift( {total} {pluralize('rule', total)} @@ -66,6 +54,32 @@ interface RuleGroupStatsProps { group: CombinedRuleGroup; } +function statsFromNamespaces(namespaces: CombinedRuleNamespace[]): AlertGroupTotals { + const stats = { ...emptyStats }; + + // sum all totals for all namespaces + namespaces.forEach(({ groups }) => { + groups.forEach((group) => { + const groupTotals = omitBy(group.totals, isUndefined); + for (let key in groupTotals) { + // @ts-ignore + stats[key] += groupTotals[key]; + } + }); + }); + + return stats; +} + +export function totalFromStats(stats: AlertGroupTotals): number { + // countable stats will pick only the states that indicate a single rule – health indicators like "error" and "nodata" should + // not be counted because they are already counted by their state + const countableStats = pick(stats, ['alerting', 'pending', 'inactive', 'recording']); + const total = sum(Object.values(countableStats)); + + return total; +} + export const RuleGroupStats = ({ group }: RuleGroupStatsProps) => { const stats = group.totals; const evaluationInterval = group?.interval;