Files
grafana/public/app/features/alerting/unified/components/amroutes/MuteTimingsTable.tsx
Nathan Rodman 825edddfb6 Alerting: UI for mute timings (#41578)
* wip: add form inputs for creating mute timing

* form for mute timings

* add action for submitting config

* fix bug in payload

* add table for viewing mute timings

* remove mute timing from routes when deleted

* attach mute timing to route

* edit a mute timing

* use field array for multiple intervals

* Add confirmation modal for deleting mute timing

* add default values to form inputs

* fetch am config prior to renderring form

* validation for mute timing fields

* fix tests

* tests for mute timing form

* small ui fixes for the form and table

* pass mute name as query param

* make time fields inline

* fix validation for an existing alert and overwrite on edit

* rename mute timing in routes on edit

* fix validation for time inputs

* make time interval its own component

* add descriptions for mute timings

* refactor time interval parsing functions

* fix linting and tests

* refactor makeAmLink

* docs for mute timings

* reorganize docs and add intro for mute timings

* doc review edits

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* run prettier

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
2022-01-05 10:16:43 -08:00

146 lines
5.1 KiB
TypeScript

import React, { FC, useMemo, useState } from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { IconButton, LinkButton, Link, useStyles2, ConfirmModal } from '@grafana/ui';
import { AlertManagerCortexConfig, MuteTimeInterval, TimeInterval } from 'app/plugins/datasource/alertmanager/types';
import { useDispatch } from 'react-redux';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import { deleteMuteTimingAction } from '../../state/actions';
import { makeAMLink } from '../../utils/misc';
import { AsyncRequestState, initialAsyncRequestState } from '../../utils/redux';
import { DynamicTable, DynamicTableItemProps, DynamicTableColumnProps } from '../DynamicTable';
import {
getTimeString,
getWeekdayString,
getDaysOfMonthString,
getMonthsString,
getYearsString,
} from '../../utils/alertmanager';
interface Props {
alertManagerSourceName: string;
muteTimingNames?: string[];
hideActions?: boolean;
}
export const MuteTimingsTable: FC<Props> = ({ alertManagerSourceName, muteTimingNames, hideActions }) => {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs);
const [muteTimingName, setMuteTimingName] = useState<string>('');
const { result }: AsyncRequestState<AlertManagerCortexConfig> =
(alertManagerSourceName && amConfigs[alertManagerSourceName]) || initialAsyncRequestState;
const items = useMemo((): Array<DynamicTableItemProps<MuteTimeInterval>> => {
const muteTimings = result?.alertmanager_config?.mute_time_intervals ?? [];
return muteTimings
.filter(({ name }) => (muteTimingNames ? muteTimingNames.includes(name) : true))
.map((mute) => {
return {
id: mute.name,
data: mute,
};
});
}, [result?.alertmanager_config?.mute_time_intervals, muteTimingNames]);
const columns = useColumns(alertManagerSourceName, hideActions, setMuteTimingName);
return (
<div>
{!hideActions && <h5>Mute timings</h5>}
{!hideActions && (
<p>
Mute timings are a named interval of time that may be referenced in the notification policy tree to mute
particular notification policies for specific times of the day.
</p>
)}
{items.length > 0 ? <DynamicTable items={items} cols={columns} /> : <p>No mute timings configured</p>}
{!hideActions && (
<ConfirmModal
isOpen={!!muteTimingName}
title="Delete mute timing"
body={`Are you sure you would like to delete "${muteTimingName}"`}
confirmText="Delete"
onConfirm={() => dispatch(deleteMuteTimingAction(alertManagerSourceName, muteTimingName))}
onDismiss={() => setMuteTimingName('')}
/>
)}
{!hideActions && (
<LinkButton
className={styles.addMuteButton}
variant="secondary"
href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)}
>
Add mute timing
</LinkButton>
)}
</div>
);
};
function useColumns(alertManagerSourceName: string, hideActions = false, setMuteTimingName: (name: string) => void) {
return useMemo((): Array<DynamicTableColumnProps<MuteTimeInterval>> => {
const columns: Array<DynamicTableColumnProps<MuteTimeInterval>> = [
{
id: 'name',
label: 'Name',
renderCell: function renderName({ data }) {
return data.name;
},
size: '250px',
},
{
id: 'timeRange',
label: 'Time range',
renderCell: ({ data }) => renderTimeIntervals(data.time_intervals),
},
];
if (!hideActions) {
columns.push({
id: 'actions',
label: 'Actions',
renderCell: function renderActions({ data }) {
return (
<div>
<Link
href={makeAMLink(`/alerting/routes/mute-timing/edit`, alertManagerSourceName, { muteName: data.name })}
>
<IconButton name="edit" title="Edit mute timing" />
</Link>
<IconButton name={'trash-alt'} title="Delete mute timing" onClick={() => setMuteTimingName(data.name)} />
</div>
);
},
size: '100px',
});
}
return columns;
}, [alertManagerSourceName, hideActions, setMuteTimingName]);
}
function renderTimeIntervals(timeIntervals: TimeInterval[]) {
return timeIntervals.map((interval, index) => {
const { times, weekdays, days_of_month, months, years } = interval;
const timeString = getTimeString(times);
const weekdayString = getWeekdayString(weekdays);
const daysString = getDaysOfMonthString(days_of_month);
const monthsString = getMonthsString(months);
const yearsString = getYearsString(years);
return (
<React.Fragment key={JSON.stringify(interval) + index}>
{`${timeString} ${weekdayString}`}
<br />
{[daysString, monthsString, yearsString].join(' | ')}
<br />
</React.Fragment>
);
});
}
const getStyles = (theme: GrafanaTheme2) => ({
addMuteButton: css`
margin-top: ${theme.spacing(1)};
`,
});