diff --git a/public/app/features/alerting/unified/NewSilencePage.tsx b/public/app/features/alerting/unified/NewSilencePage.tsx
index 81d2793eb6a..7961a6c6a13 100644
--- a/public/app/features/alerting/unified/NewSilencePage.tsx
+++ b/public/app/features/alerting/unified/NewSilencePage.tsx
@@ -9,7 +9,7 @@ import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/cons
import { parseQueryParamMatchers } from 'app/features/alerting/unified/utils/matchers';
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
-import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
+import { GrafanaAlertmanagerWarning } from './components/GrafanaAlertmanagerWarning';
import { SilencesEditor } from './components/silences/SilencesEditor';
import { useAlertmanager } from './state/AlertmanagerContext';
import { withPageErrorBoundary } from './withPageErrorBoundary';
@@ -27,7 +27,7 @@ const SilencesEditorComponent = () => {
return (
<>
-
+
{
return (
<>
-
+
{policiesSupported && canSeePoliciesTab && (
{
@@ -21,12 +26,50 @@ export const AlertManagerPicker = ({ disabled = false }: Props) => {
const { selectedAlertmanager, availableAlertManagers, setSelectedAlertmanager } = useAlertmanager();
const options = useMemo(() => {
- return availableAlertManagers.map>((ds) => ({
- label: getAlertManagerLabel(ds),
- value: ds.name,
- imgUrl: ds.imgUrl,
- meta: ds.meta,
- }));
+ // Group alertmanagers
+ const grafanaAM = availableAlertManagers.find((am) => am.name === GRAFANA_RULES_SOURCE_NAME);
+ const extraConfig = availableAlertManagers.find((am) => isExtraConfig(am.name));
+ const datasourceAMs = availableAlertManagers.filter(
+ (am) => am.name !== GRAFANA_RULES_SOURCE_NAME && !isExtraConfig(am.name)
+ );
+
+ const groupedOptions: Array | { label: string; options: Array> }> =
+ [];
+
+ // Add Grafana alertmanager first
+ if (grafanaAM) {
+ groupedOptions.push({
+ label: getAlertManagerLabel(grafanaAM),
+ value: grafanaAM.name,
+ imgUrl: grafanaAM.imgUrl,
+ meta: grafanaAM.meta,
+ });
+ }
+
+ // Add extra config (single merged configuration)
+ if (extraConfig) {
+ groupedOptions.push({
+ label: getAlertManagerLabel(extraConfig),
+ value: extraConfig.name,
+ imgUrl: extraConfig.imgUrl,
+ meta: extraConfig.meta,
+ });
+ }
+
+ // Add external alertmanagers
+ if (datasourceAMs.length > 0) {
+ groupedOptions.push({
+ label: t('alerting.alert-manager-picker.external-alertmanagers-group', 'External Alertmanagers'),
+ options: datasourceAMs.map((ds) => ({
+ label: getAlertManagerLabel(ds),
+ value: ds.name,
+ imgUrl: ds.imgUrl,
+ meta: ds.meta,
+ })),
+ });
+ }
+
+ return groupedOptions;
}, [availableAlertManagers]);
const isDisabled = disabled || options.length === 1;
@@ -61,12 +104,22 @@ const getStyles = (theme: GrafanaTheme2) => ({
field: css({
margin: 0,
}),
+ optionContent: css({
+ display: 'flex',
+ alignItems: 'center',
+ gap: theme.spacing(1),
+ whiteSpace: 'pre-line',
+ }),
});
// custom option that overwrites the default "white-space: nowrap" for Alertmanager names that are really long
-const CustomOption = (props: ComponentProps) => (
- {label}
}
- />
-);
+const CustomOption = (props: ComponentProps) => {
+ const styles = useStyles2(getStyles);
+
+ return (
+ {label}
}
+ />
+ );
+};
diff --git a/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx b/public/app/features/alerting/unified/components/GrafanaAlertmanagerWarning.test.tsx
similarity index 75%
rename from public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx
rename to public/app/features/alerting/unified/components/GrafanaAlertmanagerWarning.test.tsx
index 6ed7a2a7aab..52c87f5f41c 100644
--- a/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx
+++ b/public/app/features/alerting/unified/components/GrafanaAlertmanagerWarning.test.tsx
@@ -10,10 +10,10 @@ import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/
import { grantUserPermissions } from '../mocks';
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
-import { GrafanaAlertmanagerDeliveryWarning } from './GrafanaAlertmanagerDeliveryWarning';
+import { GrafanaAlertmanagerWarning } from './GrafanaAlertmanagerWarning';
setupMswServer();
-describe('GrafanaAlertmanagerDeliveryWarning', () => {
+describe('GrafanaAlertmanagerWarning', () => {
beforeEach(() => {
grantUserPermissions([AccessControlAction.AlertingNotificationsRead]);
});
@@ -21,9 +21,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => {
it('Should not render when the datasource is not Grafana', () => {
setAlertmanagerChoices(AlertmanagerChoice.External, 0);
- const { container } = renderWithStore(
-
- );
+ const { container } = renderWithStore();
expect(container).toBeEmptyDOMElement();
});
@@ -31,7 +29,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => {
it('Should render warning when the datasource is Grafana and using external AM', async () => {
setAlertmanagerChoices(AlertmanagerChoice.External, 1);
- renderWithStore();
+ renderWithStore();
expect(await screen.findByText('Grafana alerts are not delivered to Grafana Alertmanager')).toBeVisible();
});
@@ -39,7 +37,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => {
it('Should render warning when the datasource is Grafana and using All AM', async () => {
setAlertmanagerChoices(AlertmanagerChoice.All, 1);
- renderWithStore();
+ renderWithStore();
expect(await screen.findByText('You have additional Alertmanagers to configure')).toBeVisible();
});
@@ -48,7 +46,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => {
setAlertmanagerChoices(AlertmanagerChoice.Internal, 1);
const { container } = renderWithStore(
-
+
);
await waitFor(() => {
@@ -60,7 +58,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => {
setAlertmanagerChoices(AlertmanagerChoice.All, 0);
const { container } = renderWithStore(
-
+
);
await waitFor(() => {
diff --git a/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.tsx b/public/app/features/alerting/unified/components/GrafanaAlertmanagerWarning.tsx
similarity index 75%
rename from public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.tsx
rename to public/app/features/alerting/unified/components/GrafanaAlertmanagerWarning.tsx
index c147cb7ada2..cf2afa30e6b 100644
--- a/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.tsx
+++ b/public/app/features/alerting/unified/components/GrafanaAlertmanagerWarning.tsx
@@ -7,13 +7,23 @@ import { Alert, useStyles2 } from '@grafana/ui';
import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types';
import { alertmanagerApi } from '../api/alertmanagerApi';
import { AlertingAction, useAlertingAbility } from '../hooks/useAbilities';
+import { isExtraConfig } from '../utils/alertmanager/extraConfigs';
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
-interface GrafanaAlertmanagerDeliveryWarningProps {
+interface GrafanaAlertmanagerWarningProps {
currentAlertmanager: string;
}
-export function GrafanaAlertmanagerDeliveryWarning({ currentAlertmanager }: GrafanaAlertmanagerDeliveryWarningProps) {
+export function GrafanaAlertmanagerWarning({ currentAlertmanager }: GrafanaAlertmanagerWarningProps) {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function GrafanaExternalAlertmanagerConfigWarning({ currentAlertmanager }: GrafanaAlertmanagerWarningProps) {
const styles = useStyles2(getStyles);
const externalAlertmanager = currentAlertmanager !== GRAFANA_RULES_SOURCE_NAME;
@@ -87,6 +97,23 @@ export function GrafanaAlertmanagerDeliveryWarning({ currentAlertmanager }: Graf
return null;
}
+function GrafanaExtraConfigWarning({ currentAlertmanager }: GrafanaAlertmanagerWarningProps) {
+ const isSelectedExtraConfig = currentAlertmanager && isExtraConfig(currentAlertmanager);
+
+ if (!isSelectedExtraConfig) {
+ return null;
+ }
+
+ return (
+
+
+ This shows the merged configuration of Grafana alertmanager with imported configurations. This merged view is
+ read-only in the UI.
+
+
+ );
+}
+
const getStyles = (theme: GrafanaTheme2) => ({
adminHint: css({
fontSize: theme.typography.bodySmall.fontSize,
diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx
index 6f2423e424d..729644b8209 100644
--- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx
+++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx
@@ -23,10 +23,11 @@ import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbili
import { usePagination } from '../../hooks/usePagination';
import { useURLSearchParams } from '../../hooks/useURLSearchParams';
import { useAlertmanager } from '../../state/AlertmanagerContext';
+import { isExtraConfig } from '../../utils/alertmanager/extraConfigs';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
-import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning';
+import { GrafanaAlertmanagerWarning } from '../GrafanaAlertmanagerWarning';
import { ContactPoint } from './ContactPoint';
import { NotificationTemplates } from './NotificationTemplates';
@@ -140,8 +141,12 @@ const ContactPointsTab = () => {
) : (
)}
+
{/* Grafana manager Alertmanager does not support global config, Mimir and Cortex do */}
- {!isGrafanaManagedAlertmanager && }
+ {/* Extra configs also don't support global config */}
+ {!isGrafanaManagedAlertmanager && !isExtraConfig(selectedAlertmanager!) && (
+
+ )}
{ExportDrawer}
>
);
@@ -220,7 +225,7 @@ export const ContactPointsPageContents = () => {
return (
<>
-
+
{showContactPointsTab && (
diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
index 94ccf10db46..30154485057 100644
--- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
+++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
@@ -39,7 +39,7 @@ import { matcherFieldToMatcher } from '../../utils/alertmanager';
import { makeAMLink } from '../../utils/misc';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
-import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning';
+import { GrafanaAlertmanagerWarning } from '../GrafanaAlertmanagerWarning';
import MatchersField from './MatchersField';
import { SilencePeriod } from './SilencePeriod';
@@ -118,7 +118,7 @@ const ExistingSilenceEditor = () => {
return (
<>
-
+
>
);
diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
index 314dcf26586..8d3c80acad1 100644
--- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
+++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
@@ -30,7 +30,7 @@ import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { Authorize } from '../Authorize';
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
-import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning';
+import { GrafanaAlertmanagerWarning } from '../GrafanaAlertmanagerWarning';
import { Matchers } from './Matchers';
import { NoSilencesSplash } from './NoSilencesCTA';
@@ -146,7 +146,7 @@ const SilencesTable = () => {
return (
-
+
{!!silences.length && (
diff --git a/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx b/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx
index 49d0b8c5d79..26bd108bdd4 100644
--- a/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx
+++ b/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx
@@ -1,9 +1,11 @@
import { renderHook } from '@testing-library/react';
import * as React from 'react';
+import { Provider } from 'react-redux';
import { locationService } from '@grafana/runtime';
import store from 'app/core/store';
import { AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types';
+import { configureStore } from 'app/store/configureStore';
import * as useAlertManagerSources from '../hooks/useAlertManagerSources';
import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY } from '../utils/constants';
@@ -21,14 +23,21 @@ const externalAmMimir: AlertManagerDataSource = {
imgUrl: '',
};
+function getProviderWrapper() {
+ const reduxStore = configureStore();
+ return ({ children }: React.PropsWithChildren) => (
+
+ {children}
+
+ );
+}
+
describe('useAlertmanager', () => {
it('Should return undefined alert manager name when there are no available alert managers', () => {
jest
.spyOn(useAlertManagerSources, 'useAlertManagersByPermission')
.mockReturnValueOnce({ availableExternalDataSources: [], availableInternalDataSources: [] });
- const wrapper = ({ children }: React.PropsWithChildren) => (
- {children}
- );
+ const wrapper = getProviderWrapper();
const { result } = renderHook(() => useAlertmanager(), { wrapper });
expect(result.current.selectedAlertmanager).toBe(undefined);
@@ -40,9 +49,7 @@ describe('useAlertmanager', () => {
availableInternalDataSources: [{ name: GRAFANA_RULES_SOURCE_NAME, imgUrl: '', hasConfigurationAPI: true }],
});
- const wrapper = ({ children }: React.PropsWithChildren) => (
- {children}
- );
+ const wrapper = getProviderWrapper();
const { result } = renderHook(() => useAlertmanager(), { wrapper });
expect(result.current.selectedAlertmanager).toBe(GRAFANA_RULES_SOURCE_NAME);
@@ -55,9 +62,7 @@ describe('useAlertmanager', () => {
locationService.push({ search: `alertmanager=${externalAmProm.name}` });
- const wrapper = ({ children }: React.PropsWithChildren) => (
- {children}
- );
+ const wrapper = getProviderWrapper();
const { result } = renderHook(() => useAlertmanager(), { wrapper });
expect(result.current.selectedAlertmanager).toBe(externalAmProm.name);
@@ -70,9 +75,7 @@ describe('useAlertmanager', () => {
locationService.push({ search: `alertmanager=Not available external AM` });
- const wrapper = ({ children }: React.PropsWithChildren) => (
- {children}
- );
+ const wrapper = getProviderWrapper();
const { result } = renderHook(() => useAlertmanager(), { wrapper });
expect(result.current.selectedAlertmanager).toBe(undefined);
@@ -83,9 +86,7 @@ describe('useAlertmanager', () => {
.spyOn(useAlertManagerSources, 'useAlertManagersByPermission')
.mockReturnValueOnce({ availableExternalDataSources: [externalAmProm], availableInternalDataSources: [] });
- const wrapper = ({ children }: React.PropsWithChildren) => (
- {children}
- );
+ const wrapper = getProviderWrapper();
store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, externalAmProm.name);
locationService.push({ search: '' });
@@ -101,9 +102,7 @@ describe('useAlertmanager', () => {
locationService.push({ search: `alertmanager=${externalAmProm.name}` });
- const wrapper = ({ children }: React.PropsWithChildren) => (
- {children}
- );
+ const wrapper = getProviderWrapper();
store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, externalAmMimir.name);
diff --git a/public/app/features/alerting/unified/state/AlertmanagerContext.tsx b/public/app/features/alerting/unified/state/AlertmanagerContext.tsx
index d196dd7b565..22846f18352 100644
--- a/public/app/features/alerting/unified/state/AlertmanagerContext.tsx
+++ b/public/app/features/alerting/unified/state/AlertmanagerContext.tsx
@@ -1,10 +1,13 @@
import * as React from 'react';
-import { locationService } from '@grafana/runtime';
+import { config, locationService } from '@grafana/runtime';
import store from 'app/core/store';
import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types';
+import grafanaIconSvg from 'img/grafana_icon.svg';
import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources';
+import { useAlertmanagerConfig } from '../hooks/useAlertmanagerConfig';
+import { EXTRA_CONFIG_UID, isExtraConfig } from '../utils/alertmanager/extraConfigs';
import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants';
import {
AlertManagerDataSource,
@@ -33,9 +36,46 @@ const AlertmanagerProvider = ({ children, accessType, alertmanagerSourceName }:
const queryParams = locationService.getSearch();
const updateQueryParams = locationService.partial;
const allAvailableAlertManagers = useAlertManagersByPermission(accessType);
- const availableAlertManagers = allAvailableAlertManagers.availableInternalDataSources.concat(
- allAvailableAlertManagers.availableExternalDataSources
- );
+ const isExtraConfigEnabled = config.featureToggles.alertingImportAlertmanagerUI ?? false;
+
+ const { data: grafanaConfig } = useAlertmanagerConfig(isExtraConfigEnabled ? GRAFANA_RULES_SOURCE_NAME : undefined, {
+ refetchOnFocus: false,
+ refetchOnReconnect: false,
+ });
+ const hasExtraConfigs = Boolean(grafanaConfig?.extra_config && grafanaConfig.extra_config.length > 0);
+
+ const availableAlertManagers = React.useMemo(() => {
+ const regularAlertManagers = allAvailableAlertManagers.availableInternalDataSources.concat(
+ allAvailableAlertManagers.availableExternalDataSources
+ );
+
+ const extraConfigDataSource: AlertManagerDataSource[] =
+ isExtraConfigEnabled && hasExtraConfigs
+ ? [
+ {
+ name: EXTRA_CONFIG_UID,
+ displayName: 'Grafana (imported)',
+ imgUrl: grafanaIconSvg,
+ hasConfigurationAPI: false,
+ handleGrafanaManagedAlerts: true,
+ },
+ ]
+ : [];
+
+ // list in order: Grafana -> Extra Config -> Extra Alertmanagers
+ const grafanaAlertmanager = regularAlertManagers.find((am) => am.name === GRAFANA_RULES_SOURCE_NAME);
+ const datasourceAlertmanagers = regularAlertManagers.filter((am) => am.name !== GRAFANA_RULES_SOURCE_NAME);
+ const orderedAlertManagers: AlertManagerDataSource[] = [];
+
+ if (grafanaAlertmanager) {
+ orderedAlertManagers.push(grafanaAlertmanager);
+ }
+
+ orderedAlertManagers.push(...extraConfigDataSource);
+ orderedAlertManagers.push(...datasourceAlertmanagers);
+
+ return orderedAlertManagers;
+ }, [allAvailableAlertManagers, isExtraConfigEnabled, hasExtraConfigs]);
const updateSelectedAlertmanager = React.useCallback(
(selectedAlertManager: string) => {
@@ -73,7 +113,16 @@ const AlertmanagerProvider = ({ children, accessType, alertmanagerSourceName }:
? desiredAlertmanager
: undefined;
- const selectedAlertmanagerConfig = getAlertmanagerDataSourceByName(selectedAlertmanager)?.jsonData;
+ const selectedAlertmanagerConfig = React.useMemo(() => {
+ if (selectedAlertmanager && isExtraConfig(selectedAlertmanager)) {
+ const config: AlertManagerDataSourceJsonData = {
+ implementation: AlertManagerImplementation.prometheus,
+ handleGrafanaManagedAlerts: true,
+ };
+ return config;
+ }
+ return getAlertmanagerDataSourceByName(selectedAlertmanager)?.jsonData;
+ }, [selectedAlertmanager]);
// determine if we're dealing with an Alertmanager data source that supports the ruler API
const isGrafanaAlertmanager = selectedAlertmanager === GRAFANA_RULES_SOURCE_NAME;
diff --git a/public/app/features/alerting/unified/utils/alertmanager/extraConfigs.test.ts b/public/app/features/alerting/unified/utils/alertmanager/extraConfigs.test.ts
new file mode 100644
index 00000000000..ca16e7a4baf
--- /dev/null
+++ b/public/app/features/alerting/unified/utils/alertmanager/extraConfigs.test.ts
@@ -0,0 +1,24 @@
+import { EXTRA_CONFIG_UID, isExtraConfig } from './extraConfigs';
+
+describe('extraConfigs utilities', () => {
+ describe('isExtraConfig', () => {
+ it('should return true for the exact extra config UID', () => {
+ expect(isExtraConfig(EXTRA_CONFIG_UID)).toBe(true);
+ });
+
+ it('should return false for non-extra config names', () => {
+ expect(isExtraConfig('grafana')).toBe(false);
+ expect(isExtraConfig('prometheus-am')).toBe(false);
+ expect(isExtraConfig('regular-alertmanager')).toBe(false);
+ expect(isExtraConfig('')).toBe(false);
+ expect(isExtraConfig('~grafana-converted-extra-config-test')).toBe(false); // old pattern
+ expect(isExtraConfig('~grafana-with-extra-config-suffix')).toBe(false); // with suffix
+ });
+ });
+
+ describe('EXTRA_CONFIG_UID constant', () => {
+ it('should have the correct UID value', () => {
+ expect(EXTRA_CONFIG_UID).toBe('~grafana-with-extra-config');
+ });
+ });
+});
diff --git a/public/app/features/alerting/unified/utils/alertmanager/extraConfigs.ts b/public/app/features/alerting/unified/utils/alertmanager/extraConfigs.ts
new file mode 100644
index 00000000000..6fe8ac88ee6
--- /dev/null
+++ b/public/app/features/alerting/unified/utils/alertmanager/extraConfigs.ts
@@ -0,0 +1,15 @@
+export const EXTRA_CONFIG_UID = '~grafana-with-extra-config';
+
+export interface ExtraConfiguration {
+ identifier: string;
+ source?: string;
+ createdAt?: string;
+}
+
+export interface AlertingConfigResponse {
+ extra_config?: ExtraConfiguration[];
+}
+
+export function isExtraConfig(name: string): boolean {
+ return name === EXTRA_CONFIG_UID;
+}
diff --git a/public/app/features/alerting/unified/utils/datasource.ts b/public/app/features/alerting/unified/utils/datasource.ts
index 4bcf3757d2c..545e2b1ab6d 100644
--- a/public/app/features/alerting/unified/utils/datasource.ts
+++ b/public/app/features/alerting/unified/utils/datasource.ts
@@ -27,6 +27,7 @@ import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources';
import { isAlertManagerWithConfigAPI } from '../state/AlertmanagerContext';
import { instancesPermissions, notificationsPermissions, silencesPermissions } from './access-control';
+import { isExtraConfig } from './alertmanager/extraConfigs';
import { getAllDataSources } from './config';
import { isGrafanaRuleIdentifier } from './rules';
@@ -52,6 +53,7 @@ export enum DataSourceType {
export interface AlertManagerDataSource {
name: string;
+ displayName?: string;
imgUrl: string;
meta?: DataSourceInstanceSettings['meta'];
hasConfigurationAPI?: boolean;
@@ -300,6 +302,11 @@ export function getDatasourceAPIUid(dataSourceName: string) {
if (dataSourceName === GRAFANA_RULES_SOURCE_NAME) {
return GRAFANA_RULES_SOURCE_NAME;
}
+
+ if (isExtraConfig(dataSourceName)) {
+ return dataSourceName;
+ }
+
const ds = getDataSourceByName(dataSourceName);
if (!ds) {
throw new Error(`Datasource "${dataSourceName}" not found`);
diff --git a/public/app/plugins/datasource/alertmanager/types.ts b/public/app/plugins/datasource/alertmanager/types.ts
index 9a16da6d743..ef56b44c767 100644
--- a/public/app/plugins/datasource/alertmanager/types.ts
+++ b/public/app/plugins/datasource/alertmanager/types.ts
@@ -1,6 +1,7 @@
//DOCS: https://prometheus.io/docs/alerting/latest/configuration/
import { DataSourceJsonData, WithAccessControlMetadata } from '@grafana/data';
import { IoK8SApimachineryPkgApisMetaV1ObjectMeta } from 'app/features/alerting/unified/openapi/receiversApi.gen';
+import { ExtraConfiguration } from 'app/features/alerting/unified/utils/alertmanager/extraConfigs';
export const ROUTES_META_SYMBOL = Symbol('routes_metadata');
@@ -11,6 +12,7 @@ export type AlertManagerCortexConfig = {
template_file_provenances?: Record;
last_applied?: string;
id?: number;
+ extra_config?: ExtraConfiguration[];
};
export type TLSConfig = {
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 7ca4d97c8e3..ba574221f71 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -493,6 +493,11 @@
"title-muting-grouping-and-timings": "Muting, grouping, and timings"
},
"alert-manager-picker": {
+ "external-alertmanagers-group": "External Alertmanagers",
+ "extra-config-warning": {
+ "content": "This shows the merged configuration of Grafana alertmanager with imported configurations. This merged view is read-only in the UI.",
+ "title": "Imported configuration"
+ },
"noOptionsMessage-no-datasources-found": "No datasources found"
},
"alert-menu": {