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 287eceabbf1..0f4eedfef85 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
@@ -9,7 +9,7 @@ import { stateHistoryApi } from '../../../api/stateHistoryApi';
import { DataSourceInformation } from '../../../home/Insights';
import { LIMIT_EVENTS } from './EventListSceneObject';
-import { historyResultToDataFrame } from './utils';
+import { getStateFilterFromInQueryParams, getStateFilterToInQueryParams, historyResultToDataFrame } from './utils';
const historyDataSourceUid = '__history_api_ds_uid__';
const historyDataSourcePluginId = '__history_api_ds_pluginId__';
@@ -47,8 +47,14 @@ class HistoryAPIDatasource extends RuntimeDataSource {
const from = request.range.from.unix();
const to = request.range.to.unix();
+ // Get the labels and states filters from the URL
+ const stateTo = getStateFilterToInQueryParams();
+ const stateFrom = getStateFilterFromInQueryParams();
+
+ const historyResult = await getHistory(from, to);
+
return {
- data: historyResultToDataFrame(await getHistory(from, to)),
+ data: historyResultToDataFrame(historyResult, { stateTo, stateFrom }),
};
}
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 0ba6f9d4e7a..0c57ef96c0a 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
@@ -1,9 +1,9 @@
import { css, cx } from '@emotion/css';
-import { ReactElement, useMemo, useState } from 'react';
+import { ReactElement, useState } from 'react';
import { useLocation } from 'react-router';
import { useMeasure } from 'react-use';
-import { DataFrameJSON, GrafanaTheme2, IconName, TimeRange } from '@grafana/data';
+import { GrafanaTheme2, IconName, TimeRange } from '@grafana/data';
import {
CustomVariable,
SceneComponentProps,
@@ -25,18 +25,17 @@ import {
import { trackUseCentralHistoryFilterByClicking, trackUseCentralHistoryMaxEventsReached } from '../../../Analytics';
import { stateHistoryApi } from '../../../api/stateHistoryApi';
import { usePagination } from '../../../hooks/usePagination';
-import { combineMatcherStrings, labelsMatchMatchers } from '../../../utils/alertmanager';
+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';
import { LogRecord } from '../state-history/common';
-import { isLine, isNumbers } from '../state-history/useRuleHistoryRecords';
-import { LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO, StateFilterValues } from './CentralAlertHistoryScene';
+import { LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO } from './CentralAlertHistoryScene';
import { EventDetails } from './EventDetails';
import { HistoryErrorMessage } from './HistoryErrorMessage';
+import { useRuleHistoryRecords } from './useRuleHistoryRecords';
export const LIMIT_EVENTS = 5000; // limit is hard-capped at 5000 at the BE level.
const PAGE_SIZE = 100;
@@ -75,12 +74,11 @@ export const HistoryEventsList = ({
limit: LIMIT_EVENTS,
});
- const { historyRecords: historyRecordsNotSorted } = useRuleHistoryRecords(
- valueInLabelFilter.toString(),
- valueInStateToFilter.toString(),
- valueInStateFromFilter.toString(),
- stateHistory
- );
+ const { historyRecords: historyRecordsNotSorted } = useRuleHistoryRecords(stateHistory, {
+ labels: valueInLabelFilter.toString(),
+ stateFrom: valueInStateFromFilter.toString(),
+ stateTo: valueInStateToFilter.toString(),
+ });
const historyRecords = historyRecordsNotSorted.sort((a, b) => b.timestamp - a.timestamp);
@@ -542,54 +540,3 @@ export function HistoryEventsListObjectRenderer({ model }: SceneComponentProps
);
}
-/**
- * This hook filters the history records based on the label, stateTo and stateFrom filters.
- * @param filterInLabel
- * @param filterInStateTo
- * @param filterInStateFrom
- * @param stateHistory the original history records
- * @returns the filtered history records
- */
-function useRuleHistoryRecords(
- filterInLabel: string,
- filterInStateTo: string,
- filterInStateFrom: string,
- stateHistory?: DataFrameJSON
-) {
- return useMemo(() => {
- if (!stateHistory?.data) {
- return { historyRecords: [] };
- }
-
- const filterMatchers = filterInLabel ? parsePromQLStyleMatcherLooseSafe(filterInLabel) : [];
-
- const [tsValues, lines] = stateHistory.data.values;
- const timestamps = isNumbers(tsValues) ? tsValues : [];
-
- // merge timestamp with "line"
- const logRecords = timestamps.reduce((acc: LogRecord[], timestamp: number, index: number) => {
- const line = lines[index];
- if (!isLine(line)) {
- return acc;
- }
- // values property can be undefined for some instance states (e.g. NoData)
- const filterMatch = line.labels && labelsMatchMatchers(line.labels, filterMatchers);
- if (!isGrafanaAlertState(line.current) || !isGrafanaAlertState(line.previous)) {
- return acc;
- }
- const baseStateTo = mapStateWithReasonToBaseState(line.current);
- const baseStateFrom = mapStateWithReasonToBaseState(line.previous);
- const stateToMatch = filterInStateTo !== StateFilterValues.all ? filterInStateTo === baseStateTo : true;
- const stateFromMatch = filterInStateFrom !== StateFilterValues.all ? filterInStateFrom === baseStateFrom : true;
- if (filterMatch && stateToMatch && stateFromMatch) {
- acc.push({ timestamp, line });
- }
-
- return acc;
- }, []);
-
- return {
- historyRecords: logRecords,
- };
- }, [stateHistory, filterInLabel, filterInStateTo, filterInStateFrom]);
-}
diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/__fixtures__/alert-state-history.ts b/public/app/features/alerting/unified/components/rules/central-state-history/__fixtures__/alert-state-history.ts
new file mode 100644
index 00000000000..8debd4d8498
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rules/central-state-history/__fixtures__/alert-state-history.ts
@@ -0,0 +1,896 @@
+import { DataFrameJSON } from '@grafana/data';
+
+const data: DataFrameJSON = {
+ data: {
+ values: [
+ [
+ 1727189670000, 1727189670000, 1727189670000, 1727189670000, 1727189670000, 1727189670000, 1727189670000,
+ 1727189670000, 1727189670000, 1727189670000, 1727189680000, 1727189700000, 1727189690000, 1727189690000,
+ 1727189690000, 1727189690000, 1727189690000, 1727189690000, 1727189690000, 1727189690000, 1727189680000,
+ 1727189670000, 1727189700000, 1727189710000, 1727189710000, 1727189710000, 1727189710000, 1727189710000,
+ 1727189710000, 1727189710000, 1727189710000, 1727189710000,
+ ],
+ [
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'NoData',
+ values: {},
+ condition: 'C',
+ dashboardUID: '-W9yw_X7k',
+ panelID: 3,
+ fingerprint: '009794b4d3074732',
+ ruleTitle: 'CPU Usage 6',
+ ruleID: 10605,
+ ruleUID: '-rRg_HcVz',
+ labels: {
+ alertname: 'CPU Usage 6',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Demonstrations',
+ ref_id: 'A',
+ team: 'operations',
+ type: 'test',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'NoData',
+ values: {},
+ condition: 'C',
+ dashboardUID: 'stzZvVw7k',
+ panelID: 8,
+ fingerprint: '186e7d0c9173ddf4',
+ ruleTitle: 'CPU Usage 4',
+ ruleID: 150,
+ ruleUID: 's984p7n4k',
+ labels: {
+ alertname: 'CPU Usage 4',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Demonstrations',
+ ref_id: 'A',
+ region: 'EMEA',
+ team: 'operations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Alerting',
+ current: 'Normal',
+ values: {
+ B: -6.886811320670644,
+ C: 0,
+ },
+ condition: 'C',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'bcf38efbb616d8d2',
+ ruleTitle: 'direct to contact point',
+ ruleID: 10656,
+ ruleUID: 'ddc5zx4l3zls0a',
+ labels: {
+ alertname: 'direct to contact point',
+ grafana_folder: 'Testing and reproducing',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'NoData',
+ values: {},
+ condition: 'D',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 13,
+ fingerprint: 'd3f3ec7ad90a37fd',
+ ruleTitle: 'With Disabled Query',
+ ruleID: 10631,
+ ruleUID: 'ab2ac850-1669-4dac-b566-c91728425f39',
+ labels: {
+ alertname: 'With Disabled Query',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Testing and reproducing',
+ ref_id: 'A,B',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'NoData',
+ values: {},
+ condition: 'C',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'f4c073dd4c60e897',
+ ruleTitle: 'Alert State History Random',
+ ruleID: 10614,
+ ruleUID: 'e4d7252b-c66f-4f5c-b7db-28b9936150bd',
+ labels: {
+ alertname: 'Alert State History Random',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Testing and reproducing',
+ ref_id: 'A',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ B: 54.137611900622574,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'e11b0ff29886e8f2',
+ ruleTitle: 'node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate4',
+ ruleID: 10662,
+ ruleUID: 'ddgfq7c57fqpsc',
+ labels: {
+ alertname: 'node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate4',
+ grafana_folder: 'Testing and reproducing',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'NoData',
+ values: {},
+ condition: 'B',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'd6d7e256298c8ea8',
+ ruleTitle: 'delete-ds',
+ ruleID: 144,
+ ruleUID: 'wXS0SnWVz',
+ labels: {
+ alertname: 'delete-ds',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Testing and reproducing',
+ ref_id: 'A',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'NoData',
+ values: {},
+ condition: 'C',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'bf1b309577b4fc0c',
+ ruleTitle: 'Threshold Test',
+ ruleID: 148,
+ ruleUID: 'u0QVOqMVz',
+ labels: {
+ alertname: 'Threshold Test',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Testing and reproducing',
+ ref_id: 'A',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'NoData',
+ values: {},
+ condition: 'C',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'd6b1cde6ab63bfca',
+ ruleTitle: 'Alert History fingerprint',
+ ruleID: 10619,
+ ruleUID: 'c7a7989f-f12c-4aa5-8fb3-27f4509a5809',
+ labels: {
+ alertname: 'Alert History fingerprint',
+ datasource_uid: 'PDDA8E780A17E7EF1',
+ grafana_folder: 'Testing and reproducing',
+ ref_id: 'A',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ B: 0.0020875,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'd4c61dfe-24d8-433e-984e-701a2f14faab',
+ panelID: 1,
+ fingerprint: '40ddb0f6e97426e9',
+ ruleTitle: 'UniFi CPU Usage',
+ ruleID: 10638,
+ ruleUID: 'd10fc550-3552-4083-a3aa-f6b68058bc7f',
+ labels: {
+ alertname: 'UniFi CPU Usage',
+ grafana_folder: 'UniFi',
+ instance: 'unpoller:9130',
+ job: 'unifipoller',
+ name: 'Dream Router',
+ site_name: 'Default (default)',
+ source: 'https://192.168.1.1',
+ type: 'udm',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Pending',
+ current: 'Alerting',
+ values: {
+ B0: 36.956598435022215,
+ },
+ condition: 'B',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: '10716534bbf8086b',
+ ruleTitle: 'XSS attack vector',
+ ruleID: 37,
+ ruleUID: 'p8TBxnq7k',
+ labels: {
+ alertname: 'XSS attack vector',
+ grafana_folder: 'gdev dashboards',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'NoData',
+ current: 'Normal (MissingSeries)',
+ values: {},
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: '4a5519a42c382438',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Demonstrations',
+ ref_id: 'A',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Alerting',
+ values: {
+ B: 0.067575,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: 'cc4aceb3018da2e8',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ cpu: '7',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Alerting',
+ values: {
+ B: 0.06216900000000001,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: '20dfd648881e698f',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ cpu: '2',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Alerting',
+ values: {
+ B: 0.07027800000000001,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: '234db8cd1e160f6a',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ cpu: '1',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Alerting',
+ values: {
+ B: 0.06727466666666668,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: '4e1f85b7efc3ed75',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ cpu: '0',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Alerting',
+ values: {
+ B: 0.05315900000000001,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: '50bf9f17b9534593',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ cpu: '6',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Alerting',
+ values: {
+ B: 0.046551666666666686,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: '2fb0af95df9b2bfe',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ cpu: '5',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Alerting',
+ values: {
+ B: 0.05466066666666668,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: 'd0303e26f6c376e9',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ cpu: '4',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Alerting',
+ values: {
+ B: 0.059165666666666665,
+ C: 1,
+ },
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: 'af82d1220e70c3b4',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ cpu: '3',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Error',
+ current: 'Normal (MissingSeries)',
+ values: {},
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: '4a5519a42c382438',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Demonstrations',
+ ref_id: 'A',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'NoData',
+ values: {},
+ condition: 'C',
+ dashboardUID: 'iYSJkozVk',
+ panelID: 5,
+ fingerprint: '4a5519a42c382438',
+ ruleTitle: 'CPU Usage',
+ ruleID: 7635,
+ ruleUID: '8nEmeE44k',
+ labels: {
+ alertname: 'CPU Usage',
+ datasource_uid: 'gdev-prometheus',
+ grafana_folder: 'Demonstrations',
+ ref_id: 'A',
+ team: 'operations',
+ type: 'cpu',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ C0: 9.319781010143206,
+ },
+ condition: 'C',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'abeb2444057d7f01',
+ ruleTitle: 'test-read-only-threshold-indicators',
+ ruleID: 10606,
+ ruleUID: 'Wq5jJlc4k',
+ labels: {
+ alertname: 'test-read-only-threshold-indicators',
+ grafana_folder: 'Testing and reproducing',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ reducer: 0.07305751032920166,
+ threshold: 1,
+ },
+ condition: 'threshold',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: '2e7f5e6f5a5f2902',
+ ruleTitle: 'CPU Usage 5',
+ ruleID: 10583,
+ ruleUID: 'LvMu_IHVz',
+ labels: {
+ alertname: 'CPU Usage 5',
+ cpu: '6',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ reducer: 0.06820108623217379,
+ threshold: 1,
+ },
+ condition: 'threshold',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: '1c1b21f738468ea5',
+ ruleTitle: 'CPU Usage 5',
+ ruleID: 10583,
+ ruleUID: 'LvMu_IHVz',
+ labels: {
+ alertname: 'CPU Usage 5',
+ cpu: '5',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ reducer: 0.07685819005731041,
+ threshold: 1,
+ },
+ condition: 'threshold',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: '23ee5f02850e38f8',
+ ruleTitle: 'CPU Usage 5',
+ ruleID: 10583,
+ ruleUID: 'LvMu_IHVz',
+ labels: {
+ alertname: 'CPU Usage 5',
+ cpu: '4',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ reducer: 0.08614874050379848,
+ threshold: 1,
+ },
+ condition: 'threshold',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: '2a810fa152d6407b',
+ ruleTitle: 'CPU Usage 5',
+ ruleID: 10583,
+ ruleUID: 'LvMu_IHVz',
+ labels: {
+ alertname: 'CPU Usage 5',
+ cpu: '3',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ reducer: 0.0926943555910969,
+ threshold: 1,
+ },
+ condition: 'threshold',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: '48d127e8826c6fb6',
+ ruleTitle: 'CPU Usage 5',
+ ruleID: 10583,
+ ruleUID: 'LvMu_IHVz',
+ labels: {
+ alertname: 'CPU Usage 5',
+ cpu: '2',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ reducer: 0.1097974143675863,
+ threshold: 1,
+ },
+ condition: 'threshold',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'c9582e3d28402fe9',
+ ruleTitle: 'CPU Usage 5',
+ ruleID: 10583,
+ ruleUID: 'LvMu_IHVz',
+ labels: {
+ alertname: 'CPU Usage 5',
+ cpu: '1',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ reducer: 0.12985655737704918,
+ threshold: 1,
+ },
+ condition: 'threshold',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: 'fb3e32509acec7ec',
+ ruleTitle: 'CPU Usage 5',
+ ruleID: 10583,
+ ruleUID: 'LvMu_IHVz',
+ labels: {
+ alertname: 'CPU Usage 5',
+ cpu: '0',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ B0: 8.406488484275167,
+ },
+ condition: 'B',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: '91a8126f41689d98',
+ ruleTitle: 'Random Values',
+ ruleID: 146,
+ ruleUID: 'I1M_iCWVk',
+ labels: {
+ alertname: 'Random Values',
+ grafana_folder: 'Demonstrations',
+ },
+ },
+ {
+ schemaVersion: 1,
+ previous: 'Normal',
+ current: 'Pending',
+ values: {
+ reducer: 0.08973827135812343,
+ threshold: 1,
+ },
+ condition: 'threshold',
+ dashboardUID: '',
+ panelID: 0,
+ fingerprint: '6cd2ab200de1cf77',
+ ruleTitle: 'CPU Usage 5',
+ ruleID: 10583,
+ ruleUID: 'LvMu_IHVz',
+ labels: {
+ alertname: 'CPU Usage 5',
+ cpu: '7',
+ grafana_folder: 'Demonstrations',
+ team: 'operations',
+ },
+ },
+ ],
+ [
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'system-metrics',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default 2',
+ orgID: '1',
+ },
+ {
+ folderUID: 'a3eyrfqnk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'a3eyrfqnk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'a3eyrfqnk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'a3eyrfqnk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'a3eyrfqnk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'a3eyrfqnk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'a3eyrfqnk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'Qh4ctRZVz',
+ from: 'state-history',
+ group: 'system',
+ orgID: '1',
+ },
+ {
+ folderUID: 'pPyg37Qnk',
+ from: 'state-history',
+ group: 'security',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'usage',
+ orgID: '1',
+ },
+ {
+ folderUID: 'a3eyrfqnk',
+ from: 'state-history',
+ group: 'thresholds',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ {
+ folderUID: 'lG5pfeRVk',
+ from: 'state-history',
+ group: 'default',
+ orgID: '1',
+ },
+ ],
+ ],
+ },
+};
+
+export default data;
diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/__snapshots__/useRuleHistoryRecords.test.ts.snap b/public/app/features/alerting/unified/components/rules/central-state-history/__snapshots__/useRuleHistoryRecords.test.ts.snap
new file mode 100644
index 00000000000..b1ff6c28eb6
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rules/central-state-history/__snapshots__/useRuleHistoryRecords.test.ts.snap
@@ -0,0 +1,822 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ruleHistoryToRecords should be empty with no matches 1`] = `
+{
+ "historyRecords": [],
+}
+`;
+
+exports[`ruleHistoryToRecords should convert rule history JSON response to log records 1`] = `
+{
+ "historyRecords": [
+ {
+ "line": {
+ "condition": "C",
+ "current": "NoData",
+ "dashboardUID": "-W9yw_X7k",
+ "fingerprint": "009794b4d3074732",
+ "labels": {
+ "alertname": "CPU Usage 6",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Demonstrations",
+ "ref_id": "A",
+ "team": "operations",
+ "type": "test",
+ },
+ "panelID": 3,
+ "previous": "Normal",
+ "ruleID": 10605,
+ "ruleTitle": "CPU Usage 6",
+ "ruleUID": "-rRg_HcVz",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "NoData",
+ "dashboardUID": "stzZvVw7k",
+ "fingerprint": "186e7d0c9173ddf4",
+ "labels": {
+ "alertname": "CPU Usage 4",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Demonstrations",
+ "ref_id": "A",
+ "region": "EMEA",
+ "team": "operations",
+ },
+ "panelID": 8,
+ "previous": "Normal",
+ "ruleID": 150,
+ "ruleTitle": "CPU Usage 4",
+ "ruleUID": "s984p7n4k",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Normal",
+ "dashboardUID": "",
+ "fingerprint": "bcf38efbb616d8d2",
+ "labels": {
+ "alertname": "direct to contact point",
+ "grafana_folder": "Testing and reproducing",
+ },
+ "panelID": 0,
+ "previous": "Alerting",
+ "ruleID": 10656,
+ "ruleTitle": "direct to contact point",
+ "ruleUID": "ddc5zx4l3zls0a",
+ "schemaVersion": 1,
+ "values": {
+ "B": -6.886811320670644,
+ "C": 0,
+ },
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "D",
+ "current": "NoData",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "d3f3ec7ad90a37fd",
+ "labels": {
+ "alertname": "With Disabled Query",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Testing and reproducing",
+ "ref_id": "A,B",
+ },
+ "panelID": 13,
+ "previous": "Normal",
+ "ruleID": 10631,
+ "ruleTitle": "With Disabled Query",
+ "ruleUID": "ab2ac850-1669-4dac-b566-c91728425f39",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "NoData",
+ "dashboardUID": "",
+ "fingerprint": "f4c073dd4c60e897",
+ "labels": {
+ "alertname": "Alert State History Random",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Testing and reproducing",
+ "ref_id": "A",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10614,
+ "ruleTitle": "Alert State History Random",
+ "ruleUID": "e4d7252b-c66f-4f5c-b7db-28b9936150bd",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "e11b0ff29886e8f2",
+ "labels": {
+ "alertname": "node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate4",
+ "grafana_folder": "Testing and reproducing",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10662,
+ "ruleTitle": "node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate4",
+ "ruleUID": "ddgfq7c57fqpsc",
+ "schemaVersion": 1,
+ "values": {
+ "B": 54.137611900622574,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "B",
+ "current": "NoData",
+ "dashboardUID": "",
+ "fingerprint": "d6d7e256298c8ea8",
+ "labels": {
+ "alertname": "delete-ds",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Testing and reproducing",
+ "ref_id": "A",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 144,
+ "ruleTitle": "delete-ds",
+ "ruleUID": "wXS0SnWVz",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "NoData",
+ "dashboardUID": "",
+ "fingerprint": "bf1b309577b4fc0c",
+ "labels": {
+ "alertname": "Threshold Test",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Testing and reproducing",
+ "ref_id": "A",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 148,
+ "ruleTitle": "Threshold Test",
+ "ruleUID": "u0QVOqMVz",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "NoData",
+ "dashboardUID": "",
+ "fingerprint": "d6b1cde6ab63bfca",
+ "labels": {
+ "alertname": "Alert History fingerprint",
+ "datasource_uid": "PDDA8E780A17E7EF1",
+ "grafana_folder": "Testing and reproducing",
+ "ref_id": "A",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10619,
+ "ruleTitle": "Alert History fingerprint",
+ "ruleUID": "c7a7989f-f12c-4aa5-8fb3-27f4509a5809",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Pending",
+ "dashboardUID": "d4c61dfe-24d8-433e-984e-701a2f14faab",
+ "fingerprint": "40ddb0f6e97426e9",
+ "labels": {
+ "alertname": "UniFi CPU Usage",
+ "grafana_folder": "UniFi",
+ "instance": "unpoller:9130",
+ "job": "unifipoller",
+ "name": "Dream Router",
+ "site_name": "Default (default)",
+ "source": "https://192.168.1.1",
+ "type": "udm",
+ },
+ "panelID": 1,
+ "previous": "Normal",
+ "ruleID": 10638,
+ "ruleTitle": "UniFi CPU Usage",
+ "ruleUID": "d10fc550-3552-4083-a3aa-f6b68058bc7f",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.0020875,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "B",
+ "current": "Alerting",
+ "dashboardUID": "",
+ "fingerprint": "10716534bbf8086b",
+ "labels": {
+ "alertname": "XSS attack vector",
+ "grafana_folder": "gdev dashboards",
+ },
+ "panelID": 0,
+ "previous": "Pending",
+ "ruleID": 37,
+ "ruleTitle": "XSS attack vector",
+ "ruleUID": "p8TBxnq7k",
+ "schemaVersion": 1,
+ "values": {
+ "B0": 36.956598435022215,
+ },
+ },
+ "timestamp": 1727189680000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Normal (MissingSeries)",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "4a5519a42c382438",
+ "labels": {
+ "alertname": "CPU Usage",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Demonstrations",
+ "ref_id": "A",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "NoData",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189700000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Alerting",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "cc4aceb3018da2e8",
+ "labels": {
+ "alertname": "CPU Usage",
+ "cpu": "7",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.067575,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189690000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Alerting",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "20dfd648881e698f",
+ "labels": {
+ "alertname": "CPU Usage",
+ "cpu": "2",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.06216900000000001,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189690000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Alerting",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "234db8cd1e160f6a",
+ "labels": {
+ "alertname": "CPU Usage",
+ "cpu": "1",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.07027800000000001,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189690000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Alerting",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "4e1f85b7efc3ed75",
+ "labels": {
+ "alertname": "CPU Usage",
+ "cpu": "0",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.06727466666666668,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189690000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Alerting",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "50bf9f17b9534593",
+ "labels": {
+ "alertname": "CPU Usage",
+ "cpu": "6",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.05315900000000001,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189690000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Alerting",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "2fb0af95df9b2bfe",
+ "labels": {
+ "alertname": "CPU Usage",
+ "cpu": "5",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.046551666666666686,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189690000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Alerting",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "d0303e26f6c376e9",
+ "labels": {
+ "alertname": "CPU Usage",
+ "cpu": "4",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.05466066666666668,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189690000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Alerting",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "af82d1220e70c3b4",
+ "labels": {
+ "alertname": "CPU Usage",
+ "cpu": "3",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {
+ "B": 0.059165666666666665,
+ "C": 1,
+ },
+ },
+ "timestamp": 1727189690000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Normal (MissingSeries)",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "4a5519a42c382438",
+ "labels": {
+ "alertname": "CPU Usage",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Demonstrations",
+ "ref_id": "A",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Error",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189680000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "NoData",
+ "dashboardUID": "iYSJkozVk",
+ "fingerprint": "4a5519a42c382438",
+ "labels": {
+ "alertname": "CPU Usage",
+ "datasource_uid": "gdev-prometheus",
+ "grafana_folder": "Demonstrations",
+ "ref_id": "A",
+ "team": "operations",
+ "type": "cpu",
+ },
+ "panelID": 5,
+ "previous": "Normal",
+ "ruleID": 7635,
+ "ruleTitle": "CPU Usage",
+ "ruleUID": "8nEmeE44k",
+ "schemaVersion": 1,
+ "values": {},
+ },
+ "timestamp": 1727189670000,
+ },
+ {
+ "line": {
+ "condition": "C",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "abeb2444057d7f01",
+ "labels": {
+ "alertname": "test-read-only-threshold-indicators",
+ "grafana_folder": "Testing and reproducing",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10606,
+ "ruleTitle": "test-read-only-threshold-indicators",
+ "ruleUID": "Wq5jJlc4k",
+ "schemaVersion": 1,
+ "values": {
+ "C0": 9.319781010143206,
+ },
+ },
+ "timestamp": 1727189700000,
+ },
+ {
+ "line": {
+ "condition": "threshold",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "2e7f5e6f5a5f2902",
+ "labels": {
+ "alertname": "CPU Usage 5",
+ "cpu": "6",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10583,
+ "ruleTitle": "CPU Usage 5",
+ "ruleUID": "LvMu_IHVz",
+ "schemaVersion": 1,
+ "values": {
+ "reducer": 0.07305751032920166,
+ "threshold": 1,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ {
+ "line": {
+ "condition": "threshold",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "1c1b21f738468ea5",
+ "labels": {
+ "alertname": "CPU Usage 5",
+ "cpu": "5",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10583,
+ "ruleTitle": "CPU Usage 5",
+ "ruleUID": "LvMu_IHVz",
+ "schemaVersion": 1,
+ "values": {
+ "reducer": 0.06820108623217379,
+ "threshold": 1,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ {
+ "line": {
+ "condition": "threshold",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "23ee5f02850e38f8",
+ "labels": {
+ "alertname": "CPU Usage 5",
+ "cpu": "4",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10583,
+ "ruleTitle": "CPU Usage 5",
+ "ruleUID": "LvMu_IHVz",
+ "schemaVersion": 1,
+ "values": {
+ "reducer": 0.07685819005731041,
+ "threshold": 1,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ {
+ "line": {
+ "condition": "threshold",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "2a810fa152d6407b",
+ "labels": {
+ "alertname": "CPU Usage 5",
+ "cpu": "3",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10583,
+ "ruleTitle": "CPU Usage 5",
+ "ruleUID": "LvMu_IHVz",
+ "schemaVersion": 1,
+ "values": {
+ "reducer": 0.08614874050379848,
+ "threshold": 1,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ {
+ "line": {
+ "condition": "threshold",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "48d127e8826c6fb6",
+ "labels": {
+ "alertname": "CPU Usage 5",
+ "cpu": "2",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10583,
+ "ruleTitle": "CPU Usage 5",
+ "ruleUID": "LvMu_IHVz",
+ "schemaVersion": 1,
+ "values": {
+ "reducer": 0.0926943555910969,
+ "threshold": 1,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ {
+ "line": {
+ "condition": "threshold",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "c9582e3d28402fe9",
+ "labels": {
+ "alertname": "CPU Usage 5",
+ "cpu": "1",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10583,
+ "ruleTitle": "CPU Usage 5",
+ "ruleUID": "LvMu_IHVz",
+ "schemaVersion": 1,
+ "values": {
+ "reducer": 0.1097974143675863,
+ "threshold": 1,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ {
+ "line": {
+ "condition": "threshold",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "fb3e32509acec7ec",
+ "labels": {
+ "alertname": "CPU Usage 5",
+ "cpu": "0",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10583,
+ "ruleTitle": "CPU Usage 5",
+ "ruleUID": "LvMu_IHVz",
+ "schemaVersion": 1,
+ "values": {
+ "reducer": 0.12985655737704918,
+ "threshold": 1,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ {
+ "line": {
+ "condition": "B",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "91a8126f41689d98",
+ "labels": {
+ "alertname": "Random Values",
+ "grafana_folder": "Demonstrations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 146,
+ "ruleTitle": "Random Values",
+ "ruleUID": "I1M_iCWVk",
+ "schemaVersion": 1,
+ "values": {
+ "B0": 8.406488484275167,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ {
+ "line": {
+ "condition": "threshold",
+ "current": "Pending",
+ "dashboardUID": "",
+ "fingerprint": "6cd2ab200de1cf77",
+ "labels": {
+ "alertname": "CPU Usage 5",
+ "cpu": "7",
+ "grafana_folder": "Demonstrations",
+ "team": "operations",
+ },
+ "panelID": 0,
+ "previous": "Normal",
+ "ruleID": 10583,
+ "ruleTitle": "CPU Usage 5",
+ "ruleUID": "LvMu_IHVz",
+ "schemaVersion": 1,
+ "values": {
+ "reducer": 0.08973827135812343,
+ "threshold": 1,
+ },
+ },
+ "timestamp": 1727189710000,
+ },
+ ],
+}
+`;
+
+exports[`ruleHistoryToRecords should convert rule history JSON response with filters 1`] = `
+{
+ "historyRecords": [
+ {
+ "line": {
+ "condition": "B",
+ "current": "Alerting",
+ "dashboardUID": "",
+ "fingerprint": "10716534bbf8086b",
+ "labels": {
+ "alertname": "XSS attack vector",
+ "grafana_folder": "gdev dashboards",
+ },
+ "panelID": 0,
+ "previous": "Pending",
+ "ruleID": 37,
+ "ruleTitle": "XSS attack vector",
+ "ruleUID": "p8TBxnq7k",
+ "schemaVersion": 1,
+ "values": {
+ "B0": 36.956598435022215,
+ },
+ },
+ "timestamp": 1727189680000,
+ },
+ ],
+}
+`;
diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/__snapshots__/utils.test.ts.snap b/public/app/features/alerting/unified/components/rules/central-state-history/__snapshots__/utils.test.ts.snap
new file mode 100644
index 00000000000..e1edf6a4842
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rules/central-state-history/__snapshots__/utils.test.ts.snap
@@ -0,0 +1,71 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`historyResultToDataFrame should decode 1`] = `
+[
+ {
+ "fields": [
+ {
+ "config": {
+ "custom": {
+ "fillOpacity": 100,
+ },
+ "displayName": "Time",
+ },
+ "name": "time",
+ "type": "time",
+ "values": [
+ 1727189670000,
+ 1727189680000,
+ 1727189700000,
+ 1727189690000,
+ 1727189710000,
+ ],
+ },
+ {
+ "config": {},
+ "name": "value",
+ "type": "number",
+ "values": [
+ 11,
+ 2,
+ 2,
+ 8,
+ 9,
+ ],
+ },
+ ],
+ "length": 5,
+ },
+]
+`;
+
+exports[`historyResultToDataFrame should decode and filter 1`] = `
+[
+ {
+ "fields": [
+ {
+ "config": {
+ "custom": {
+ "fillOpacity": 100,
+ },
+ "displayName": "Time",
+ },
+ "name": "time",
+ "type": "time",
+ "values": [
+ 1727189680000,
+ ],
+ },
+ {
+ "config": {},
+ "name": "value",
+ "type": "number",
+ "values": [
+ 1,
+ ],
+ },
+ ],
+ "length": 1,
+ },
+]
+`;
diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.test.ts b/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.test.ts
new file mode 100644
index 00000000000..9acf26edd49
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.test.ts
@@ -0,0 +1,20 @@
+import fixtureData from './__fixtures__/alert-state-history';
+import { ruleHistoryToRecords } from './useRuleHistoryRecords';
+
+describe('ruleHistoryToRecords', () => {
+ it('should convert rule history JSON response to log records', () => {
+ expect(ruleHistoryToRecords(fixtureData)).toMatchSnapshot();
+ });
+
+ it('should convert rule history JSON response with filters', () => {
+ expect(
+ ruleHistoryToRecords(fixtureData, { stateFrom: 'Pending', stateTo: 'Alerting', labels: '' })
+ ).toMatchSnapshot();
+ });
+
+ it('should be empty with no matches', () => {
+ expect(
+ ruleHistoryToRecords(fixtureData, { stateFrom: 'Pending', stateTo: 'Alerting', labels: 'doesNot=exist' })
+ ).toMatchSnapshot();
+ });
+});
diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.ts b/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.ts
new file mode 100644
index 00000000000..ee8c739d3e5
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.ts
@@ -0,0 +1,65 @@
+import { useMemo } from 'react';
+
+import { DataFrameJSON } from '@grafana/data';
+import { mapStateWithReasonToBaseState } from 'app/types/unified-alerting-dto';
+
+import { labelsMatchMatchers } from '../../../utils/alertmanager';
+import { parsePromQLStyleMatcherLooseSafe } from '../../../utils/matchers';
+import { LogRecord } from '../state-history/common';
+import { isLine, isNumbers } from '../state-history/useRuleHistoryRecords';
+
+import { StateFilterValues } from './CentralAlertHistoryScene';
+
+const emptyFilters = {
+ labels: '',
+ stateFrom: 'all',
+ stateTo: 'all',
+};
+
+/**
+ * This hook filters the history records based on the label, stateTo and stateFrom filters.
+ * @param filterInLabel
+ * @param filterInStateTo
+ * @param filterInStateFrom
+ * @param stateHistory the original history records
+ * @returns the filtered history records
+ */
+export function useRuleHistoryRecords(stateHistory?: DataFrameJSON, filters = emptyFilters) {
+ return useMemo(() => ruleHistoryToRecords(stateHistory, filters), [filters, stateHistory]);
+}
+
+export function ruleHistoryToRecords(stateHistory?: DataFrameJSON, filters = emptyFilters) {
+ const { labels, stateFrom, stateTo } = filters;
+
+ if (!stateHistory?.data) {
+ return { historyRecords: [] };
+ }
+
+ const filterMatchers = labels ? parsePromQLStyleMatcherLooseSafe(labels) : [];
+
+ const [tsValues, lines] = stateHistory.data.values;
+ const timestamps = isNumbers(tsValues) ? tsValues : [];
+
+ // merge timestamp with "line"
+ const logRecords = timestamps.reduce((acc: LogRecord[], timestamp: number, index: number) => {
+ const line = lines[index];
+ if (!isLine(line)) {
+ return acc;
+ }
+ // values property can be undefined for some instance states (e.g. NoData)
+ const filterMatch = line.labels && labelsMatchMatchers(line.labels, filterMatchers);
+ const baseStateTo = mapStateWithReasonToBaseState(line.current);
+ const baseStateFrom = mapStateWithReasonToBaseState(line.previous);
+ const stateToMatch = stateTo !== StateFilterValues.all ? stateTo === baseStateTo : true;
+ const stateFromMatch = stateFrom !== StateFilterValues.all ? stateFrom === baseStateFrom : true;
+ if (filterMatch && stateToMatch && stateFromMatch) {
+ acc.push({ timestamp, line });
+ }
+
+ return acc;
+ }, []);
+
+ return {
+ historyRecords: logRecords,
+ };
+}
diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/utils.test.ts b/public/app/features/alerting/unified/components/rules/central-state-history/utils.test.ts
new file mode 100644
index 00000000000..547db61db8f
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rules/central-state-history/utils.test.ts
@@ -0,0 +1,12 @@
+import fixtureData from './__fixtures__/alert-state-history';
+import { historyResultToDataFrame } from './utils';
+
+describe('historyResultToDataFrame', () => {
+ it('should decode', () => {
+ expect(historyResultToDataFrame(fixtureData)).toMatchSnapshot();
+ });
+
+ it('should decode and filter', () => {
+ expect(historyResultToDataFrame(fixtureData, { stateFrom: 'Pending', stateTo: 'Alerting' })).toMatchSnapshot();
+ });
+});
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 e032858c69d..28c620b4605 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
@@ -12,7 +12,7 @@ import {
getDisplayProcessor,
} from '@grafana/data';
import { fieldIndexComparer } from '@grafana/data/src/field/fieldComparers';
-import { isGrafanaAlertState, mapStateWithReasonToBaseState } from 'app/types/unified-alerting-dto';
+import { mapStateWithReasonToBaseState } from 'app/types/unified-alerting-dto';
import { labelsMatchMatchers } from '../../../utils/alertmanager';
import { parsePromQLStyleMatcherLooseSafe } from '../../../utils/matchers';
@@ -23,43 +23,43 @@ 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
+
+const emptyFilters = {
+ stateTo: 'all',
+ stateFrom: 'all',
+};
+
/*
* This function is used to convert the history response to a DataFrame list and filter the data by labels and states
* The response is a list of log records, each log record has a timestamp and a line.
* We group all records by alert instance (unique set of labels) and create a DataFrame for each group (instance).
* This allows us to be able to filter by labels and states in the groupDataFramesByTime function.
*/
-export function historyResultToDataFrame(data: DataFrameJSON): DataFrame[] {
- // Get the labels and states filters from the URL
- const stateToInQueryParams = getStateFilterToInQueryParams();
- const stateFromInQueryParams = getStateFilterFromInQueryParams();
- const stateToFilterValue = stateToInQueryParams === '' ? StateFilterValues.all : stateToInQueryParams;
- const stateFromFilterValue = stateFromInQueryParams === '' ? StateFilterValues.all : stateFromInQueryParams;
+export function historyResultToDataFrame({ data }: DataFrameJSON, filters = emptyFilters): DataFrame[] {
+ const { stateTo, stateFrom } = filters;
// Extract timestamps and lines from the response
- const tsValues = data?.data?.values[0] ?? [];
- const timestamps: number[] = isNumbers(tsValues) ? tsValues : [];
- const lines = data?.data?.values[1] ?? [];
+ const [tsValues = [], lines = []] = data?.values ?? [];
+ const timestamps = isNumbers(tsValues) ? tsValues : [];
// Filter log records by state and create a list of log records with the timestamp and line
- const logRecords = timestamps.reduce((acc: LogRecord[], timestamp: number, index: number) => {
+ const logRecords = timestamps.reduce((acc, timestamp: number, index: number) => {
const line = lines[index];
- // values property can be undefined for some instance states (e.g. NoData)
- if (isLine(line)) {
- if (!isGrafanaAlertState(line.current)) {
- return acc;
- }
- // we have to filter out by state at that point , because we are going to group by timestamp and these states are going to be lost
- const baseStateTo = mapStateWithReasonToBaseState(line.current);
- const baseStateFrom = mapStateWithReasonToBaseState(line.previous);
- const stateToMatch = stateToFilterValue !== StateFilterValues.all ? stateToFilterValue === baseStateTo : true;
- const stateFromMatch =
- stateFromFilterValue !== StateFilterValues.all ? stateFromFilterValue === baseStateFrom : true;
- // filter by state
- if (stateToMatch && stateFromMatch) {
- acc.push({ timestamp, line });
- }
+ if (!isLine(line)) {
+ return acc;
}
+
+ // we have to filter out by state at that point , because we are going to group by timestamp and these states are going to be lost
+ const baseStateTo = mapStateWithReasonToBaseState(line.current);
+ const baseStateFrom = mapStateWithReasonToBaseState(line.previous);
+ const stateToMatch = stateTo !== StateFilterValues.all ? stateTo === baseStateTo : true;
+ const stateFromMatch = stateFrom !== StateFilterValues.all ? stateFrom === baseStateFrom : true;
+
+ // filter by state
+ if (stateToMatch && stateFromMatch) {
+ acc.push({ timestamp, line });
+ }
+
return acc;
}, []);
@@ -79,19 +79,21 @@ export function historyResultToDataFrame(data: DataFrameJSON): DataFrame[] {
}
// Scenes sync variables in the URL adding a prefix to the variable name.
-function getLabelsFilterInQueryParams() {
+export function getLabelsFilterInQueryParams() {
const queryParams = new URLSearchParams(window.location.search);
return queryParams.get(`${QUERY_PARAM_PREFIX}${LABELS_FILTER}`) ?? '';
}
-function getStateFilterToInQueryParams() {
+
+export function getStateFilterToInQueryParams() {
const queryParams = new URLSearchParams(window.location.search);
- return queryParams.get(`${QUERY_PARAM_PREFIX}${STATE_FILTER_TO}`) ?? '';
+ return queryParams.get(`${QUERY_PARAM_PREFIX}${STATE_FILTER_TO}`) ?? StateFilterValues.all;
}
-function getStateFilterFromInQueryParams() {
+export function getStateFilterFromInQueryParams() {
const queryParams = new URLSearchParams(window.location.search);
- return queryParams.get(`${QUERY_PARAM_PREFIX}${STATE_FILTER_FROM}`) ?? '';
+ return queryParams.get(`${QUERY_PARAM_PREFIX}${STATE_FILTER_FROM}`) ?? StateFilterValues.all;
}
+
/*
* This function groups the data frames by time and filters them by labels.
* The interval is set to 10 seconds.