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": {