From e2cd5c870f9eaa904263b7dcb4815802062f55f4 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Wed, 21 May 2025 12:26:23 +0200 Subject: [PATCH] Alerting: Limit GMA alerts on the new list page (#105657) --- .../features/alerting/unified/api/prometheusApi.ts | 7 ++++--- .../alerting/unified/components/rules/RuleStats.tsx | 13 +++---------- public/app/features/alerting/unified/mocks.ts | 2 ++ .../unified/rule-list/GrafanaGroupLoader.test.tsx | 2 ++ .../unified/rule-list/GrafanaGroupLoader.tsx | 1 + .../unified/rule-list/GrafanaRuleLoader.tsx | 4 +++- .../unified/rule-list/PaginatedGrafanaLoader.tsx | 2 +- .../rule-list/hooks/prometheusGroupsGenerator.ts | 7 ++++--- .../rule-list/hooks/useFilteredRulesIterator.ts | 2 +- .../RuleStats.test.tsx => utils/ruleStats.test.ts} | 4 ++-- .../features/alerting/unified/utils/ruleStats.ts | 12 ++++++++++++ .../fixtures/alertRules.fixture.ts | 2 ++ public/app/types/unified-alerting-dto.ts | 7 +++++-- 13 files changed, 42 insertions(+), 23 deletions(-) rename public/app/features/alerting/unified/{components/rules/RuleStats.test.tsx => utils/ruleStats.test.ts} (93%) create mode 100644 public/app/features/alerting/unified/utils/ruleStats.ts diff --git a/public/app/features/alerting/unified/api/prometheusApi.ts b/public/app/features/alerting/unified/api/prometheusApi.ts index 3c2a8b50efe..aaa484f7556 100644 --- a/public/app/features/alerting/unified/api/prometheusApi.ts +++ b/public/app/features/alerting/unified/api/prometheusApi.ts @@ -28,10 +28,11 @@ type PromRulesOptions = WithNotificationOptions<{ groupNextToken?: string; }>; -type GrafanaPromRulesOptions = Omit & { +type GrafanaPromRulesOptions = Omit & { folderUid?: string; dashboardUid?: string; panelId?: number; + limitAlerts?: number; }; export const prometheusApi = alertingApi.injectEndpoints({ @@ -71,13 +72,13 @@ export const prometheusApi = alertingApi.injectEndpoints({ }, }), getGrafanaGroups: build.query, GrafanaPromRulesOptions>({ - query: ({ folderUid, groupName, ruleName, groupLimit, excludeAlerts, groupNextToken }) => ({ + query: ({ folderUid, groupName, ruleName, groupLimit, limitAlerts, groupNextToken }) => ({ url: `api/prometheus/grafana/api/v1/rules`, params: { folder_uid: folderUid, rule_group: groupName, rule_name: ruleName, - exclude_alerts: excludeAlerts?.toString(), + limit_alerts: limitAlerts, group_limit: groupLimit?.toFixed(0), group_next_token: groupNextToken, }, diff --git a/public/app/features/alerting/unified/components/rules/RuleStats.tsx b/public/app/features/alerting/unified/components/rules/RuleStats.tsx index 8d2a8c36ecc..57ad357a9bb 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, pick, sum } from 'lodash'; +import { isUndefined, omitBy } from 'lodash'; import pluralize from 'pluralize'; import * as React from 'react'; import { Fragment, useDeferredValue, useMemo } from 'react'; @@ -13,6 +13,8 @@ import { } from 'app/types/unified-alerting'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; +import { totalFromStats } from '../../utils/ruleStats'; + interface Props { namespaces: CombinedRuleNamespace[]; } @@ -80,15 +82,6 @@ function statsFromNamespaces(namespaces: CombinedRuleNamespace[]): AlertGroupTot 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', 'recovering']); - const total = sum(Object.values(countableStats)); - - return total; -} - export const RuleGroupStats = ({ group }: RuleGroupStatsProps) => { const stats = group.totals; const evaluationInterval = group?.interval; diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index be3484ba89f..e8d3bc2e2ab 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -232,6 +232,8 @@ export const mockGrafanaPromAlertingRule = ( uid: 'mock-rule-uid-123', folderUid: 'NAMESPACE_UID', isPaused: false, + totals: { alerting: 1 }, + totalsFiltered: { alerting: 1 }, ...partial, }; }; diff --git a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx index ef76569b713..783c0c23d91 100644 --- a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx +++ b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx @@ -168,6 +168,8 @@ function rulerRuleToPromRule(rule: RulerGrafanaRuleDTO): GrafanaPromRuleDTO { health: 'ok', state: PromAlertingRuleState.Inactive, type: rulerRuleType.grafana.alertingRule(rule) ? PromRuleType.Alerting : PromRuleType.Recording, + totals: {}, + totalsFiltered: {}, }; } diff --git a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx index 05e25a90115..45e5dfec099 100644 --- a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx @@ -44,6 +44,7 @@ export function GrafanaGroupLoader({ { folderUid: groupIdentifier.namespace.uid, groupName: groupIdentifier.groupName, + limitAlerts: 0, }, { pollingInterval: RULE_LIST_POLL_INTERVAL_MS } ); diff --git a/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx b/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx index 0ec493bd0db..8ce559087b4 100644 --- a/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/GrafanaRuleLoader.tsx @@ -6,6 +6,7 @@ import { GrafanaPromRuleDTO, PromRuleType, RulerGrafanaRuleDTO } from 'app/types import { alertRuleApi } from '../api/alertRuleApi'; import { prometheusApi } from '../api/prometheusApi'; import { GrafanaRulesSource } from '../utils/datasource'; +import { totalFromStats } from '../utils/ruleStats'; import { rulerRuleType } from '../utils/rules'; import { createRelativeUrl } from '../utils/url'; @@ -123,13 +124,14 @@ export function GrafanaRuleListItem({ if (rulerRuleType.grafana.alertingRule(rulerRule)) { const promAlertingRule = rule && rule.type === PromRuleType.Alerting ? rule : undefined; + const instancesCount = totalFromStats(promAlertingRule?.totals ?? {}); return ( ); diff --git a/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx index 67110157ea8..9fbd32edc35 100644 --- a/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx @@ -24,7 +24,7 @@ import { useLazyLoadPrometheusGroups } from './hooks/useLazyLoadPrometheusGroups export const GRAFANA_GROUP_PAGE_SIZE = 40; export function PaginatedGrafanaLoader() { - const grafanaGroupsGenerator = useGrafanaGroupsGenerator({ populateCache: true }); + const grafanaGroupsGenerator = useGrafanaGroupsGenerator({ populateCache: true, limitAlerts: 0 }); const groupsGenerator = useRef(toIndividualRuleGroups(grafanaGroupsGenerator(GRAFANA_GROUP_PAGE_SIZE))); diff --git a/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts b/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts index 2087e31c1c4..5165996b05c 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/prometheusGroupsGenerator.ts @@ -11,6 +11,7 @@ const { useLazyGetGroupsQuery, useLazyGetGrafanaGroupsQuery } = prometheusApi; interface UseGeneratorHookOptions { populateCache?: boolean; + limitAlerts?: number; } interface FetchGroupsOptions { @@ -58,7 +59,7 @@ export function useGrafanaGroupsGenerator(hookOptions: UseGeneratorHookOptions = const getGroupsAndProvideCache = useCallback( async (fetchOptions: FetchGroupsOptions) => { - const response = await getGrafanaGroups(fetchOptions).unwrap(); + const response = await getGrafanaGroups({ ...fetchOptions, limitAlerts: hookOptions.limitAlerts }).unwrap(); // This is not mandatory to preload ruler rules, but it improves the UX // Because the user waits a bit longer for the initial load but doesn't need to wait for each group to be loaded @@ -74,7 +75,7 @@ export function useGrafanaGroupsGenerator(hookOptions: UseGeneratorHookOptions = await dispatch( prometheusApi.util.upsertQueryData( 'getGrafanaGroups', - { folderUid: group.folderUid, groupName: group.name }, + { folderUid: group.folderUid, groupName: group.name, limitAlerts: hookOptions.limitAlerts }, { data: { groups: [group] }, status: 'success' } ) ); @@ -85,7 +86,7 @@ export function useGrafanaGroupsGenerator(hookOptions: UseGeneratorHookOptions = return response; }, - [getGrafanaGroups, dispatch, hookOptions.populateCache] + [getGrafanaGroups, dispatch, hookOptions.populateCache, hookOptions.limitAlerts] ); return useCallback( diff --git a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts index 0b7fc4159a6..56178cee913 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts @@ -56,7 +56,7 @@ export function useFilteredRulesIteratorProvider() { const allExternalRulesSources = getExternalRulesSources(); const prometheusGroupsGenerator = usePrometheusGroupsGenerator(); - const grafanaGroupsGenerator = useGrafanaGroupsGenerator(); + const grafanaGroupsGenerator = useGrafanaGroupsGenerator({ limitAlerts: 0 }); const getFilteredRulesIterable = (filterState: RulesFilter, groupLimit: number): GetIteratorResult => { /* this is the abort controller that allows us to stop an AsyncIterable */ diff --git a/public/app/features/alerting/unified/components/rules/RuleStats.test.tsx b/public/app/features/alerting/unified/utils/ruleStats.test.ts similarity index 93% rename from public/app/features/alerting/unified/components/rules/RuleStats.test.tsx rename to public/app/features/alerting/unified/utils/ruleStats.test.ts index 1292fdbd57a..280c5b51646 100644 --- a/public/app/features/alerting/unified/components/rules/RuleStats.test.tsx +++ b/public/app/features/alerting/unified/utils/ruleStats.test.ts @@ -1,6 +1,6 @@ -import { totalFromStats } from './RuleStats'; +import { totalFromStats } from './ruleStats'; -describe('RuleStats', () => { +describe('totalFromStats', () => { it('should count 0', () => { expect( totalFromStats({ diff --git a/public/app/features/alerting/unified/utils/ruleStats.ts b/public/app/features/alerting/unified/utils/ruleStats.ts new file mode 100644 index 00000000000..a2a03f40324 --- /dev/null +++ b/public/app/features/alerting/unified/utils/ruleStats.ts @@ -0,0 +1,12 @@ +import { pick, sum } from 'lodash'; + +import { AlertGroupTotals } from 'app/types/unified-alerting'; + +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', 'recovering']); + const total = sum(Object.values(countableStats)); + + return total; +} diff --git a/public/app/features/browse-dashboards/fixtures/alertRules.fixture.ts b/public/app/features/browse-dashboards/fixtures/alertRules.fixture.ts index 1363b10ad9a..0aded6b5053 100644 --- a/public/app/features/browse-dashboards/fixtures/alertRules.fixture.ts +++ b/public/app/features/browse-dashboards/fixtures/alertRules.fixture.ts @@ -87,6 +87,8 @@ export function getPrometheusRulesResponse( evaluationTime: 0, uid: rule_uid, folderUid: folderUid, + totals: {}, + totalsFiltered: {}, }, ], interval: 60, diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index d8793729370..0978062b808 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -3,7 +3,7 @@ import { DataQuery, RelativeTimeRange } from '@grafana/data'; import { ExpressionQuery } from 'app/features/expressions/types'; -import { AlertGroupTotals } from './unified-alerting'; +import { AlertGroupTotals, AlertInstanceTotals } from './unified-alerting'; export type Labels = Record; export type Annotations = Record; @@ -170,7 +170,10 @@ export interface PromRuleGroupDTO { lastEvaluation?: string; } -export interface GrafanaPromAlertingRuleDTO extends GrafanaPromRuleDTOBase, PromAlertingRuleDTO {} +export interface GrafanaPromAlertingRuleDTO extends GrafanaPromRuleDTOBase, PromAlertingRuleDTO { + totals: AlertInstanceTotals; + totalsFiltered: AlertInstanceTotals; +} export interface GrafanaPromRecordingRuleDTO extends GrafanaPromRuleDTOBase, PromRecordingRuleDTO {}