mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Use time_intervals instead of the deprecated mute_time_intervals in a… (#83147)
* Use time_intervals instead of the deprecated mute_time_intervals in alert manager config * don't send mute_time_intervals in the payload * Add and update tests * Fix usecase when having both fields in response from getting alert manager config * Use mute_timings for grafana data source and both for cloud data source when deleting mute timing * Use mute_timings for grafana data source and both for cloud data source when saving a new or existing alert rule * Address first code review * Address more review comments
This commit is contained in:
parent
90e7791086
commit
ed3c36bb46
@ -1,4 +1,4 @@
|
|||||||
import { render, waitFor, fireEvent, within } from '@testing-library/react';
|
import { fireEvent, render, waitFor, within } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TestProvider } from 'test/helpers/TestProvider';
|
import { TestProvider } from 'test/helpers/TestProvider';
|
||||||
@ -10,7 +10,7 @@ import { AccessControlAction } from 'app/types';
|
|||||||
|
|
||||||
import MuteTimings from './MuteTimings';
|
import MuteTimings from './MuteTimings';
|
||||||
import { fetchAlertManagerConfig, updateAlertManagerConfig } from './api/alertmanager';
|
import { fetchAlertManagerConfig, updateAlertManagerConfig } from './api/alertmanager';
|
||||||
import { grantUserPermissions, mockDataSource, MockDataSourceSrv } from './mocks';
|
import { MockDataSourceSrv, grantUserPermissions, mockDataSource } from './mocks';
|
||||||
import { DataSourceType } from './utils/datasource';
|
import { DataSourceType } from './utils/datasource';
|
||||||
|
|
||||||
jest.mock('./api/alertmanager');
|
jest.mock('./api/alertmanager');
|
||||||
@ -71,6 +71,21 @@ const muteTimeInterval: MuteTimeInterval = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
const muteTimeInterval2: MuteTimeInterval = {
|
||||||
|
name: 'default-mute2',
|
||||||
|
time_intervals: [
|
||||||
|
{
|
||||||
|
times: [
|
||||||
|
{
|
||||||
|
start_time: '12:00',
|
||||||
|
end_time: '24:00',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
days_of_month: ['15', '-1'],
|
||||||
|
months: ['august:december', 'march'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const defaultConfig: AlertManagerCortexConfig = {
|
const defaultConfig: AlertManagerCortexConfig = {
|
||||||
alertmanager_config: {
|
alertmanager_config: {
|
||||||
@ -90,6 +105,44 @@ const defaultConfig: AlertManagerCortexConfig = {
|
|||||||
},
|
},
|
||||||
template_files: {},
|
template_files: {},
|
||||||
};
|
};
|
||||||
|
const defaultConfigWithNewTimeIntervalsField: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {
|
||||||
|
receivers: [{ name: 'default' }, { name: 'critical' }],
|
||||||
|
route: {
|
||||||
|
receiver: 'default',
|
||||||
|
group_by: ['alertname'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
matchers: ['env=prod', 'region!=EU'],
|
||||||
|
mute_time_intervals: [muteTimeInterval.name],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
templates: [],
|
||||||
|
time_intervals: [muteTimeInterval],
|
||||||
|
},
|
||||||
|
template_files: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultConfigWithBothTimeIntervalsField: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {
|
||||||
|
receivers: [{ name: 'default' }, { name: 'critical' }],
|
||||||
|
route: {
|
||||||
|
receiver: 'default',
|
||||||
|
group_by: ['alertname'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
matchers: ['env=prod', 'region!=EU'],
|
||||||
|
mute_time_intervals: [muteTimeInterval.name],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
templates: [],
|
||||||
|
time_intervals: [muteTimeInterval],
|
||||||
|
mute_time_intervals: [muteTimeInterval2],
|
||||||
|
},
|
||||||
|
template_files: {},
|
||||||
|
};
|
||||||
|
|
||||||
const resetMocks = () => {
|
const resetMocks = () => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
@ -110,7 +163,7 @@ describe('Mute timings', () => {
|
|||||||
grantUserPermissions(Object.values(AccessControlAction));
|
grantUserPermissions(Object.values(AccessControlAction));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a new mute timing', async () => {
|
it('creates a new mute timing, with mute_time_intervals in config', async () => {
|
||||||
renderMuteTimings();
|
renderMuteTimings();
|
||||||
|
|
||||||
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled());
|
||||||
@ -125,10 +178,12 @@ describe('Mute timings', () => {
|
|||||||
fireEvent.submit(ui.form.get());
|
fireEvent.submit(ui.form.get());
|
||||||
|
|
||||||
await waitFor(() => expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled());
|
await waitFor(() => expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled());
|
||||||
|
|
||||||
|
const { mute_time_intervals: _, ...configWithoutMuteTimings } = defaultConfig.alertmanager_config;
|
||||||
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith('grafana', {
|
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith('grafana', {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
alertmanager_config: {
|
alertmanager_config: {
|
||||||
...defaultConfig.alertmanager_config,
|
...configWithoutMuteTimings,
|
||||||
mute_time_intervals: [
|
mute_time_intervals: [
|
||||||
muteTimeInterval,
|
muteTimeInterval,
|
||||||
{
|
{
|
||||||
@ -151,6 +206,102 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('creates a new mute timing, with time_intervals in config', async () => {
|
||||||
|
mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
...defaultConfigWithNewTimeIntervalsField,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
renderMuteTimings();
|
||||||
|
|
||||||
|
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled());
|
||||||
|
expect(ui.nameField.get()).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.type(ui.nameField.get(), 'maintenance period');
|
||||||
|
await userEvent.type(ui.startsAt.get(), '22:00');
|
||||||
|
await userEvent.type(ui.endsAt.get(), '24:00');
|
||||||
|
await userEvent.type(ui.days.get(), '-1');
|
||||||
|
await userEvent.type(ui.months.get(), 'january, july');
|
||||||
|
|
||||||
|
fireEvent.submit(ui.form.get());
|
||||||
|
|
||||||
|
await waitFor(() => expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled());
|
||||||
|
|
||||||
|
const { mute_time_intervals: _, ...configWithoutMuteTimings } = defaultConfig.alertmanager_config;
|
||||||
|
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith('grafana', {
|
||||||
|
...defaultConfig,
|
||||||
|
alertmanager_config: {
|
||||||
|
...configWithoutMuteTimings,
|
||||||
|
mute_time_intervals: [
|
||||||
|
muteTimeInterval,
|
||||||
|
{
|
||||||
|
name: 'maintenance period',
|
||||||
|
time_intervals: [
|
||||||
|
{
|
||||||
|
days_of_month: ['-1'],
|
||||||
|
months: ['january', 'july'],
|
||||||
|
times: [
|
||||||
|
{
|
||||||
|
start_time: '22:00',
|
||||||
|
end_time: '24:00',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('creates a new mute timing, with time_intervals and mute_time_intervals in config', async () => {
|
||||||
|
mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
...defaultConfigWithBothTimeIntervalsField,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
renderMuteTimings();
|
||||||
|
|
||||||
|
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled());
|
||||||
|
expect(ui.nameField.get()).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.type(ui.nameField.get(), 'maintenance period');
|
||||||
|
await userEvent.type(ui.startsAt.get(), '22:00');
|
||||||
|
await userEvent.type(ui.endsAt.get(), '24:00');
|
||||||
|
await userEvent.type(ui.days.get(), '-1');
|
||||||
|
await userEvent.type(ui.months.get(), 'january, july');
|
||||||
|
|
||||||
|
fireEvent.submit(ui.form.get());
|
||||||
|
|
||||||
|
await waitFor(() => expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled());
|
||||||
|
|
||||||
|
const { mute_time_intervals, time_intervals, ...configWithoutMuteTimings } = defaultConfig.alertmanager_config;
|
||||||
|
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith('grafana', {
|
||||||
|
...defaultConfig,
|
||||||
|
alertmanager_config: {
|
||||||
|
...configWithoutMuteTimings,
|
||||||
|
mute_time_intervals: [
|
||||||
|
muteTimeInterval,
|
||||||
|
muteTimeInterval2,
|
||||||
|
{
|
||||||
|
name: 'maintenance period',
|
||||||
|
time_intervals: [
|
||||||
|
{
|
||||||
|
days_of_month: ['-1'],
|
||||||
|
months: ['january', 'july'],
|
||||||
|
times: [
|
||||||
|
{
|
||||||
|
start_time: '22:00',
|
||||||
|
end_time: '24:00',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('prepopulates the form when editing a mute timing', async () => {
|
it('prepopulates the form when editing a mute timing', async () => {
|
||||||
renderMuteTimings('/alerting/routes/mute-timing/edit' + `?muteName=${encodeURIComponent(muteTimeInterval.name)}`);
|
renderMuteTimings('/alerting/routes/mute-timing/edit' + `?muteName=${encodeURIComponent(muteTimeInterval.name)}`);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Route, Redirect, Switch, useRouteMatch } from 'react-router-dom';
|
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom';
|
||||||
|
|
||||||
import { NavModelItem } from '@grafana/data';
|
import { NavModelItem } from '@grafana/data';
|
||||||
import { Alert } from '@grafana/ui';
|
import { Alert } from '@grafana/ui';
|
||||||
@ -21,8 +21,9 @@ const MuteTimings = () => {
|
|||||||
const config = currentData?.alertmanager_config;
|
const config = currentData?.alertmanager_config;
|
||||||
|
|
||||||
const getMuteTimingByName = useCallback(
|
const getMuteTimingByName = useCallback(
|
||||||
(id: string): MuteTimeInterval | undefined => {
|
(id: string, fromTimeIntervals: boolean): MuteTimeInterval | undefined => {
|
||||||
const timing = config?.mute_time_intervals?.find(({ name }: MuteTimeInterval) => name === id);
|
const time_intervals = fromTimeIntervals ? config?.time_intervals ?? [] : config?.mute_time_intervals ?? [];
|
||||||
|
const timing = time_intervals.find(({ name }: MuteTimeInterval) => name === id);
|
||||||
|
|
||||||
if (timing) {
|
if (timing) {
|
||||||
const provenance = config?.muteTimeProvenances?.[timing.name];
|
const provenance = config?.muteTimeProvenances?.[timing.name];
|
||||||
@ -53,13 +54,17 @@ const MuteTimings = () => {
|
|||||||
<Route exact path="/alerting/routes/mute-timing/edit">
|
<Route exact path="/alerting/routes/mute-timing/edit">
|
||||||
{() => {
|
{() => {
|
||||||
if (queryParams['muteName']) {
|
if (queryParams['muteName']) {
|
||||||
const muteTiming = getMuteTimingByName(String(queryParams['muteName']));
|
const muteTimingInMuteTimings = getMuteTimingByName(String(queryParams['muteName']), false);
|
||||||
|
const muteTimingInTimeIntervals = getMuteTimingByName(String(queryParams['muteName']), true);
|
||||||
|
const inTimeIntervals = Boolean(muteTimingInTimeIntervals);
|
||||||
|
const muteTiming = inTimeIntervals ? muteTimingInTimeIntervals : muteTimingInMuteTimings;
|
||||||
const provenance = muteTiming?.provenance;
|
const provenance = muteTiming?.provenance;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuteTimingForm
|
<MuteTimingForm
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
muteTiming={muteTiming}
|
fromLegacyTimeInterval={muteTimingInMuteTimings}
|
||||||
|
fromTimeIntervals={muteTimingInTimeIntervals}
|
||||||
showError={!muteTiming && !isLoading}
|
showError={!muteTiming && !isLoading}
|
||||||
provenance={provenance}
|
provenance={provenance}
|
||||||
/>
|
/>
|
||||||
|
@ -15,6 +15,7 @@ import { useGetContactPointsState } from './api/receiversApi';
|
|||||||
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
|
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
|
||||||
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
||||||
import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable';
|
import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable';
|
||||||
|
import { mergeTimeIntervals } from './components/mute-timings/util';
|
||||||
import {
|
import {
|
||||||
NotificationPoliciesFilter,
|
NotificationPoliciesFilter,
|
||||||
findRoutesByMatchers,
|
findRoutesByMatchers,
|
||||||
@ -191,8 +192,9 @@ const AmRoutes = () => {
|
|||||||
if (!selectedAlertmanager) {
|
if (!selectedAlertmanager) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const time_intervals = result?.alertmanager_config ? mergeTimeIntervals(result?.alertmanager_config) : [];
|
||||||
|
|
||||||
const numberOfMuteTimings = result?.alertmanager_config.mute_time_intervals?.length ?? 0;
|
const numberOfMuteTimings = time_intervals.length;
|
||||||
const haveData = result && !resultError && !resultLoading;
|
const haveData = result && !resultError && !resultLoading;
|
||||||
const isFetching = !result && resultLoading;
|
const isFetching = !result && resultLoading;
|
||||||
const haveError = resultError && !resultLoading;
|
const haveError = resultError && !resultLoading;
|
||||||
|
@ -12,6 +12,7 @@ import { useAlertmanager } from '../../state/AlertmanagerContext';
|
|||||||
import { updateAlertManagerConfigAction } from '../../state/actions';
|
import { updateAlertManagerConfigAction } from '../../state/actions';
|
||||||
import { MuteTimingFields } from '../../types/mute-timing-form';
|
import { MuteTimingFields } from '../../types/mute-timing-form';
|
||||||
import { renameMuteTimings } from '../../utils/alertmanager';
|
import { renameMuteTimings } from '../../utils/alertmanager';
|
||||||
|
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||||
import { makeAMLink } from '../../utils/misc';
|
import { makeAMLink } from '../../utils/misc';
|
||||||
import { createMuteTiming, defaultTimeInterval } from '../../utils/mute-timings';
|
import { createMuteTiming, defaultTimeInterval } from '../../utils/mute-timings';
|
||||||
import { ProvisionedResource, ProvisioningAlert } from '../Provisioning';
|
import { ProvisionedResource, ProvisioningAlert } from '../Provisioning';
|
||||||
@ -19,7 +20,8 @@ import { ProvisionedResource, ProvisioningAlert } from '../Provisioning';
|
|||||||
import { MuteTimingTimeInterval } from './MuteTimingTimeInterval';
|
import { MuteTimingTimeInterval } from './MuteTimingTimeInterval';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
muteTiming?: MuteTimeInterval;
|
fromLegacyTimeInterval?: MuteTimeInterval; // mute time interval when comes from the old config , mute_time_intervals
|
||||||
|
fromTimeIntervals?: MuteTimeInterval; // mute time interval when comes from the new config , time_intervals. These two fields are mutually exclusive
|
||||||
showError?: boolean;
|
showError?: boolean;
|
||||||
provenance?: string;
|
provenance?: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
@ -50,7 +52,26 @@ const useDefaultValues = (muteTiming?: MuteTimeInterval): MuteTimingFields => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const MuteTimingForm = ({ muteTiming, showError, loading, provenance }: Props) => {
|
const replaceMuteTiming = (
|
||||||
|
originalTimings: MuteTimeInterval[],
|
||||||
|
existingTiming: MuteTimeInterval | undefined,
|
||||||
|
newTiming: MuteTimeInterval,
|
||||||
|
addNew: boolean
|
||||||
|
) => {
|
||||||
|
// we only add new timing if addNew is true. Otherwise, we just remove the existing timing
|
||||||
|
const originalTimingsWithoutNew = existingTiming
|
||||||
|
? originalTimings?.filter(({ name }) => name !== existingTiming.name)
|
||||||
|
: originalTimings;
|
||||||
|
return addNew ? [...originalTimingsWithoutNew, newTiming] : [...originalTimingsWithoutNew];
|
||||||
|
};
|
||||||
|
|
||||||
|
const MuteTimingForm = ({
|
||||||
|
fromLegacyTimeInterval: fromMuteTimings,
|
||||||
|
fromTimeIntervals,
|
||||||
|
showError,
|
||||||
|
loading,
|
||||||
|
provenance,
|
||||||
|
}: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { selectedAlertmanager } = useAlertmanager();
|
const { selectedAlertmanager } = useAlertmanager();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
@ -60,6 +81,12 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provenance }: Props) =
|
|||||||
const { currentData: result } = useAlertmanagerConfig(selectedAlertmanager);
|
const { currentData: result } = useAlertmanagerConfig(selectedAlertmanager);
|
||||||
const config = result?.alertmanager_config;
|
const config = result?.alertmanager_config;
|
||||||
|
|
||||||
|
const fromIntervals = Boolean(fromTimeIntervals);
|
||||||
|
const muteTiming = fromIntervals ? fromTimeIntervals : fromMuteTimings;
|
||||||
|
|
||||||
|
const originalMuteTimings = config?.mute_time_intervals ?? [];
|
||||||
|
const originalTimeIntervals = config?.time_intervals ?? [];
|
||||||
|
|
||||||
const defaultValues = useDefaultValues(muteTiming);
|
const defaultValues = useDefaultValues(muteTiming);
|
||||||
const formApi = useForm({ defaultValues });
|
const formApi = useForm({ defaultValues });
|
||||||
|
|
||||||
@ -70,19 +97,44 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provenance }: Props) =
|
|||||||
|
|
||||||
const newMuteTiming = createMuteTiming(values);
|
const newMuteTiming = createMuteTiming(values);
|
||||||
|
|
||||||
const muteTimings = muteTiming
|
const isGrafanaDataSource = selectedAlertmanager === GRAFANA_RULES_SOURCE_NAME;
|
||||||
? config?.mute_time_intervals?.filter(({ name }) => name !== muteTiming.name)
|
const isNewMuteTiming = fromTimeIntervals === undefined && fromMuteTimings === undefined;
|
||||||
: config?.mute_time_intervals;
|
|
||||||
|
|
||||||
|
// If is Grafana data source, we wil save mute timings in the alertmanager_config.mute_time_intervals
|
||||||
|
// Otherwise, we will save it on alertmanager_config.time_intervals or alertmanager_config.mute_time_intervals depending on the original config
|
||||||
|
|
||||||
|
const newMutetimeIntervals = isGrafanaDataSource
|
||||||
|
? {
|
||||||
|
// for Grafana data source, we will save mute timings in the alertmanager_config.mute_time_intervals
|
||||||
|
mute_time_intervals: [
|
||||||
|
...replaceMuteTiming(originalTimeIntervals, fromTimeIntervals, newMuteTiming, false),
|
||||||
|
...replaceMuteTiming(originalMuteTimings, fromMuteTimings, newMuteTiming, true),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
// for non-Grafana data source, we will save mute timings in the alertmanager_config.time_intervals or alertmanager_config.mute_time_intervals depending on the original config
|
||||||
|
time_intervals: replaceMuteTiming(
|
||||||
|
originalTimeIntervals,
|
||||||
|
fromTimeIntervals,
|
||||||
|
newMuteTiming,
|
||||||
|
Boolean(fromTimeIntervals) || isNewMuteTiming
|
||||||
|
),
|
||||||
|
mute_time_intervals:
|
||||||
|
Boolean(fromMuteTimings) && !isNewMuteTiming
|
||||||
|
? replaceMuteTiming(originalMuteTimings, fromMuteTimings, newMuteTiming, true)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mute_time_intervals: _, time_intervals: __, ...configWithoutMuteTimings } = config ?? {};
|
||||||
const newConfig: AlertManagerCortexConfig = {
|
const newConfig: AlertManagerCortexConfig = {
|
||||||
...result,
|
...result,
|
||||||
alertmanager_config: {
|
alertmanager_config: {
|
||||||
...config,
|
...configWithoutMuteTimings,
|
||||||
route:
|
route:
|
||||||
muteTiming && newMuteTiming.name !== muteTiming.name
|
muteTiming && newMuteTiming.name !== muteTiming.name
|
||||||
? renameMuteTimings(newMuteTiming.name, muteTiming.name, config?.route ?? {})
|
? renameMuteTimings(newMuteTiming.name, muteTiming.name, config?.route ?? {})
|
||||||
: config?.route,
|
: config?.route,
|
||||||
mute_time_intervals: [...(muteTimings || []), newMuteTiming],
|
...newMutetimeIntervals,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -123,13 +175,8 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provenance }: Props) =
|
|||||||
<Input
|
<Input
|
||||||
{...formApi.register('name', {
|
{...formApi.register('name', {
|
||||||
required: true,
|
required: true,
|
||||||
validate: (value) => {
|
validate: (value) =>
|
||||||
if (!muteTiming) {
|
validateMuteTiming(value, muteTiming, originalMuteTimings, originalTimeIntervals),
|
||||||
const existingMuteTiming = config?.mute_time_intervals?.find(({ name }) => value === name);
|
|
||||||
return existingMuteTiming ? `Mute timing already exists for "${value}"` : true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
})}
|
})}
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
data-testid={'mute-timing-name'}
|
data-testid={'mute-timing-name'}
|
||||||
@ -156,6 +203,22 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provenance }: Props) =
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function validateMuteTiming(
|
||||||
|
value: string,
|
||||||
|
muteTiming: MuteTimeInterval | undefined,
|
||||||
|
originalMuteTimings: MuteTimeInterval[],
|
||||||
|
originalTimeIntervals: MuteTimeInterval[]
|
||||||
|
) {
|
||||||
|
if (!muteTiming) {
|
||||||
|
const existingMuteTimingInMuteTimings = originalMuteTimings?.find(({ name }) => value === name);
|
||||||
|
const existingMuteTimingInTimeIntervals = originalTimeIntervals?.find(({ name }) => value === name);
|
||||||
|
return existingMuteTimingInMuteTimings || existingMuteTimingInTimeIntervals
|
||||||
|
? `Mute timing already exists for "${value}"`
|
||||||
|
: true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
input: css`
|
input: css`
|
||||||
width: 400px;
|
width: 400px;
|
||||||
|
@ -18,7 +18,7 @@ import { ProvisioningBadge } from '../Provisioning';
|
|||||||
import { Spacer } from '../Spacer';
|
import { Spacer } from '../Spacer';
|
||||||
import { GrafanaMuteTimingsExporter } from '../export/GrafanaMuteTimingsExporter';
|
import { GrafanaMuteTimingsExporter } from '../export/GrafanaMuteTimingsExporter';
|
||||||
|
|
||||||
import { renderTimeIntervals } from './util';
|
import { mergeTimeIntervals, renderTimeIntervals } from './util';
|
||||||
|
|
||||||
const ALL_MUTE_TIMINGS = Symbol('all mute timings');
|
const ALL_MUTE_TIMINGS = Symbol('all mute timings');
|
||||||
|
|
||||||
@ -72,9 +72,9 @@ export const MuteTimingsTable = ({ alertManagerSourceName, muteTimingNames, hide
|
|||||||
const config = currentData?.alertmanager_config;
|
const config = currentData?.alertmanager_config;
|
||||||
|
|
||||||
const [muteTimingName, setMuteTimingName] = useState<string>('');
|
const [muteTimingName, setMuteTimingName] = useState<string>('');
|
||||||
|
|
||||||
const items = useMemo((): Array<DynamicTableItemProps<MuteTimeInterval>> => {
|
const items = useMemo((): Array<DynamicTableItemProps<MuteTimeInterval>> => {
|
||||||
const muteTimings = config?.mute_time_intervals ?? [];
|
// merge both fields mute_time_intervals and time_intervals to support both old and new config
|
||||||
|
const muteTimings = config ? mergeTimeIntervals(config) : [];
|
||||||
const muteTimingsProvenances = config?.muteTimeProvenances ?? {};
|
const muteTimingsProvenances = config?.muteTimeProvenances ?? {};
|
||||||
|
|
||||||
return muteTimings
|
return muteTimings
|
||||||
@ -88,7 +88,7 @@ export const MuteTimingsTable = ({ alertManagerSourceName, muteTimingNames, hide
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [config?.mute_time_intervals, config?.muteTimeProvenances, muteTimingNames]);
|
}, [muteTimingNames, config]);
|
||||||
|
|
||||||
const [_, allowedToCreateMuteTiming] = useAlertmanagerAbility(AlertmanagerAction.CreateMuteTiming);
|
const [_, allowedToCreateMuteTiming] = useAlertmanagerAbility(AlertmanagerAction.CreateMuteTiming);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertmanagerConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getDaysOfMonthString,
|
getDaysOfMonthString,
|
||||||
@ -18,6 +18,12 @@ const isvalidTimeFormat = (timeString: string): boolean => {
|
|||||||
return timeString ? TIME_RANGE_REGEX.test(timeString) : true;
|
return timeString ? TIME_RANGE_REGEX.test(timeString) : true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// merge both fields mute_time_intervals and time_intervals to support both old and new config
|
||||||
|
export const mergeTimeIntervals = (alertManagerConfig: AlertmanagerConfig) => {
|
||||||
|
return [...(alertManagerConfig.mute_time_intervals ?? []), ...(alertManagerConfig.time_intervals ?? [])];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usage
|
||||||
const isValidStartAndEndTime = (startTime?: string, endTime?: string): boolean => {
|
const isValidStartAndEndTime = (startTime?: string, endTime?: string): boolean => {
|
||||||
// empty time range is perfactly valid for a mute timing
|
// empty time range is perfactly valid for a mute timing
|
||||||
if (!startTime && !endTime) {
|
if (!startTime && !endTime) {
|
||||||
@ -67,4 +73,4 @@ function renderTimeIntervals(muteTiming: MuteTimeInterval) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isvalidTimeFormat, isValidStartAndEndTime, renderTimeIntervals };
|
export { isValidStartAndEndTime, isvalidTimeFormat, renderTimeIntervals };
|
||||||
|
@ -2,6 +2,7 @@ import { useMemo } from 'react';
|
|||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
import { mergeTimeIntervals } from '../components/mute-timings/util';
|
||||||
import { useAlertmanager } from '../state/AlertmanagerContext';
|
import { useAlertmanager } from '../state/AlertmanagerContext';
|
||||||
import { timeIntervalToString } from '../utils/alertmanager';
|
import { timeIntervalToString } from '../utils/alertmanager';
|
||||||
|
|
||||||
@ -13,8 +14,9 @@ export function useMuteTimingOptions(): Array<SelectableValue<string>> {
|
|||||||
const config = currentData?.alertmanager_config;
|
const config = currentData?.alertmanager_config;
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
const time_intervals = config ? mergeTimeIntervals(config) : [];
|
||||||
const muteTimingsOptions: Array<SelectableValue<string>> =
|
const muteTimingsOptions: Array<SelectableValue<string>> =
|
||||||
config?.mute_time_intervals?.map((value) => ({
|
time_intervals?.map((value) => ({
|
||||||
value: value.name,
|
value: value.name,
|
||||||
label: value.name,
|
label: value.name,
|
||||||
description: value.time_intervals.map((interval) => timeIntervalToString(interval)).join(', AND '),
|
description: value.time_intervals.map((interval) => timeIntervalToString(interval)).join(', AND '),
|
||||||
|
@ -693,10 +693,24 @@ export const deleteMuteTimingAction = (alertManagerSourceName: string, muteTimin
|
|||||||
alertmanagerApi.endpoints.getAlertmanagerConfiguration.initiate(alertManagerSourceName)
|
alertmanagerApi.endpoints.getAlertmanagerConfiguration.initiate(alertManagerSourceName)
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
const muteIntervals =
|
const isGrafanaDatasource = alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME;
|
||||||
config?.alertmanager_config?.mute_time_intervals?.filter(({ name }) => name !== muteTimingName) ?? [];
|
|
||||||
|
const muteIntervalsFiltered =
|
||||||
|
(config?.alertmanager_config?.mute_time_intervals ?? [])?.filter(({ name }) => name !== muteTimingName) ?? [];
|
||||||
|
const timeIntervalsFiltered =
|
||||||
|
(config?.alertmanager_config?.time_intervals ?? [])?.filter(({ name }) => name !== muteTimingName) ?? [];
|
||||||
|
|
||||||
|
const time_intervals_without_mute_to_save = isGrafanaDatasource
|
||||||
|
? {
|
||||||
|
mute_time_intervals: [...muteIntervalsFiltered, ...timeIntervalsFiltered],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
time_intervals: timeIntervalsFiltered,
|
||||||
|
mute_time_intervals: muteIntervalsFiltered,
|
||||||
|
};
|
||||||
|
|
||||||
if (config) {
|
if (config) {
|
||||||
|
const { mute_time_intervals: _, ...configWithoutMuteTimings } = config?.alertmanager_config ?? {};
|
||||||
withAppEvents(
|
withAppEvents(
|
||||||
dispatch(
|
dispatch(
|
||||||
updateAlertManagerConfigAction({
|
updateAlertManagerConfigAction({
|
||||||
@ -705,11 +719,11 @@ export const deleteMuteTimingAction = (alertManagerSourceName: string, muteTimin
|
|||||||
newConfig: {
|
newConfig: {
|
||||||
...config,
|
...config,
|
||||||
alertmanager_config: {
|
alertmanager_config: {
|
||||||
...config.alertmanager_config,
|
...configWithoutMuteTimings,
|
||||||
route: config.alertmanager_config.route
|
route: config.alertmanager_config.route
|
||||||
? removeMuteTimingFromRoute(muteTimingName, config.alertmanager_config?.route)
|
? removeMuteTimingFromRoute(muteTimingName, config.alertmanager_config?.route)
|
||||||
: undefined,
|
: undefined,
|
||||||
mute_time_intervals: muteIntervals,
|
...time_intervals_without_mute_to_save,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -157,6 +157,7 @@ export type AlertmanagerConfig = {
|
|||||||
inhibit_rules?: InhibitRule[];
|
inhibit_rules?: InhibitRule[];
|
||||||
receivers?: Receiver[];
|
receivers?: Receiver[];
|
||||||
mute_time_intervals?: MuteTimeInterval[];
|
mute_time_intervals?: MuteTimeInterval[];
|
||||||
|
time_intervals?: MuteTimeInterval[];
|
||||||
/** { [name]: provenance } */
|
/** { [name]: provenance } */
|
||||||
muteTimeProvenances?: Record<string, string>;
|
muteTimeProvenances?: Record<string, string>;
|
||||||
last_applied?: boolean;
|
last_applied?: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user