From e9e59895490d41a26467c463aa32b5d2a1459d2e Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 13 Aug 2024 14:34:38 +0200 Subject: [PATCH] Alerting: Introduce useProduceNewAlertmanagerConfiguration (#91623) useProduceNewAlertmanagerConfiguration --- package.json | 1 + .../unified/hooks/mergeRequestStates.ts | 19 ++++++ .../hooks/useProduceNewAlertmanagerConfig.ts | 64 +++++++++++++++++++ yarn.lock | 8 +++ 4 files changed, 92 insertions(+) create mode 100644 public/app/features/alerting/unified/hooks/mergeRequestStates.ts create mode 100644 public/app/features/alerting/unified/hooks/useProduceNewAlertmanagerConfig.ts diff --git a/package.json b/package.json index b9fcda3167c..ce28afc6e5b 100644 --- a/package.json +++ b/package.json @@ -383,6 +383,7 @@ "react-window": "1.8.10", "react-window-infinite-loader": "1.0.9", "react-zoom-pan-pinch": "^3.3.0", + "reduce-reducers": "^1.0.4", "redux": "5.0.1", "redux-thunk": "3.1.0", "regenerator-runtime": "0.14.1", diff --git a/public/app/features/alerting/unified/hooks/mergeRequestStates.ts b/public/app/features/alerting/unified/hooks/mergeRequestStates.ts new file mode 100644 index 00000000000..f57f6b1d415 --- /dev/null +++ b/public/app/features/alerting/unified/hooks/mergeRequestStates.ts @@ -0,0 +1,19 @@ +interface RequestState { + error?: unknown; + + isUninitialized: boolean; + isSuccess: boolean; + isError: boolean; + isLoading: boolean; +} + +// @TODO what to do with the other props that we get from RTKQ's state such as originalArgs, etc? +export function mergeRequestStates(...states: RequestState[]): RequestState { + return { + error: states.find((s) => s.error), + isUninitialized: states.every((s) => s.isUninitialized), + isSuccess: states.every((s) => s.isSuccess), + isError: states.some((s) => s.isError), + isLoading: states.some((s) => s.isLoading), + }; +} diff --git a/public/app/features/alerting/unified/hooks/useProduceNewAlertmanagerConfig.ts b/public/app/features/alerting/unified/hooks/useProduceNewAlertmanagerConfig.ts new file mode 100644 index 00000000000..090591fa486 --- /dev/null +++ b/public/app/features/alerting/unified/hooks/useProduceNewAlertmanagerConfig.ts @@ -0,0 +1,64 @@ +import { Action } from '@reduxjs/toolkit'; +import reduceReducers from 'reduce-reducers'; + +import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; + +import { alertmanagerApi } from '../api/alertmanagerApi'; +import { useAlertmanager } from '../state/AlertmanagerContext'; + +import { mergeRequestStates } from './mergeRequestStates'; + +const ERR_NO_ACTIVE_AM = new Error('no active Alertmanager'); + +const { useLazyGetAlertmanagerConfigurationQuery, useUpdateAlertmanagerConfigurationMutation } = alertmanagerApi; + +export const initialAlertmanagerConfiguration: AlertManagerCortexConfig = { + alertmanager_config: { + receivers: [], + route: {}, + time_intervals: [], + mute_time_intervals: [], + inhibit_rules: [], + templates: [], + }, + template_files: {}, +}; + +const configurationReducer = reduceReducers(initialAlertmanagerConfiguration); + +/** + * This hook will make sure we are always applying actions that mutate the Alertmanager configuration + * on top of the latest Alertmanager configuration object. + */ +export function useProduceNewAlertmanagerConfiguration() { + const { selectedAlertmanager } = useAlertmanager(); + + const [fetchAlertmanagerConfig, fetchAlertmanagerState] = useLazyGetAlertmanagerConfigurationQuery(); + const [updateAlertManager, updateAlertmanagerState] = useUpdateAlertmanagerConfigurationMutation(); + + const newConfigurationState = mergeRequestStates(fetchAlertmanagerState, updateAlertmanagerState); + + if (!selectedAlertmanager) { + throw ERR_NO_ACTIVE_AM; + } + + /** + * This function will fetch the latest Alertmanager configuration, apply a diff to it via a reducer and + * returns the result. + * + * ┌────────────────────────────┐ ┌───────────────┐ ┌───────────────────┐ + * │ fetch latest configuration │─▶│ apply reducer │─▶│ new rule config │ + * └────────────────────────────┘ └───────────────┘ └───────────────────┘ + */ + const produceNewAlertmanagerConfiguration = async (action: Action) => { + const currentAlertmanagerConfiguration = await fetchAlertmanagerConfig(selectedAlertmanager).unwrap(); + const newConfig = configurationReducer(currentAlertmanagerConfiguration, action); + + return updateAlertManager({ + selectedAlertmanager, + config: newConfig, + }).unwrap(); + }; + + return [produceNewAlertmanagerConfiguration, newConfigurationState] as const; +} diff --git a/yarn.lock b/yarn.lock index fc049ff3b85..e2fcec7826c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17865,6 +17865,7 @@ __metadata: react-window: "npm:1.8.10" react-window-infinite-loader: "npm:1.0.9" react-zoom-pan-pinch: "npm:^3.3.0" + reduce-reducers: "npm:^1.0.4" redux: "npm:5.0.1" redux-mock-store: "npm:1.5.4" redux-thunk: "npm:3.1.0" @@ -26635,6 +26636,13 @@ __metadata: languageName: node linkType: hard +"reduce-reducers@npm:^1.0.4": + version: 1.0.4 + resolution: "reduce-reducers@npm:1.0.4" + checksum: 10/9b3648fa505a3ac7834cd8150ec98f9915e4020c8df0e9a4eddb5d3615814067943913f2582d0be4763ad6583209c0cf7ad3cc1021745b9b8e6ace768d49f6a9 + languageName: node + linkType: hard + "redux-mock-store@npm:1.5.4": version: 1.5.4 resolution: "redux-mock-store@npm:1.5.4"