diff --git a/public/app/features/alerting/unified/Receivers.test.tsx b/public/app/features/alerting/unified/Receivers.test.tsx index f002c1c41f4..9ffc2e43e97 100644 --- a/public/app/features/alerting/unified/Receivers.test.tsx +++ b/public/app/features/alerting/unified/Receivers.test.tsx @@ -7,8 +7,14 @@ import { locationService, setDataSourceSrv } from '@grafana/runtime'; import { act, render } from '@testing-library/react'; import { getAllDataSources } from './utils/config'; import { typeAsJestMock } from 'test/helpers/typeAsJestMock'; -import { updateAlertManagerConfig, fetchAlertManagerConfig } from './api/alertmanager'; -import { mockDataSource, MockDataSourceSrv, someCloudAlertManagerConfig, someGrafanaAlertManagerConfig } from './mocks'; +import { updateAlertManagerConfig, fetchAlertManagerConfig, fetchStatus } from './api/alertmanager'; +import { + mockDataSource, + MockDataSourceSrv, + someCloudAlertManagerConfig, + someCloudAlertManagerStatus, + someGrafanaAlertManagerConfig, +} from './mocks'; import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; import { fetchNotifiers } from './api/grafana'; import { grafanaNotifiersMock } from './mocks/grafana-notifiers'; @@ -27,6 +33,7 @@ const mocks = { api: { fetchConfig: typeAsJestMock(fetchAlertManagerConfig), + fetchStatus: typeAsJestMock(fetchStatus), updateConfig: typeAsJestMock(updateAlertManagerConfig), fetchNotifiers: typeAsJestMock(fetchNotifiers), }, @@ -302,4 +309,25 @@ describe('Receivers', () => { }, }); }, 10000); + + it('Loads config from status endpoint if there is no user config', async () => { + // loading an empty config with make it fetch config from status endpoint + mocks.api.fetchConfig.mockResolvedValue({ + template_files: {}, + alertmanager_config: {}, + }); + mocks.api.fetchStatus.mockResolvedValue(someCloudAlertManagerStatus); + await renderReceivers('CloudManager'); + + // check that receiver from the default config is represented + const receiversTable = await ui.receiversTable.find(); + const receiverRows = receiversTable.querySelectorAll('tbody tr'); + expect(receiverRows[0]).toHaveTextContent('default-email'); + + // check that both config and status endpoints were called + expect(mocks.api.fetchConfig).toHaveBeenCalledTimes(1); + expect(mocks.api.fetchConfig).toHaveBeenLastCalledWith('CloudManager'); + expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1); + expect(mocks.api.fetchStatus).toHaveBeenLastCalledWith('CloudManager'); + }); }); diff --git a/public/app/features/alerting/unified/api/alertmanager.ts b/public/app/features/alerting/unified/api/alertmanager.ts index 90ccaf53ae0..5689a46f671 100644 --- a/public/app/features/alerting/unified/api/alertmanager.ts +++ b/public/app/features/alerting/unified/api/alertmanager.ts @@ -7,6 +7,7 @@ import { Silence, SilenceCreatePayload, Matcher, + AlertmanagerStatus, } from 'app/plugins/datasource/alertmanager/types'; import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; @@ -131,6 +132,18 @@ export async function fetchAlertGroups(alertmanagerSourceName: string): Promise< return result.data; } +export async function fetchStatus(alertManagerSourceName: string): Promise { + const result = await getBackendSrv() + .fetch({ + url: `/api/alertmanager/${getDatasourceAPIId(alertManagerSourceName)}/api/v2/status`, + showErrorAlert: false, + showSuccessAlert: false, + }) + .toPromise(); + + return result.data; +} + function escapeQuotes(value: string): string { return value.replace(/"/g, '\\"'); } diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index b2f80a70a1c..d737b9def90 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -3,7 +3,11 @@ import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting- import { AlertingRule, Alert, RecordingRule, RuleGroup, RuleNamespace } from 'app/types/unified-alerting'; import DatasourceSrv from 'app/features/plugins/datasource_srv'; import { DataSourceSrv, GetDataSourceListFilters } from '@grafana/runtime'; -import { AlertManagerCortexConfig, GrafanaManagedReceiverConfig } from 'app/plugins/datasource/alertmanager/types'; +import { + AlertManagerCortexConfig, + AlertmanagerStatus, + GrafanaManagedReceiverConfig, +} from 'app/plugins/datasource/alertmanager/types'; let nextDataSourceId = 1; @@ -176,6 +180,37 @@ export const someGrafanaAlertManagerConfig: AlertManagerCortexConfig = { }, }; +export const someCloudAlertManagerStatus: AlertmanagerStatus = { + cluster: { + peers: [], + status: 'ok', + }, + uptime: '10 hours', + versionInfo: { + branch: '', + version: '', + goVersion: '', + buildDate: '', + buildUser: '', + revision: '', + }, + config: { + route: { + receiver: 'default-email', + }, + receivers: [ + { + name: 'default-email', + email_configs: [ + { + to: 'example@example.com', + }, + ], + }, + ], + }, +}; + export const someCloudAlertManagerConfig: AlertManagerCortexConfig = { template_files: { 'foo template': 'foo content', diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 04a907a833a..a67986cf8a2 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -22,6 +22,7 @@ import { fetchSilences, createOrUpdateSilence, updateAlertManagerConfig, + fetchStatus, } from '../api/alertmanager'; import { fetchRules } from '../api/prometheus'; import { @@ -47,6 +48,7 @@ import { } from '../utils/rules'; import { addDefaultsToAlertmanagerConfig } from '../utils/alertmanager'; import { backendSrv } from 'app/core/services/backend_srv'; +import { isEmpty } from 'lodash'; export const fetchPromRulesAction = createAsyncThunk( 'unifiedalerting/fetchPromRules', @@ -56,7 +58,18 @@ export const fetchPromRulesAction = createAsyncThunk( export const fetchAlertManagerConfigAction = createAsyncThunk( 'unifiedalerting/fetchAmConfig', (alertManagerSourceName: string): Promise => - withSerializedError(fetchAlertManagerConfig(alertManagerSourceName)) + withSerializedError( + fetchAlertManagerConfig(alertManagerSourceName).then((result) => { + // if user config is empty for cortex alertmanager, try to get config from status endpoint + if (isEmpty(result.alertmanager_config) && alertManagerSourceName !== GRAFANA_RULES_SOURCE_NAME) { + return fetchStatus(alertManagerSourceName).then((status) => ({ + alertmanager_config: status.config, + template_files: {}, + })); + } + return result; + }) + ) ); export const fetchRulerRulesAction = createAsyncThunk( diff --git a/public/app/plugins/datasource/alertmanager/types.ts b/public/app/plugins/datasource/alertmanager/types.ts index add15e180bc..ca953310c5d 100644 --- a/public/app/plugins/datasource/alertmanager/types.ts +++ b/public/app/plugins/datasource/alertmanager/types.ts @@ -5,13 +5,6 @@ export type AlertManagerCortexConfig = { alertmanager_config: AlertmanagerConfig; }; -// NOTE - This type is incomplete! But currently, we don't need more. -export type AlertmanagerStatusPayload = { - config: { - original: string; - }; -}; - export type TLSConfig = { ca_file: string; cert_file: string; @@ -217,3 +210,20 @@ export type AlertmanagerGroup = { alerts: AlertmanagerAlert[]; id: string; }; + +export interface AlertmanagerStatus { + cluster: { + peers: unknown; + status: string; + }; + config: AlertmanagerConfig; + uptime: string; + versionInfo: { + branch: string; + buildDate: string; + buildUser: string; + goVersion: string; + revision: string; + version: string; + }; +}