diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/CentralHistoryRuntimeDataSource.ts b/public/app/features/alerting/unified/components/rules/central-state-history/CentralHistoryRuntimeDataSource.ts index bab7a40e855..f6b76f5e00a 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/CentralHistoryRuntimeDataSource.ts +++ b/public/app/features/alerting/unified/components/rules/central-state-history/CentralHistoryRuntimeDataSource.ts @@ -12,7 +12,7 @@ import { stateHistoryApi } from '../../../api/stateHistoryApi'; import { DataSourceInformation } from '../../../home/Insights'; import { LIMIT_EVENTS } from './EventListSceneObject'; -import { historyResultToDataFrame } from './utils'; +import { historyResultToDataFrame, parseBackendLabelFilters } from './utils'; const historyDataSourceUid = '__history_api_ds_uid__'; const historyDataSourcePluginId = '__history_api_ds_pluginId__'; @@ -65,7 +65,9 @@ class HistoryAPIDatasource extends RuntimeDataSource { const stateTo = templateSrv.replace(query.stateTo ?? '', request.scopedVars); const stateFrom = templateSrv.replace(query.stateFrom ?? '', request.scopedVars); - const historyResult = await getHistory(from, to); + const labelFilters = parseBackendLabelFilters(labels); + + const historyResult = await getHistory(from, to, labelFilters); return { data: historyResultToDataFrame(historyResult, { stateTo, stateFrom, labels }), @@ -85,15 +87,17 @@ class HistoryAPIDatasource extends RuntimeDataSource { * Fetch the history events from the history api. * @param from the start time * @param to the end time - * @returns the history events only filtered by time + * @param labels optional label filters for backend filtering + * @returns the history events filtered by time and labels */ -export const getHistory = (from: number, to: number) => { +export const getHistory = (from: number, to: number, labels?: Record) => { return dispatch( stateHistoryApi.endpoints.getRuleHistory.initiate( { from: from, to: to, limit: LIMIT_EVENTS, + labels: labels, }, { forceRefetch: Boolean(getTimeSrv().getAutoRefreshInteval().interval), // force refetch in case we are using the refresh option 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 c8fd1eb751b..4f83a7aec0f 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,7 +29,6 @@ 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'; @@ -39,6 +38,7 @@ import { LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO } from './CentralAler import { EventDetails } from './EventDetails'; import { HistoryErrorMessage } from './HistoryErrorMessage'; import { useRuleHistoryRecords } from './useRuleHistoryRecords'; +import { parseBackendLabelFilters } from './utils'; export const LIMIT_EVENTS = 5000; // limit is hard-capped at 5000 at the BE level. const PAGE_SIZE = 100; @@ -66,16 +66,7 @@ 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 labelFilters = parseBackendLabelFilters(valueInLabelFilter.toString()); const { data: stateHistory, @@ -86,7 +77,7 @@ export const HistoryEventsList = ({ from: from, to: to, limit: LIMIT_EVENTS, - labels: Object.keys(labelFilters).length > 0 ? labelFilters : undefined, + labels: labelFilters, }); const { historyRecords: historyRecordsNotSorted } = useRuleHistoryRecords(stateHistory, { 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 1dea299dd2e..d538fc8b0c2 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 @@ -7,6 +7,7 @@ import { setupMswServer } from '../../../mockApi'; import { captureRequests } from '../../../mocks/server/events'; import { StateFilterValues } from './CentralAlertHistoryScene'; +import { getHistory } from './CentralHistoryRuntimeDataSource'; import { HistoryEventsList } from './EventListSceneObject'; setupMswServer(); @@ -178,5 +179,25 @@ describe('HistoryEventsList', () => { expect(url.searchParams.get('labels_grafana_folder')).toBeNull(); expect(url.searchParams.get('labels_severity')).toBeNull(); }); + + it('should apply same backend filtering to chart data via getHistory function', async () => { + const capture = captureRequests((req) => req.url.includes('/api/v1/rules/history')); + + const from = 123; + const to = 456; + const labels = { alertname: 'alert_1', team: 'alerting' }; + + await getHistory(from, to, labels); + + const requests = await capture; + expect(requests).toHaveLength(1); + + const url = new URL(requests[0].url); + + expect(url.searchParams.get('labels_alertname')).toBe(labels.alertname); + expect(url.searchParams.get('labels_team')).toBe(labels.team); + expect(url.searchParams.get('from')).toBe(from.toString()); + expect(url.searchParams.get('to')).toBe(to.toString()); + }); }); }); diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/utils.ts b/public/app/features/alerting/unified/components/rules/central-state-history/utils.ts index a629b03dfb2..aaa7ea4830e 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/utils.ts +++ b/public/app/features/alerting/unified/components/rules/central-state-history/utils.ts @@ -24,6 +24,23 @@ import { LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO, StateFilterValues } const GROUPING_INTERVAL = 10 * 1000; // 10 seconds const QUERY_PARAM_PREFIX = 'var-'; // Prefix used by Grafana to sync variables in the URL +/** + * Parse label filters and prepare backend filters. + * Backend supports only exact matchers. + */ +export function parseBackendLabelFilters(labelFilter: string): Record { + const labelMatchers = parsePromQLStyleMatcherLooseSafe(labelFilter); + const labelFilters: Record = {}; + + labelMatchers.forEach((matcher) => { + if (!matcher.isRegex && matcher.isEqual) { + labelFilters[matcher.name] = matcher.value; + } + }); + + return labelFilters; +} + interface HistoryFilters { stateTo: string; stateFrom: string;