Alerting: Add choice to external alertmanagers (#45157)

* implement alertmanagersChoice

* return empty array and remove non null assertion
This commit is contained in:
Peter Holmberg 2022-02-17 12:47:38 +01:00 committed by GitHub
parent 52d4e2ddcc
commit 0502a84922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 66 deletions

View File

@ -13,6 +13,7 @@ import {
TestReceiversAlert,
TestReceiversPayload,
TestReceiversResult,
ExternalAlertmanagerConfig,
} from 'app/plugins/datasource/alertmanager/types';
import { lastValueFrom } from 'rxjs';
import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
@ -222,11 +223,11 @@ function getReceiverResultError(receiversResult: TestReceiversResult) {
.join('; ');
}
export async function addAlertManagers(alertManagers: string[]): Promise<void> {
export async function addAlertManagers(alertManagerConfig: ExternalAlertmanagerConfig): Promise<void> {
await lastValueFrom(
getBackendSrv().fetch({
method: 'POST',
data: { alertmanagers: alertManagers },
data: alertManagerConfig,
url: '/api/v1/ngalert/admin_config',
showErrorAlert: false,
showSuccessAlert: false,
@ -247,9 +248,9 @@ export async function fetchExternalAlertmanagers(): Promise<ExternalAlertmanager
return result.data;
}
export async function fetchExternalAlertmanagerConfig(): Promise<{ alertmanagers: string[] }> {
export async function fetchExternalAlertmanagerConfig(): Promise<ExternalAlertmanagerConfig> {
const result = await lastValueFrom(
getBackendSrv().fetch<{ alertmanagers: string[] }>({
getBackendSrv().fetch<ExternalAlertmanagerConfig>({
method: 'GET',
url: '/api/v1/ngalert/admin_config',
showErrorAlert: false,

View File

@ -1,19 +1,17 @@
import React, { FC, useMemo } from 'react';
import { css, cx } from '@emotion/css';
import { useDispatch } from 'react-redux';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Field, FieldArray, Form, Icon, Input, Modal, useStyles2 } from '@grafana/ui';
import { addExternalAlertmanagersAction } from '../../state/actions';
import { AlertmanagerUrl } from 'app/plugins/datasource/alertmanager/types';
interface Props {
onClose: () => void;
alertmanagers: AlertmanagerUrl[];
onChangeAlertmanagerConfig: (alertmanagers: string[]) => void;
}
export const AddAlertManagerModal: FC<Props> = ({ alertmanagers, onClose }) => {
export const AddAlertManagerModal: FC<Props> = ({ alertmanagers, onChangeAlertmanagerConfig, onClose }) => {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const defaultValues: Record<string, AlertmanagerUrl[]> = useMemo(
() => ({
alertmanagers: alertmanagers,
@ -29,7 +27,7 @@ export const AddAlertManagerModal: FC<Props> = ({ alertmanagers, onClose }) => {
);
const onSubmit = (values: Record<string, AlertmanagerUrl[]>) => {
dispatch(addExternalAlertmanagersAction(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url))));
onChangeAlertmanagerConfig(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url)));
onClose();
};

View File

@ -1,8 +1,18 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { css } from '@emotion/css';
import { useDispatch, useSelector } from 'react-redux';
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, ConfirmModal, HorizontalGroup, Icon, Tooltip, useStyles2 } from '@grafana/ui';
import {
Button,
ConfirmModal,
Field,
HorizontalGroup,
Icon,
RadioButtonGroup,
Tooltip,
useStyles2,
useTheme2,
} from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { AddAlertManagerModal } from './AddAlertManagerModal';
import {
@ -11,13 +21,25 @@ import {
fetchExternalAlertmanagersConfigAction,
} from '../../state/actions';
import { useExternalAmSelector } from '../../hooks/useExternalAmSelector';
import { StoreState } from 'app/types/store';
const alertmanagerChoices = [
{ value: 'internal', label: 'Only Internal' },
{ value: 'external', label: 'Only External' },
{ value: 'all', label: 'Both internal and external' },
];
export const ExternalAlertmanagers = () => {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const [modalState, setModalState] = useState({ open: false, payload: [{ url: '' }] });
const [deleteModalState, setDeleteModalState] = useState({ open: false, index: 0 });
const externalAlertManagers = useExternalAmSelector();
const alertmanagersChoice = useSelector(
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.alertmanagerConfig.result?.alertmanagersChoice
);
const theme = useTheme2();
useEffect(() => {
dispatch(fetchExternalAlertmanagersAction());
@ -37,10 +59,12 @@ export const ExternalAlertmanagers = () => {
.map((am) => {
return am.url;
});
dispatch(addExternalAlertmanagersAction(newList));
dispatch(
addExternalAlertmanagersAction({ alertmanagers: newList, alertmanagersChoice: alertmanagersChoice ?? 'all' })
);
setDeleteModalState({ open: false, index: 0 });
},
[externalAlertManagers, dispatch]
[externalAlertManagers, dispatch, alertmanagersChoice]
);
const onEdit = useCallback(() => {
@ -70,16 +94,26 @@ export const ExternalAlertmanagers = () => {
}));
}, [setModalState]);
const onChangeAlertmanagerChoice = (alertmanagersChoice: string) => {
dispatch(
addExternalAlertmanagersAction({ alertmanagers: externalAlertManagers.map((am) => am.url), alertmanagersChoice })
);
};
const onChangeAlertmanagers = (alertmanagers: string[]) => {
dispatch(addExternalAlertmanagersAction({ alertmanagers, alertmanagersChoice: alertmanagersChoice ?? 'all' }));
};
const getStatusColor = (status: string) => {
switch (status) {
case 'active':
return 'green';
return theme.colors.success.main;
case 'pending':
return 'yellow';
return theme.colors.warning.main;
default:
return 'red';
return theme.colors.error.main;
}
};
@ -107,7 +141,8 @@ export const ExternalAlertmanagers = () => {
buttonIcon="bell-slash"
/>
) : (
<table className="filter-table form-inline filter-table--hover">
<>
<table className={cx('filter-table form-inline filter-table--hover', styles.table)}>
<thead>
<tr>
<th>Url</th>
@ -150,6 +185,19 @@ export const ExternalAlertmanagers = () => {
})}
</tbody>
</table>
<div>
<Field
label="Send alerts to"
description="Sets which Alertmanager will handle your alerts. Internal (Grafana built in Alertmanager), External (All Alertmanagers configured above), or both."
>
<RadioButtonGroup
options={alertmanagerChoices}
value={alertmanagersChoice}
onChange={(value) => onChangeAlertmanagerChoice(value!)}
/>
</Field>
</div>
</>
)}
<ConfirmModal
isOpen={deleteModalState.open}
@ -159,7 +207,13 @@ export const ExternalAlertmanagers = () => {
onConfirm={() => onDelete(deleteModalState.index)}
onDismiss={() => setDeleteModalState({ open: false, index: 0 })}
/>
{modalState.open && <AddAlertManagerModal onClose={onCloseModal} alertmanagers={modalState.payload} />}
{modalState.open && (
<AddAlertManagerModal
onClose={onCloseModal}
alertmanagers={modalState.payload}
onChangeAlertmanagerConfig={onChangeAlertmanagers}
/>
)}
</div>
);
};
@ -176,5 +230,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
display: flex;
justify-content: flex-end;
`,
table: css``,
table: css`
margin-bottom: ${theme.spacing(2)};
`,
});

View File

@ -4,7 +4,7 @@ import { StoreState } from '../../../../types';
const SUFFIX_REGEX = /\/api\/v[1|2]\/alerts/i;
type AlertmanagerConfig = { url: string; status: string; actualUrl: string };
export function useExternalAmSelector(): AlertmanagerConfig[] | undefined {
export function useExternalAmSelector(): AlertmanagerConfig[] | [] {
const discoveredAlertmanagers = useSelector(
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result?.data
);

View File

@ -2,6 +2,7 @@ import { getBackendSrv, locationService } from '@grafana/runtime';
import { createAsyncThunk } from '@reduxjs/toolkit';
import {
AlertmanagerAlert,
ExternalAlertmanagerConfig,
AlertManagerCortexConfig,
AlertmanagerGroup,
ExternalAlertmanagersResponse,
@ -122,7 +123,7 @@ export const fetchExternalAlertmanagersAction = createAsyncThunk(
export const fetchExternalAlertmanagersConfigAction = createAsyncThunk(
'unifiedAlerting/fetchExternAlertmanagersConfig',
(): Promise<{ alertmanagers: string[] }> => {
(): Promise<ExternalAlertmanagerConfig> => {
return withSerializedError(fetchExternalAlertmanagerConfig());
}
);
@ -808,11 +809,11 @@ export const updateLotexNamespaceAndGroupAction = createAsyncThunk(
export const addExternalAlertmanagersAction = createAsyncThunk(
'unifiedAlerting/addExternalAlertmanagers',
async (alertManagerUrls: string[], thunkAPI): Promise<void> => {
async (alertmanagerConfig: ExternalAlertmanagerConfig, thunkAPI): Promise<void> => {
return withAppEvents(
withSerializedError(
(async () => {
await addAlertManagers(alertManagerUrls);
await addAlertManagers(alertmanagerConfig);
thunkAPI.dispatch(fetchExternalAlertmanagersConfigAction());
})()
),

View File

@ -272,6 +272,12 @@ export interface ExternalAlertmanagersResponse {
data: ExternalAlertmanagers;
status: 'string';
}
export interface ExternalAlertmanagerConfig {
alertmanagers: string[];
alertmanagersChoice: string;
}
export enum AlertManagerImplementation {
cortex = 'cortex',
prometheus = 'prometheus',