import { Silence, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types'; import React, { FC, useMemo, useState } from 'react'; import { Button, Field, FieldSet, Input, LinkButton, TextArea, useStyles } from '@grafana/ui'; import { DefaultTimeZone, GrafanaTheme, parseDuration, intervalToAbbreviatedDurationString, addDurationToDate, dateTime, isValidDate, UrlQueryMap, } from '@grafana/data'; import { useDebounce } from 'react-use'; import { config } from '@grafana/runtime'; import { pickBy } from 'lodash'; import MatchersField from './MatchersField'; import { useForm, FormProvider } from 'react-hook-form'; import { SilenceFormFields } from '../../types/silence-form'; import { useDispatch } from 'react-redux'; import { createOrUpdateSilenceAction } from '../../state/actions'; import { SilencePeriod } from './SilencePeriod'; import { css, cx } from '@emotion/css'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { makeAMLink } from '../../utils/misc'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { parseQueryParamMatchers } from '../../utils/matchers'; interface Props { silence?: Silence; alertManagerSourceName: string; } const defaultsFromQuery = (queryParams: UrlQueryMap): Partial => { const defaults: Partial = {}; const { matchers, comment } = queryParams; if (typeof matchers === 'string') { const formMatchers = parseQueryParamMatchers(matchers); if (formMatchers.length) { defaults.matchers = formMatchers; } } if (typeof comment === 'string') { defaults.comment = comment; } return defaults; }; const getDefaultFormValues = (queryParams: UrlQueryMap, silence?: Silence): SilenceFormFields => { const now = new Date(); if (silence) { const isExpired = Date.parse(silence.endsAt) < Date.now(); const interval = isExpired ? { start: now, end: addDurationToDate(now, { hours: 2 }), } : { start: new Date(silence.startsAt), end: new Date(silence.endsAt) }; return { id: silence.id, startsAt: interval.start.toISOString(), endsAt: interval.end.toISOString(), comment: silence.comment, createdBy: silence.createdBy, duration: intervalToAbbreviatedDurationString(interval), isRegex: false, matchers: silence.matchers || [], matcherName: '', matcherValue: '', timeZone: DefaultTimeZone, }; } else { const endsAt = addDurationToDate(now, { hours: 2 }); // Default time period is now + 2h return { id: '', startsAt: now.toISOString(), endsAt: endsAt.toISOString(), comment: '', createdBy: config.bootData.user.name, duration: '2h', isRegex: false, matchers: [{ name: '', value: '', isRegex: false, isEqual: true }], matcherName: '', matcherValue: '', timeZone: DefaultTimeZone, ...defaultsFromQuery(queryParams), }; } }; export const SilencesEditor: FC = ({ silence, alertManagerSourceName }) => { const [queryParams] = useQueryParams(); const defaultValues = useMemo(() => getDefaultFormValues(queryParams, silence), [silence, queryParams]); const formAPI = useForm({ defaultValues }); const dispatch = useDispatch(); const styles = useStyles(getStyles); const { loading } = useUnifiedAlertingSelector((state) => state.updateSilence); useCleanup((state) => state.unifiedAlerting.updateSilence); const { register, handleSubmit, formState, watch, setValue, clearErrors } = formAPI; const onSubmit = (data: SilenceFormFields) => { const { id, startsAt, endsAt, comment, createdBy, matchers } = data; const payload = pickBy( { id, startsAt, endsAt, comment, createdBy, matchers, }, (value) => !!value ) as SilenceCreatePayload; dispatch( createOrUpdateSilenceAction({ alertManagerSourceName, payload, exitOnSave: true, successMessage: `Silence ${payload.id ? 'updated' : 'created'}`, }) ); }; const duration = watch('duration'); const startsAt = watch('startsAt'); const endsAt = watch('endsAt'); // Keep duration and endsAt in sync const [prevDuration, setPrevDuration] = useState(duration); useDebounce( () => { if (isValidDate(startsAt) && isValidDate(endsAt)) { if (duration !== prevDuration) { setValue('endsAt', dateTime(addDurationToDate(new Date(startsAt), parseDuration(duration))).toISOString()); setPrevDuration(duration); } else { const startValue = new Date(startsAt).valueOf(); const endValue = new Date(endsAt).valueOf(); if (endValue > startValue) { const nextDuration = intervalToAbbreviatedDurationString({ start: new Date(startsAt), end: new Date(endsAt), }); setValue('duration', nextDuration); setPrevDuration(nextDuration); } } } }, 700, [clearErrors, duration, endsAt, prevDuration, setValue, startsAt] ); return (
Object.keys(parseDuration(value)).length === 0 ? 'Invalid duration. Valid example: 1d 4h (Available units: y, M, w, d, h, m, s)' : undefined, })} id="duration" />