Alerting: Consolidate mute timings selector logic (#92372)

This commit is contained in:
Tom Ratcliffe
2024-08-29 15:09:53 +01:00
committed by GitHub
parent 15a4ff992b
commit 6298825cf7
8 changed files with 97 additions and 72 deletions
@@ -0,0 +1,33 @@
import { SelectableValue } from '@grafana/data';
import { MultiSelect, MultiSelectCommonProps } from '@grafana/ui';
import { useSelectableMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks';
import { timeIntervalToString } from 'app/features/alerting/unified/utils/alertmanager';
import { MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
const mapMuteTiming = ({ name, time_intervals }: MuteTimeInterval): SelectableValue<string> => ({
value: name,
label: name,
description: time_intervals.map((interval) => timeIntervalToString(interval)).join(', AND '),
});
/** Provides a MultiSelect with available mute timings for the given alertmanager */
const MuteTimingsSelector = ({
alertmanager,
selectProps,
}: BaseAlertmanagerArgs & { selectProps: MultiSelectCommonProps<string> }) => {
const { data } = useSelectableMuteTimings({ alertmanager });
const muteTimingOptions = data?.map((value) => mapMuteTiming(value)) || [];
return (
<MultiSelect
aria-label="Mute timings"
options={muteTimingOptions}
placeholder="Select mute timings..."
{...selectProps}
/>
);
};
export default MuteTimingsSelector;
@@ -96,6 +96,8 @@
]
}
],
"templates": ["slack-template", "custom-email", "provisioned-template", "template with spaces"]
"templates": ["slack-template", "custom-email", "provisioned-template", "template with spaces"],
"time_intervals": [],
"mute_time_intervals": []
}
}
@@ -36,6 +36,7 @@ exports[`should be able to test and save a receiver 2`] = `
[
{
"alertmanager_config": {
"mute_time_intervals": [],
"receivers": [
{
"grafana_managed_receiver_configs": [
@@ -158,6 +159,7 @@ exports[`should be able to test and save a receiver 2`] = `
"provisioned-template",
"template with spaces",
],
"time_intervals": [],
},
"template_file_provenances": {
"provisioned-template": "api",
@@ -7,7 +7,8 @@ import {
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval,
IoK8SApimachineryPkgApisMetaV1ObjectMeta,
} from 'app/features/alerting/unified/openapi/timeIntervalsApi.gen';
import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks';
import { BaseAlertmanagerArgs, Skippable } from 'app/features/alerting/unified/types/hooks';
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
import { PROVENANCE_ANNOTATION, PROVENANCE_NONE } from 'app/features/alerting/unified/utils/k8s/constants';
import { getK8sNamespace, shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils';
import { MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
@@ -20,7 +21,7 @@ import {
updateMuteTimingAction,
} from '../../reducers/alertmanager/muteTimings';
const { useLazyGetAlertmanagerConfigurationQuery } = alertmanagerApi;
const { useLazyGetAlertmanagerConfigurationQuery, useGetMuteTimingListQuery } = alertmanagerApi;
const {
useLazyListNamespacedTimeIntervalQuery,
useCreateNamespacedTimeIntervalMutation,
@@ -98,20 +99,23 @@ const useGrafanaAlertmanagerIntervals = () =>
*
* Otherwise, fetches and parses from the alertmanager config API
*/
export const useMuteTimings = ({ alertmanager }: BaseAlertmanagerArgs) => {
export const useMuteTimings = ({ alertmanager, skip }: BaseAlertmanagerArgs & Skippable) => {
const useK8sApi = shouldUseK8sApi(alertmanager);
const [getGrafanaTimeIntervals, intervalsResponse] = useGrafanaAlertmanagerIntervals();
const [getAlertmanagerTimeIntervals, configApiResponse] = useAlertmanagerIntervals();
useEffect(() => {
if (skip) {
return;
}
if (useK8sApi) {
const namespace = getK8sNamespace();
getGrafanaTimeIntervals({ namespace });
} else {
getAlertmanagerTimeIntervals(alertmanager);
}
}, [alertmanager, getAlertmanagerTimeIntervals, getGrafanaTimeIntervals, useK8sApi]);
}, [alertmanager, getAlertmanagerTimeIntervals, getGrafanaTimeIntervals, skip, useK8sApi]);
return useK8sApi ? intervalsResponse : configApiResponse;
};
@@ -294,3 +298,21 @@ export const useValidateMuteTiming = ({ alertmanager }: BaseAlertmanagerArgs) =>
});
};
};
/**
* @deprecated This will be deprecated by the K8S API.
* Once that is enabled by default, this method should be removed and `useMuteTimings` should always be used instead
*/
export const useSelectableMuteTimings = ({ alertmanager }: BaseAlertmanagerArgs) => {
const useK8sApi = shouldUseK8sApi(alertmanager);
const useDeprecatedEndpoint = alertmanager === GRAFANA_RULES_SOURCE_NAME && !useK8sApi;
/** Fetch from the (to be deprecated) specific endpoint for time-intervals */
const deprecatedMuteTimingsResponse = useGetMuteTimingListQuery(undefined, {
skip: !useDeprecatedEndpoint,
});
const fetchMuteTimings = useMuteTimings({ alertmanager, skip: useDeprecatedEndpoint });
return useDeprecatedEndpoint ? deprecatedMuteTimingsResponse : fetchMuteTimings;
};
@@ -1,8 +1,8 @@
import { css } from '@emotion/css';
import { ReactNode, useMemo, useState } from 'react';
import { ReactNode, useState } from 'react';
import { useForm, Controller, useFieldArray } from 'react-hook-form';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import {
Badge,
Button,
@@ -16,13 +16,14 @@ import {
Switch,
useStyles2,
} from '@grafana/ui';
import MuteTimingsSelector from 'app/features/alerting/unified/components/alertmanager-entities/MuteTimingsSelector';
import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector';
import { handleContactPointSelect } from 'app/features/alerting/unified/components/notification-policies/utils';
import { MatcherOperator, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { FormAmRoute } from '../../types/amroutes';
import { matcherFieldOptions, timeIntervalToString } from '../../utils/alertmanager';
import { matcherFieldOptions } from '../../utils/alertmanager';
import {
amRouteToFormAmRoute,
commonGroupByOptions,
@@ -33,7 +34,6 @@ import {
stringToSelectableValue,
stringsToSelectableValues,
} from '../../utils/amroutes';
import { useMuteTimings } from '../mute-timings/useMuteTimings';
import { PromDurationInput } from './PromDurationInput';
import { getFormStyles } from './formStyles';
@@ -52,18 +52,6 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
const { selectedAlertmanager } = useAlertmanager();
const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues(route?.group_by));
const emptyMatcher = [{ name: '', operator: MatcherOperator.equal, value: '' }];
const { data: muteTimings } = useMuteTimings({ alertmanager: selectedAlertmanager! });
const muteTimingOptions = useMemo(() => {
const muteTimingsOptions: Array<SelectableValue<string>> =
muteTimings?.map((value) => ({
value: value.name,
label: value.name,
description: value.time_intervals.map((interval) => timeIntervalToString(interval)).join(', AND '),
})) ?? [];
return muteTimingsOptions;
}, [muteTimings]);
const formAmRoute = {
...amRouteToFormAmRoute(route),
@@ -285,12 +273,12 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
>
<Controller
render={({ field: { onChange, ref, ...field } }) => (
<MultiSelect
aria-label="Mute timings"
{...field}
className={formStyles.input}
onChange={(value) => onChange(mapMultiSelectValueToStrings(value))}
options={muteTimingOptions}
<MuteTimingsSelector
alertmanager={selectedAlertmanager!}
selectProps={{
...field,
onChange: (value) => onChange(mapMultiSelectValueToStrings(value)),
}}
/>
)}
control={control}
@@ -61,7 +61,7 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
className={styles.collapsableSection}
>
<Stack direction="column" gap={1}>
<MuteTimingFields alertManager={alertManagerName} />
<MuteTimingFields alertmanager={alertManagerName} />
<RoutingSettings alertManager={alertManagerName} />
</Stack>
</CollapsableSection>
@@ -1,65 +1,38 @@
import { useFormContext, Controller } from 'react-hook-form';
import { SelectableValue } from '@grafana/data';
import { Field, MultiSelect, useStyles2 } from '@grafana/ui';
import { alertmanagerApi } from 'app/features/alerting/unified/api/alertmanagerApi';
import { Field } from '@grafana/ui';
import MuteTimingsSelector from 'app/features/alerting/unified/components/alertmanager-entities/MuteTimingsSelector';
import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks';
import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form';
import { timeIntervalToString } from 'app/features/alerting/unified/utils/alertmanager';
import { mapMultiSelectValueToStrings } from 'app/features/alerting/unified/utils/amroutes';
import { getFormStyles } from '../../../../notification-policies/formStyles';
export interface MuteTimingFieldsProps {
alertManager: string;
}
export function MuteTimingFields({ alertManager }: MuteTimingFieldsProps) {
const styles = useStyles2(getFormStyles);
/** Provides a form field for use in simplified routing, for selecting appropriate mute timings */
export function MuteTimingFields({ alertmanager }: BaseAlertmanagerArgs) {
const {
control,
formState: { errors },
} = useFormContext<RuleFormValues>();
const muteTimingOptions = useSelectableMuteTimings();
return (
<Field
label="Mute timings"
data-testid="am-mute-timing-select"
description="Select a mute timing to define when not to send notifications for this alert rule"
invalid={!!errors.contactPoints?.[alertManager]?.muteTimeIntervals}
invalid={!!errors.contactPoints?.[alertmanager]?.muteTimeIntervals}
>
<Controller
render={({ field: { onChange, ref, ...field } }) => (
<MultiSelect
aria-label="Mute timings"
{...field}
className={styles.input}
onChange={(value) => onChange(mapMultiSelectValueToStrings(value))}
options={muteTimingOptions}
placeholder="Select mute timings..."
<MuteTimingsSelector
alertmanager={alertmanager}
selectProps={{
...field,
onChange: (value) => onChange(mapMultiSelectValueToStrings(value)),
}}
/>
)}
control={control}
name={`contactPoints.${alertManager}.muteTimeIntervals`}
name={`contactPoints.${alertmanager}.muteTimeIntervals`}
/>
</Field>
);
}
function useSelectableMuteTimings(): Array<SelectableValue<string>> {
const fetchGrafanaMuteTimings = alertmanagerApi.endpoints.getMuteTimingList.useQuery(undefined, {
refetchOnFocus: true,
refetchOnReconnect: true,
selectFromResult: (result) => ({
...result,
mutetimings: result.data
? result.data.map((value) => ({
value: value.name,
label: value.name,
description: value.time_intervals.map((interval) => timeIntervalToString(interval)).join(', AND '),
}))
: [],
}),
});
return fetchGrafanaMuteTimings.mutetimings;
}
@@ -1,13 +1,18 @@
import { HttpResponse, http } from 'msw';
import alertmanagerConfig from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json';
import { GrafanaManagedContactPoint } from 'app/plugins/datasource/alertmanager/types';
import { GrafanaManagedContactPoint, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
const defaultReceiversResponse: GrafanaManagedContactPoint[] = JSON.parse(JSON.stringify(alertmanagerConfig))
.alertmanager_config.receivers;
const defaultReceiversResponse: GrafanaManagedContactPoint[] = alertmanagerConfig.alertmanager_config.receivers;
const defaultTimeIntervalsResponse: MuteTimeInterval[] = alertmanagerConfig.alertmanager_config.time_intervals;
const getNotificationReceiversHandler = (response = defaultReceiversResponse) =>
http.get('/api/v1/notifications/receivers', () => HttpResponse.json(response));
const handlers = [getNotificationReceiversHandler()];
const getTimeIntervalsHandler = (response = defaultTimeIntervalsResponse) =>
http.get('/api/v1/notifications/time-intervals', () => HttpResponse.json(response));
const handlers = [getNotificationReceiversHandler(), getTimeIntervalsHandler()];
export default handlers;