From 0502a84922853d9a5022b10d856dc094212a3f70 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Thu, 17 Feb 2022 12:47:38 +0100 Subject: [PATCH] Alerting: Add choice to external alertmanagers (#45157) * implement alertmanagersChoice * return empty array and remove non null assertion --- .../alerting/unified/api/alertmanager.ts | 9 +- .../components/admin/AddAlertManagerModal.tsx | 8 +- .../admin/ExternalAlertmanagers.tsx | 162 ++++++++++++------ .../unified/hooks/useExternalAmSelector.ts | 2 +- .../alerting/unified/state/actions.ts | 7 +- .../plugins/datasource/alertmanager/types.ts | 6 + 6 files changed, 128 insertions(+), 66 deletions(-) diff --git a/public/app/features/alerting/unified/api/alertmanager.ts b/public/app/features/alerting/unified/api/alertmanager.ts index 25cd0320f56..9986590701c 100644 --- a/public/app/features/alerting/unified/api/alertmanager.ts +++ b/public/app/features/alerting/unified/api/alertmanager.ts @@ -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 { +export async function addAlertManagers(alertManagerConfig: ExternalAlertmanagerConfig): Promise { 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 { +export async function fetchExternalAlertmanagerConfig(): Promise { const result = await lastValueFrom( - getBackendSrv().fetch<{ alertmanagers: string[] }>({ + getBackendSrv().fetch({ method: 'GET', url: '/api/v1/ngalert/admin_config', showErrorAlert: false, diff --git a/public/app/features/alerting/unified/components/admin/AddAlertManagerModal.tsx b/public/app/features/alerting/unified/components/admin/AddAlertManagerModal.tsx index 82b258803bb..aabb017f588 100644 --- a/public/app/features/alerting/unified/components/admin/AddAlertManagerModal.tsx +++ b/public/app/features/alerting/unified/components/admin/AddAlertManagerModal.tsx @@ -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 = ({ alertmanagers, onClose }) => { +export const AddAlertManagerModal: FC = ({ alertmanagers, onChangeAlertmanagerConfig, onClose }) => { const styles = useStyles2(getStyles); - const dispatch = useDispatch(); const defaultValues: Record = useMemo( () => ({ alertmanagers: alertmanagers, @@ -29,7 +27,7 @@ export const AddAlertManagerModal: FC = ({ alertmanagers, onClose }) => { ); const onSubmit = (values: Record) => { - dispatch(addExternalAlertmanagersAction(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url)))); + onChangeAlertmanagerConfig(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url))); onClose(); }; diff --git a/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx b/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx index 9bb29cac46d..efcbccf9ab6 100644 --- a/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx +++ b/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx @@ -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,49 +141,63 @@ export const ExternalAlertmanagers = () => { buttonIcon="bell-slash" /> ) : ( - - - - - - - - - - {externalAlertManagers?.map((am, index) => { - return ( - - - - - - ); - })} - -
UrlStatusAction
- {am.url} - {am.actualUrl ? ( - - - - ) : null} - - - - - - - -
+ <> + + + + + + + + + + {externalAlertManagers?.map((am, index) => { + return ( + + + + + + ); + })} + +
UrlStatusAction
+ {am.url} + {am.actualUrl ? ( + + + + ) : null} + + + + + + + +
+
+ + onChangeAlertmanagerChoice(value!)} + /> + +
+ )} { onConfirm={() => onDelete(deleteModalState.index)} onDismiss={() => setDeleteModalState({ open: false, index: 0 })} /> - {modalState.open && } + {modalState.open && ( + + )} ); }; @@ -176,5 +230,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ display: flex; justify-content: flex-end; `, - table: css``, + table: css` + margin-bottom: ${theme.spacing(2)}; + `, }); diff --git a/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts b/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts index a4297f7b877..9bc63239fcb 100644 --- a/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts +++ b/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts @@ -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 ); diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index ead22ca396c..38e4a4fdb40 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -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 => { return withSerializedError(fetchExternalAlertmanagerConfig()); } ); @@ -808,11 +809,11 @@ export const updateLotexNamespaceAndGroupAction = createAsyncThunk( export const addExternalAlertmanagersAction = createAsyncThunk( 'unifiedAlerting/addExternalAlertmanagers', - async (alertManagerUrls: string[], thunkAPI): Promise => { + async (alertmanagerConfig: ExternalAlertmanagerConfig, thunkAPI): Promise => { return withAppEvents( withSerializedError( (async () => { - await addAlertManagers(alertManagerUrls); + await addAlertManagers(alertmanagerConfig); thunkAPI.dispatch(fetchExternalAlertmanagersConfigAction()); })() ), diff --git a/public/app/plugins/datasource/alertmanager/types.ts b/public/app/plugins/datasource/alertmanager/types.ts index ea3b649bb92..7c4f195e836 100644 --- a/public/app/plugins/datasource/alertmanager/types.ts +++ b/public/app/plugins/datasource/alertmanager/types.ts @@ -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',