From c44af3ca2069505a5c6ffe2e219ec5c259204496 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Tue, 8 Feb 2022 17:40:11 +0100 Subject: [PATCH] Alerting: Improve Contact Points error handling (#44888) (#45093) * Add 400 and 408 errors handling to display useful error message * Add generic error handling * Improve type guard (cherry picked from commit 1cf48618dee807d3b93ac54f53dc76632e14c892) --- .../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 6181273c5a9..c1ea6830ec7 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 } from '../utils/alertmanager'; +import { addDefaultsToAlertmanagerConfig, 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 6e33c32bec7..634208c161f 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.ts @@ -4,6 +4,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 @@ -176,3 +177,7 @@ export function getAllAlertmanagerDataSources() { export function getAlertmanagerByUid(uid?: string) { return getAllAlertmanagerDataSources().find((ds) => uid === ds.uid); } + +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 b2c50b2658e..2de11f311f4 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 9fe5511ee55..8a086d6e438 100644 --- a/public/app/plugins/datasource/alertmanager/types.ts +++ b/public/app/plugins/datasource/alertmanager/types.ts @@ -245,7 +245,7 @@ interface TestReceiversResultGrafanaReceiverConfig { name: string; uid?: string; error?: string; - status: 'failed'; + status: 'ok' | 'failed'; } interface TestReceiversResultReceiver {