Alerting: Improve Contact Points error handling (#44888)

* Add 400 and 408 errors handling to display useful error message

* Add generic error handling

* Improve type guard
This commit is contained in:
Konrad Lalik 2022-02-08 17:08:27 +01:00 committed by GitHub
parent 92b33d6528
commit 1cf48618de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 62 additions and 33 deletions

View File

@ -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<AlertManagerCortexConfig> {
@ -171,28 +171,55 @@ export async function testReceivers(
receivers,
alert,
};
const result = await lastValueFrom(
getBackendSrv().fetch<TestReceiversResult>({
method: 'POST',
data,
url: `/api/alertmanager/${getDatasourceAPIId(alertManagerSourceName)}/config/api/v1/receivers/test`,
showErrorAlert: false,
showSuccessAlert: false,
})
);
try {
const result = await lastValueFrom(
getBackendSrv().fetch<TestReceiversResult>({
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<void> {

View File

@ -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';

View File

@ -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;
}

View File

@ -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<T> {
result?: T;
@ -138,10 +139,6 @@ export function withAppEvents<T>(
});
}
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) {

View File

@ -247,7 +247,7 @@ interface TestReceiversResultGrafanaReceiverConfig {
name: string;
uid?: string;
error?: string;
status: 'failed';
status: 'ok' | 'failed';
}
interface TestReceiversResultReceiver {