Alerting: Apply backend label filtering to chart visualization in state history UI (#109459)
This commit is contained in:
+8
-4
@@ -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<HistoryAPIQuery> {
|
||||
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<HistoryAPIQuery> {
|
||||
* 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<string, string>) => {
|
||||
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
|
||||
|
||||
+3
-12
@@ -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<string, string> = {};
|
||||
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, {
|
||||
|
||||
+21
@@ -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());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<string, string> {
|
||||
const labelMatchers = parsePromQLStyleMatcherLooseSafe(labelFilter);
|
||||
const labelFilters: Record<string, string> = {};
|
||||
|
||||
labelMatchers.forEach((matcher) => {
|
||||
if (!matcher.isRegex && matcher.isEqual) {
|
||||
labelFilters[matcher.name] = matcher.value;
|
||||
}
|
||||
});
|
||||
|
||||
return labelFilters;
|
||||
}
|
||||
|
||||
interface HistoryFilters {
|
||||
stateTo: string;
|
||||
stateFrom: string;
|
||||
|
||||
Reference in New Issue
Block a user