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.