From 73f24d31169b0345e904088a24d3f4cd4208179d Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Fri, 8 Aug 2025 16:03:27 +0200 Subject: [PATCH] Alerting: Pass exact matchers to the backend in the state history UI (#109292) --- .../alerting/unified/api/stateHistoryApi.ts | 28 ++++++++++++++--- .../EventListSceneObject.tsx | 15 ++++++++- .../HistoryEventsList.test.tsx | 31 +++++++++++++++++++ public/locales/en-US/grafana.json | 2 +- 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/public/app/features/alerting/unified/api/stateHistoryApi.ts b/public/app/features/alerting/unified/api/stateHistoryApi.ts index 2f82fe6ef2d..bb4c85c58a9 100644 --- a/public/app/features/alerting/unified/api/stateHistoryApi.ts +++ b/public/app/features/alerting/unified/api/stateHistoryApi.ts @@ -4,11 +4,29 @@ import { alertingApi } from './alertingApi'; export const stateHistoryApi = alertingApi.injectEndpoints({ endpoints: (build) => ({ - getRuleHistory: build.query({ - query: ({ ruleUid, from, to, limit = 100 }) => ({ - url: '/api/v1/rules/history', - params: { ruleUID: ruleUid, from, to, limit }, - }), + getRuleHistory: build.query< + DataFrameJSON, + { ruleUid?: string; from?: number; to?: number; limit?: number; labels?: Record } + >({ + query: ({ ruleUid, from, to, limit = 100, labels }) => { + const params: Record = { + ruleUID: ruleUid, + from, + to, + limit, + }; + + if (labels) { + Object.entries(labels).forEach(([key, value]) => { + params[`labels_${key}`] = value; + }); + } + + return { + url: '/api/v1/rules/history', + params, + }; + }, }), }), }); diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/EventListSceneObject.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/EventListSceneObject.tsx index 24fe014c246..c8fd1eb751b 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/EventListSceneObject.tsx +++ b/public/app/features/alerting/unified/components/rules/central-state-history/EventListSceneObject.tsx @@ -29,6 +29,7 @@ import { AITriageButtonComponent } from '../../../enterprise-components/AI/AIGen import { usePagination } from '../../../hooks/usePagination'; import { combineMatcherStrings } from '../../../utils/alertmanager'; import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource'; +import { parsePromQLStyleMatcherLooseSafe } from '../../../utils/matchers'; import { createRelativeUrl } from '../../../utils/url'; import { AlertLabels } from '../../AlertLabels'; import { CollapseToggle } from '../../CollapseToggle'; @@ -65,6 +66,17 @@ export const HistoryEventsList = ({ const from = timeRange?.from.unix(); const to = timeRange?.to.unix(); + const labelMatchers = parsePromQLStyleMatcherLooseSafe(valueInLabelFilter.toString()); + + // Prepare labels for filtering on the backend side. + // Backend supports only exact matchers. + const labelFilters: Record = {}; + labelMatchers.forEach((matcher) => { + if (!matcher.isRegex && matcher.isEqual) { + labelFilters[matcher.name] = matcher.value; + } + }); + const { data: stateHistory, isLoading, @@ -74,6 +86,7 @@ export const HistoryEventsList = ({ from: from, to: to, limit: LIMIT_EVENTS, + labels: Object.keys(labelFilters).length > 0 ? labelFilters : undefined, }); const { historyRecords: historyRecordsNotSorted } = useRuleHistoryRecords(stateHistory, { @@ -102,7 +115,7 @@ export const HistoryEventsList = ({ > {t( 'alerting.central-alert-history.too-many-events.text', - 'The selected time period has too many events to display. Diplaying the latest 5000 events. Try using a shorter time period.' + 'The selected time period has too many events to display. Displaying the latest 5000 events. Try using a shorter time period.' )} )} diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/HistoryEventsList.test.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/HistoryEventsList.test.tsx index e2196595e57..1dea299dd2e 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/HistoryEventsList.test.tsx +++ b/public/app/features/alerting/unified/components/rules/central-state-history/HistoryEventsList.test.tsx @@ -4,6 +4,7 @@ import { byLabelText, byTestId } from 'testing-library-selector'; import { getDefaultTimeRange } from '@grafana/data'; import { setupMswServer } from '../../../mockApi'; +import { captureRequests } from '../../../mocks/server/events'; import { StateFilterValues } from './CentralAlertHistoryScene'; import { HistoryEventsList } from './EventListSceneObject'; @@ -148,4 +149,34 @@ describe('HistoryEventsList', () => { }); expect(ui.rowHeader.query()).not.toBeInTheDocument(); }); + + describe('backend filtering', () => { + it('should send only exact match filters to the backend', async () => { + const capture = captureRequests((req) => req.url.includes('/api/v1/rules/history')); + + render( + + ); + + await waitFor(() => { + expect(ui.loadingBar.query()).not.toBeInTheDocument(); + }); + + const requests = await capture; + expect(requests).toHaveLength(1); + + const url = new URL(requests[0].url); + + expect(url.searchParams.get('labels_alertname')).toBe('alert1'); + expect(url.searchParams.get('labels_team')).toBe('alerting'); + expect(url.searchParams.get('labels_grafana_folder')).toBeNull(); + expect(url.searchParams.get('labels_severity')).toBeNull(); + }); + }); }); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index dcde328be5e..34429ae992c 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -788,7 +788,7 @@ }, "filterBy": "Filter by:", "too-many-events": { - "text": "The selected time period has too many events to display. Diplaying the latest 5000 events. Try using a shorter time period.", + "text": "The selected time period has too many events to display. Displaying the latest 5000 events. Try using a shorter time period.", "title": "Unable to display all events" } },