From da3c22d3bea6ed28500e2b1770cf70f62510792e Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 8 Feb 2022 17:23:15 +0100 Subject: [PATCH] Alerting: Improve Contact Points error handling (#44888) (#45092) (cherry picked from commit 1cf48618dee807d3b93ac54f53dc76632e14c892) Co-authored-by: Konrad Lalik --- .../alerting/unified/api/alertmanager.ts | 79 +++++++++++++------ .../alerting/unified/state/actions.ts | 4 +- .../alerting/unified/utils/alertmanager.ts | 5 ++ .../features/alerting/unified/utils/redux.ts | 5 +- .../plugins/datasource/alertmanager/types.ts | 2 +- 5 files changed, 62 insertions(+), 33 deletions(-) diff --git a/public/app/features/alerting/unified/api/alertmanager.ts b/public/app/features/alerting/unified/api/alertmanager.ts index 0ea54d1121b..25cd0320f56 100644 --- a/public/app/features/alerting/unified/api/alertmanager.ts +++ b/public/app/features/alerting/unified/api/alertmanager.ts @@ -1,22 +1,22 @@ -import { lastValueFrom } from 'rxjs'; import { urlUtil } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; - import { AlertmanagerAlert, AlertManagerCortexConfig, AlertmanagerGroup, + AlertmanagerStatus, + ExternalAlertmanagersResponse, + Matcher, + Receiver, Silence, SilenceCreatePayload, - Matcher, - AlertmanagerStatus, - Receiver, + TestReceiversAlert, TestReceiversPayload, TestReceiversResult, - TestReceiversAlert, - ExternalAlertmanagersResponse, } from 'app/plugins/datasource/alertmanager/types'; +import { lastValueFrom } from 'rxjs'; import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; +import { isFetchError } from '../utils/alertmanager'; // "grafana" for grafana-managed, otherwise a datasource name export async function fetchAlertManagerConfig(alertManagerSourceName: string): Promise { @@ -171,28 +171,55 @@ export async function testReceivers( receivers, alert, }; - const result = await lastValueFrom( - getBackendSrv().fetch({ - method: 'POST', - data, - url: `/api/alertmanager/${getDatasourceAPIId(alertManagerSourceName)}/config/api/v1/receivers/test`, - showErrorAlert: false, - showSuccessAlert: false, - }) - ); + try { + const result = await lastValueFrom( + getBackendSrv().fetch({ + method: 'POST', + data, + url: `/api/alertmanager/${getDatasourceAPIId(alertManagerSourceName)}/config/api/v1/receivers/test`, + showErrorAlert: false, + showSuccessAlert: false, + }) + ); - // api returns 207 if one or more receivers has failed test. Collect errors in this case - if (result.status === 207) { - throw new Error( - result.data.receivers - .flatMap((receiver) => - receiver.grafana_managed_receiver_configs - .filter((receiver) => receiver.status === 'failed') - .map((receiver) => receiver.error ?? 'Unknown error.') - ) - .join('; ') + if (receiversResponseContainsErrors(result.data)) { + throw new Error(getReceiverResultError(result.data)); + } + } catch (error) { + if (isFetchError(error) && isTestReceiversResult(error.data) && receiversResponseContainsErrors(error.data)) { + throw new Error(getReceiverResultError(error.data)); + } + + throw error; + } +} + +function receiversResponseContainsErrors(result: TestReceiversResult) { + return result.receivers.some((receiver) => + receiver.grafana_managed_receiver_configs.some((config) => config.status === 'failed') + ); +} + +function isTestReceiversResult(data: any): data is TestReceiversResult { + const receivers = data?.receivers; + + if (Array.isArray(receivers)) { + return receivers.every( + (receiver: any) => typeof receiver.name === 'string' && Array.isArray(receiver.grafana_managed_receiver_configs) ); } + + return false; +} + +function getReceiverResultError(receiversResult: TestReceiversResult) { + return receiversResult.receivers + .flatMap((receiver) => + receiver.grafana_managed_receiver_configs + .filter((receiver) => receiver.status === 'failed') + .map((receiver) => receiver.error ?? 'Unknown error.') + ) + .join('; '); } export async function addAlertManagers(alertManagers: string[]): Promise { diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 048717794bf..e3fcd25b03d 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -53,7 +53,7 @@ import { isVanillaPrometheusAlertManagerDataSource, } from '../utils/datasource'; import { makeAMLink, retryWhile } from '../utils/misc'; -import { isFetchError, withAppEvents, withSerializedError } from '../utils/redux'; +import { withAppEvents, withSerializedError } from '../utils/redux'; import { formValuesToRulerRuleDTO, formValuesToRulerGrafanaRuleDTO } from '../utils/rule-form'; import { isCloudRuleIdentifier, @@ -62,7 +62,7 @@ import { isPrometheusRuleIdentifier, isRulerNotSupportedResponse, } from '../utils/rules'; -import { addDefaultsToAlertmanagerConfig, removeMuteTimingFromRoute } from '../utils/alertmanager'; +import { addDefaultsToAlertmanagerConfig, removeMuteTimingFromRoute, isFetchError } from '../utils/alertmanager'; import * as ruleId from '../utils/rule-id'; import { isEmpty } from 'lodash'; import messageFromError from 'app/plugins/datasource/grafana-azure-monitor-datasource/utils/messageFromError'; diff --git a/public/app/features/alerting/unified/utils/alertmanager.ts b/public/app/features/alerting/unified/utils/alertmanager.ts index 4b2c27ad119..af69f38bc09 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.ts @@ -11,6 +11,7 @@ import { MatcherFieldValue } from '../types/silence-form'; import { SelectableValue } from '@grafana/data'; import { getAllDataSources } from './config'; import { DataSourceType } from './datasource'; +import { FetchError } from '@grafana/runtime'; export function addDefaultsToAlertmanagerConfig(config: AlertManagerCortexConfig): AlertManagerCortexConfig { // add default receiver if it does not exist @@ -254,3 +255,7 @@ export function getMonthsString(months?: string[]): string { export function getYearsString(years?: string[]): string { return 'Years: ' + (years?.join(', ') ?? 'All'); } + +export function isFetchError(e: unknown): e is FetchError { + return typeof e === 'object' && e !== null && 'status' in e && 'data' in e; +} diff --git a/public/app/features/alerting/unified/utils/redux.ts b/public/app/features/alerting/unified/utils/redux.ts index 345164d3c9c..39a3d2ed992 100644 --- a/public/app/features/alerting/unified/utils/redux.ts +++ b/public/app/features/alerting/unified/utils/redux.ts @@ -4,6 +4,7 @@ import { FetchError } from '@grafana/runtime'; import { AppEvents } from '@grafana/data'; import { appEvents } from 'app/core/core'; +import { isFetchError } from './alertmanager'; export interface AsyncRequestState { result?: T; @@ -138,10 +139,6 @@ export function withAppEvents( }); } -export function isFetchError(e: unknown): e is FetchError { - return typeof e === 'object' && e !== null && 'status' in e && 'data' in e; -} - export function messageFromError(e: Error | FetchError | SerializedError): string { if (isFetchError(e)) { if (e.data?.message) { diff --git a/public/app/plugins/datasource/alertmanager/types.ts b/public/app/plugins/datasource/alertmanager/types.ts index 1ef41d32b79..ea3b649bb92 100644 --- a/public/app/plugins/datasource/alertmanager/types.ts +++ b/public/app/plugins/datasource/alertmanager/types.ts @@ -247,7 +247,7 @@ interface TestReceiversResultGrafanaReceiverConfig { name: string; uid?: string; error?: string; - status: 'failed'; + status: 'ok' | 'failed'; } interface TestReceiversResultReceiver {