mirror of https://github.com/grafana/grafana
Alerting: use new Alertmanager update hooks for mute and active time intervals (#91404)
parent
7b919e3277
commit
0a2db346ab
@ -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), |
||||
}; |
||||
} |
@ -1,27 +0,0 @@ |
||||
import { useMemo } from 'react'; |
||||
|
||||
import { SelectableValue } from '@grafana/data'; |
||||
|
||||
import { mergeTimeIntervals } from '../components/mute-timings/util'; |
||||
import { useAlertmanager } from '../state/AlertmanagerContext'; |
||||
import { timeIntervalToString } from '../utils/alertmanager'; |
||||
|
||||
import { useAlertmanagerConfig } from './useAlertmanagerConfig'; |
||||
|
||||
export function useMuteTimingOptions(): Array<SelectableValue<string>> { |
||||
const { selectedAlertmanager } = useAlertmanager(); |
||||
const { currentData } = useAlertmanagerConfig(selectedAlertmanager); |
||||
const config = currentData?.alertmanager_config; |
||||
|
||||
return useMemo(() => { |
||||
const time_intervals = config ? mergeTimeIntervals(config) : []; |
||||
const muteTimingsOptions: Array<SelectableValue<string>> = |
||||
time_intervals?.map((value) => ({ |
||||
value: value.name, |
||||
label: value.name, |
||||
description: value.time_intervals.map((interval) => timeIntervalToString(interval)).join(', AND '), |
||||
})) ?? []; |
||||
|
||||
return muteTimingsOptions; |
||||
}, [config]); |
||||
} |
@ -0,0 +1,329 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`mute timings should be able to add a new mute timing 1`] = ` |
||||
{ |
||||
"alertmanager_config": { |
||||
"mute_time_intervals": [ |
||||
{ |
||||
"name": "default legacy time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
"route": { |
||||
"routes": [ |
||||
{ |
||||
"mute_time_intervals": [ |
||||
"default time interval", |
||||
], |
||||
}, |
||||
{ |
||||
"mute_time_intervals": [ |
||||
"default legacy time interval", |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
"time_intervals": [ |
||||
{ |
||||
"name": "default time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
"name": "new mute time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
"template_files": {}, |
||||
} |
||||
`; |
||||
|
||||
exports[`mute timings should be able to remove a mute timing 1`] = ` |
||||
{ |
||||
"alertmanager_config": { |
||||
"mute_time_intervals": [], |
||||
"route": { |
||||
"active_time_intervals": [], |
||||
"mute_time_intervals": [], |
||||
"routes": [ |
||||
{ |
||||
"active_time_intervals": [], |
||||
"mute_time_intervals": [ |
||||
"default time interval", |
||||
], |
||||
"routes": undefined, |
||||
}, |
||||
{ |
||||
"active_time_intervals": [], |
||||
"mute_time_intervals": [], |
||||
"routes": undefined, |
||||
}, |
||||
], |
||||
}, |
||||
"time_intervals": [ |
||||
{ |
||||
"name": "default time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
"template_files": {}, |
||||
} |
||||
`; |
||||
|
||||
exports[`mute timings should be able to remove a mute timing 2`] = ` |
||||
{ |
||||
"alertmanager_config": { |
||||
"mute_time_intervals": [ |
||||
{ |
||||
"name": "default legacy time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
"route": { |
||||
"active_time_intervals": [], |
||||
"mute_time_intervals": [], |
||||
"routes": [ |
||||
{ |
||||
"active_time_intervals": [], |
||||
"mute_time_intervals": [], |
||||
"routes": undefined, |
||||
}, |
||||
{ |
||||
"active_time_intervals": [], |
||||
"mute_time_intervals": [ |
||||
"default legacy time interval", |
||||
], |
||||
"routes": undefined, |
||||
}, |
||||
], |
||||
}, |
||||
"time_intervals": [], |
||||
}, |
||||
"template_files": {}, |
||||
} |
||||
`; |
||||
|
||||
exports[`mute timings should be able to update a time interval 1`] = ` |
||||
{ |
||||
"alertmanager_config": { |
||||
"mute_time_intervals": [ |
||||
{ |
||||
"name": "new mute time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
"route": { |
||||
"active_time_intervals": undefined, |
||||
"mute_time_intervals": undefined, |
||||
"routes": [ |
||||
{ |
||||
"active_time_intervals": undefined, |
||||
"mute_time_intervals": [ |
||||
"default time interval", |
||||
], |
||||
"routes": undefined, |
||||
}, |
||||
{ |
||||
"active_time_intervals": undefined, |
||||
"mute_time_intervals": [ |
||||
"new mute time interval", |
||||
], |
||||
"routes": undefined, |
||||
}, |
||||
], |
||||
}, |
||||
"time_intervals": [ |
||||
{ |
||||
"name": "default time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
"template_files": {}, |
||||
} |
||||
`; |
||||
|
||||
exports[`mute timings should be able to update a time interval 2`] = ` |
||||
{ |
||||
"alertmanager_config": { |
||||
"mute_time_intervals": [ |
||||
{ |
||||
"name": "default legacy time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
"route": { |
||||
"active_time_intervals": undefined, |
||||
"mute_time_intervals": undefined, |
||||
"routes": [ |
||||
{ |
||||
"active_time_intervals": undefined, |
||||
"mute_time_intervals": [ |
||||
"new mute time interval", |
||||
], |
||||
"routes": undefined, |
||||
}, |
||||
{ |
||||
"active_time_intervals": undefined, |
||||
"mute_time_intervals": [ |
||||
"default legacy time interval", |
||||
], |
||||
"routes": undefined, |
||||
}, |
||||
], |
||||
}, |
||||
"time_intervals": [ |
||||
{ |
||||
"name": "new mute time interval", |
||||
"time_intervals": [ |
||||
{ |
||||
"days_of_month": [ |
||||
"15", |
||||
], |
||||
"months": [ |
||||
"august:december", |
||||
"march", |
||||
], |
||||
"times": [ |
||||
{ |
||||
"end_time": "24:00", |
||||
"start_time": "12:00", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
"template_files": {}, |
||||
} |
||||
`; |
@ -0,0 +1,84 @@ |
||||
import { UnknownAction } from 'redux'; |
||||
|
||||
import { AlertManagerCortexConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types'; |
||||
|
||||
import { addMuteTimingAction, deleteMuteTimingAction, muteTimingsReducer, updateMuteTimingAction } from './muteTimings'; |
||||
|
||||
describe('mute timings', () => { |
||||
const initialConfig: AlertManagerCortexConfig = { |
||||
alertmanager_config: { |
||||
time_intervals: [mockTimeInterval({ name: 'default time interval' })], |
||||
mute_time_intervals: [mockTimeInterval({ name: 'default legacy time interval' })], |
||||
route: { |
||||
routes: [ |
||||
{ |
||||
mute_time_intervals: ['default time interval'], |
||||
}, |
||||
{ |
||||
mute_time_intervals: ['default legacy time interval'], |
||||
}, |
||||
], |
||||
}, |
||||
}, |
||||
template_files: {}, |
||||
}; |
||||
|
||||
it('should be able to add a new mute timing', () => { |
||||
const newMuteTiming = mockTimeInterval({ name: 'new mute time interval' }); |
||||
const action = addMuteTimingAction({ interval: newMuteTiming }); |
||||
|
||||
expect(muteTimingsReducer(initialConfig, action)).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should be able to remove a mute timing', () => { |
||||
const legacyMuteTimingName = initialConfig.alertmanager_config.mute_time_intervals![0].name; |
||||
const deleteFromLegacyKey = deleteMuteTimingAction({ name: legacyMuteTimingName }); |
||||
expect(muteTimingsReducer(initialConfig, deleteFromLegacyKey)).toMatchSnapshot(); |
||||
|
||||
const muteTimingName = initialConfig.alertmanager_config.time_intervals![0].name; |
||||
const deleteMuteTiming = deleteMuteTimingAction({ name: muteTimingName }); |
||||
expect(muteTimingsReducer(initialConfig, deleteMuteTiming)).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should be able to update a time interval', () => { |
||||
const newMuteTiming = mockTimeInterval({ name: 'new mute time interval' }); |
||||
|
||||
const legacyMuteTimingName = initialConfig.alertmanager_config.mute_time_intervals![0].name; |
||||
const updateLegacyMuteTiming = updateMuteTimingAction({ |
||||
originalName: legacyMuteTimingName, |
||||
interval: newMuteTiming, |
||||
}); |
||||
expect(muteTimingsReducer(initialConfig, updateLegacyMuteTiming)).toMatchSnapshot(); |
||||
|
||||
const muteTimingName = initialConfig.alertmanager_config.time_intervals![0].name; |
||||
const updateMuteTiming = updateMuteTimingAction({ originalName: muteTimingName, interval: newMuteTiming }); |
||||
expect(muteTimingsReducer(initialConfig, updateMuteTiming)).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should throw for unknown action', () => { |
||||
const action: UnknownAction = { type: 'unknown' }; |
||||
|
||||
expect(() => { |
||||
muteTimingsReducer(initialConfig, action); |
||||
}).toThrow('unknown'); |
||||
}); |
||||
}); |
||||
|
||||
function mockTimeInterval(overrides: Partial<MuteTimeInterval> = {}): MuteTimeInterval { |
||||
return { |
||||
name: 'mock time interval', |
||||
time_intervals: [ |
||||
{ |
||||
times: [ |
||||
{ |
||||
start_time: '12:00', |
||||
end_time: '24:00', |
||||
}, |
||||
], |
||||
days_of_month: ['15'], |
||||
months: ['august:december', 'march'], |
||||
}, |
||||
], |
||||
...overrides, |
||||
}; |
||||
} |
@ -0,0 +1,75 @@ |
||||
import { createAction, createReducer } from '@reduxjs/toolkit'; |
||||
import { remove } from 'lodash'; |
||||
|
||||
import { AlertManagerCortexConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types'; |
||||
|
||||
import { removeTimeIntervalFromRoute, renameTimeInterval } from '../../utils/alertmanager'; |
||||
|
||||
export const addMuteTimingAction = createAction<{ interval: MuteTimeInterval }>('muteTiming/add'); |
||||
export const updateMuteTimingAction = createAction<{ |
||||
interval: MuteTimeInterval; |
||||
originalName: string; |
||||
}>('muteTiming/update'); |
||||
export const deleteMuteTimingAction = createAction<{ name: string }>('muteTiming/delete'); |
||||
|
||||
const initialState: AlertManagerCortexConfig = { |
||||
alertmanager_config: {}, |
||||
template_files: {}, |
||||
}; |
||||
|
||||
/** |
||||
* This reducer will manage action related to mute timings and make sure all operations on the alertmanager |
||||
* configuration happen immutably and only mutate what they need. |
||||
*/ |
||||
export const muteTimingsReducer = createReducer(initialState, (builder) => { |
||||
builder |
||||
// add a mute timing to the alertmanager configuration
|
||||
.addCase(addMuteTimingAction, (draft, { payload }) => { |
||||
const { interval } = payload; |
||||
draft.alertmanager_config.time_intervals = (draft.alertmanager_config.time_intervals ?? []).concat(interval); |
||||
}) |
||||
// add a mute timing to the alertmanager configuration
|
||||
// make sure we update the mute timing in either the deprecated or the new time intervals property
|
||||
.addCase(updateMuteTimingAction, (draft, { payload }) => { |
||||
const { interval, originalName } = payload; |
||||
const nameHasChanged = interval.name !== originalName; |
||||
|
||||
const timeIntervals = draft.alertmanager_config.time_intervals ?? []; |
||||
const muteTimeIntervals = draft.alertmanager_config.mute_time_intervals ?? []; |
||||
|
||||
const existingIntervalIndex = timeIntervals.findIndex(({ name }) => name === originalName); |
||||
if (existingIntervalIndex !== -1) { |
||||
timeIntervals[existingIntervalIndex] = interval; |
||||
} |
||||
|
||||
const existingMuteIntervalIndex = muteTimeIntervals.findIndex(({ name }) => name === originalName); |
||||
if (existingMuteIntervalIndex !== -1) { |
||||
muteTimeIntervals[existingMuteIntervalIndex] = interval; |
||||
} |
||||
|
||||
if (nameHasChanged && draft.alertmanager_config.route) { |
||||
draft.alertmanager_config.route = renameTimeInterval( |
||||
interval.name, |
||||
originalName, |
||||
draft.alertmanager_config.route |
||||
); |
||||
} |
||||
}) |
||||
// delete a mute timing from the alertmanager configuration, since the configuration might be using the "deprecated" mute_time_intervals
|
||||
// let's also check there
|
||||
.addCase(deleteMuteTimingAction, (draft, { payload }) => { |
||||
const { name } = payload; |
||||
const { alertmanager_config } = draft; |
||||
const { time_intervals = [], mute_time_intervals = [] } = alertmanager_config; |
||||
|
||||
// remove the mute timings from the legacy and new time intervals definition
|
||||
remove(time_intervals, (interval) => interval.name === name); |
||||
remove(mute_time_intervals, (interval) => interval.name === name); |
||||
|
||||
// remove the mute timing from all routes
|
||||
alertmanager_config.route = removeTimeIntervalFromRoute(name, alertmanager_config.route ?? {}); |
||||
}) |
||||
.addDefaultCase((_state, action) => { |
||||
throw new Error(`Unknown action for mute timing reducer: ${action.type}`); |
||||
}); |
||||
}); |
Loading…
Reference in new issue