From 7f6806e2202efb0f8404291416f3f0feea111b13 Mon Sep 17 00:00:00 2001
From: Gilles De Mey <gilles.de.mey@gmail.com>
Date: Thu, 1 Feb 2024 14:18:43 +0100
Subject: [PATCH] Alerting: Refactor `useExternalDataSourceAlertmanagers`
 (#81081)

---
 .../alerting/unified/api/alertingApi.ts       |   8 +-
 .../alerting/unified/api/dataSourcesApi.ts    |  15 ++
 .../admin/ExternalAlertmanagerDataSources.tsx |  18 +-
 .../admin/ExternalAlertmanagers.tsx           |   5 +-
 .../components/rules/RuleListErrors.tsx       |   6 +-
 .../hooks/useExternalAMSelector.test.tsx      | 196 +++++++-----------
 .../unified/hooks/useExternalAmSelector.ts    | 152 +++++++++-----
 public/app/features/alerting/unified/mocks.ts |  13 --
 .../features/alerting/unified/utils/config.ts |   5 +-
 .../alerting/unified/utils/datasource.ts      |  25 ++-
 .../features/alerting/unified/utils/misc.ts   |   6 +-
 11 files changed, 226 insertions(+), 223 deletions(-)
 create mode 100644 public/app/features/alerting/unified/api/dataSourcesApi.ts

diff --git a/public/app/features/alerting/unified/api/alertingApi.ts b/public/app/features/alerting/unified/api/alertingApi.ts
index 2ec7fc1d5c6..d4f9118395e 100644
--- a/public/app/features/alerting/unified/api/alertingApi.ts
+++ b/public/app/features/alerting/unified/api/alertingApi.ts
@@ -27,6 +27,12 @@ export const backendSrvBaseQuery = (): BaseQueryFn<BackendSrvRequest> => async (
 export const alertingApi = createApi({
   reducerPath: 'alertingApi',
   baseQuery: backendSrvBaseQuery(),
-  tagTypes: ['AlertmanagerChoice', 'AlertmanagerConfiguration', 'OnCallIntegrations', 'OrgMigrationState'],
+  tagTypes: [
+    'AlertmanagerChoice',
+    'AlertmanagerConfiguration',
+    'OnCallIntegrations',
+    'OrgMigrationState',
+    'DataSourceSettings',
+  ],
   endpoints: () => ({}),
 });
diff --git a/public/app/features/alerting/unified/api/dataSourcesApi.ts b/public/app/features/alerting/unified/api/dataSourcesApi.ts
new file mode 100644
index 00000000000..94bb2de0937
--- /dev/null
+++ b/public/app/features/alerting/unified/api/dataSourcesApi.ts
@@ -0,0 +1,15 @@
+import { DataSourceJsonData, DataSourceSettings } from '@grafana/data';
+
+import { alertingApi } from './alertingApi';
+
+export const dataSourcesApi = alertingApi.injectEndpoints({
+  endpoints: (build) => ({
+    getAllDataSourceSettings: build.query<Array<DataSourceSettings<DataSourceJsonData>>, void>({
+      query: () => ({ url: 'api/datasources' }),
+      // we'll create individual cache entries for each datasource UID
+      providesTags: (result) => {
+        return result ? result.map(({ uid }) => ({ type: 'DataSourceSettings', id: uid })) : ['DataSourceSettings'];
+      },
+    }),
+  }),
+});
diff --git a/public/app/features/alerting/unified/components/admin/ExternalAlertmanagerDataSources.tsx b/public/app/features/alerting/unified/components/admin/ExternalAlertmanagerDataSources.tsx
index d84f930d3cc..757346b7976 100644
--- a/public/app/features/alerting/unified/components/admin/ExternalAlertmanagerDataSources.tsx
+++ b/public/app/features/alerting/unified/components/admin/ExternalAlertmanagerDataSources.tsx
@@ -5,11 +5,11 @@ import React from 'react';
 import { GrafanaTheme2 } from '@grafana/data';
 import { Badge, CallToActionCard, Card, Icon, LinkButton, Tooltip, useStyles2 } from '@grafana/ui';
 
-import { ExternalDataSourceAM } from '../../hooks/useExternalAmSelector';
+import { ExternalAlertmanagerDataSourceWithStatus } from '../../hooks/useExternalAmSelector';
 import { makeDataSourceLink } from '../../utils/misc';
 
 export interface ExternalAlertManagerDataSourcesProps {
-  alertmanagers: ExternalDataSourceAM[];
+  alertmanagers: ExternalAlertmanagerDataSourceWithStatus[];
   inactive: boolean;
 }
 
@@ -39,7 +39,7 @@ export function ExternalAlertmanagerDataSources({ alertmanagers, inactive }: Ext
       {alertmanagers.length > 0 && (
         <div className={styles.externalDs}>
           {alertmanagers.map((am) => (
-            <ExternalAMdataSourceCard key={am.dataSource.uid} alertmanager={am} inactive={inactive} />
+            <ExternalAMdataSourceCard key={am.dataSourceSettings.uid} alertmanager={am} inactive={inactive} />
           ))}
         </div>
       )}
@@ -48,20 +48,20 @@ export function ExternalAlertmanagerDataSources({ alertmanagers, inactive }: Ext
 }
 
 interface ExternalAMdataSourceCardProps {
-  alertmanager: ExternalDataSourceAM;
+  alertmanager: ExternalAlertmanagerDataSourceWithStatus;
   inactive: boolean;
 }
 
 export function ExternalAMdataSourceCard({ alertmanager, inactive }: ExternalAMdataSourceCardProps) {
   const styles = useStyles2(getStyles);
 
-  const { dataSource, status, statusInconclusive, url } = alertmanager;
+  const { dataSourceSettings, status } = alertmanager;
 
   return (
     <Card>
       <Card.Heading className={styles.externalHeading}>
-        {dataSource.name}{' '}
-        {statusInconclusive && (
+        {dataSourceSettings.name}{' '}
+        {status === 'inconclusive' && (
           <Tooltip content="Multiple Alertmanagers have the same URL configured. The state might be inconclusive.">
             <Icon name="exclamation-triangle" size="md" className={styles.externalWarningIcon} />
           </Tooltip>
@@ -90,9 +90,9 @@ export function ExternalAMdataSourceCard({ alertmanager, inactive }: ExternalAMd
           />
         )}
       </Card.Tags>
-      <Card.Meta>{url}</Card.Meta>
+      <Card.Meta>{dataSourceSettings.url}</Card.Meta>
       <Card.Actions>
-        <LinkButton href={makeDataSourceLink(dataSource)} size="sm" variant="secondary">
+        <LinkButton href={makeDataSourceLink(dataSourceSettings.uid)} size="sm" variant="secondary">
           Go to datasource
         </LinkButton>
       </Card.Actions>
diff --git a/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx b/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx
index 1bfb98fe018..ddcf5b9176d 100644
--- a/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx
+++ b/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx
@@ -23,6 +23,9 @@ export const ExternalAlertmanagers = () => {
   const dispatch = useDispatch();
 
   const externalDsAlertManagers = useExternalDataSourceAlertmanagers();
+  const gmaHandlingAlertmanagers = externalDsAlertManagers.filter(
+    (settings) => settings.dataSourceSettings.jsonData.handleGrafanaManagedAlerts === true
+  );
 
   const {
     useSaveExternalAlertmanagersConfigMutation,
@@ -71,7 +74,7 @@ export const ExternalAlertmanagers = () => {
       </div>
 
       <ExternalAlertmanagerDataSources
-        alertmanagers={externalDsAlertManagers}
+        alertmanagers={gmaHandlingAlertmanagers}
         inactive={alertmanagersChoice === AlertmanagerChoice.Internal}
       />
     </div>
diff --git a/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx b/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx
index c595014ac96..16da99ee314 100644
--- a/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx
+++ b/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx
@@ -53,7 +53,7 @@ export function RuleListErrors(): ReactElement {
       result.push(
         <>
           Failed to load the data source configuration for{' '}
-          <a href={makeDataSourceLink(dataSource)} className={styles.dsLink}>
+          <a href={makeDataSourceLink(dataSource.uid)} className={styles.dsLink}>
             {dataSource.name}
           </a>
           : {error.message || 'Unknown error.'}
@@ -65,7 +65,7 @@ export function RuleListErrors(): ReactElement {
       result.push(
         <>
           Failed to load rules state from{' '}
-          <a href={makeDataSourceLink(dataSource)} className={styles.dsLink}>
+          <a href={makeDataSourceLink(dataSource.uid)} className={styles.dsLink}>
             {dataSource.name}
           </a>
           : {error.message || 'Unknown error.'}
@@ -77,7 +77,7 @@ export function RuleListErrors(): ReactElement {
       result.push(
         <>
           Failed to load rules config from{' '}
-          <a href={makeDataSourceLink(dataSource)} className={styles.dsLink}>
+          <a href={makeDataSourceLink(dataSource.uid)} className={styles.dsLink}>
             {dataSource.name}
           </a>
           : {error.message || 'Unknown error.'}
diff --git a/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx b/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx
index f45b2bc0ffe..6feda94a91c 100644
--- a/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx
+++ b/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx
@@ -1,16 +1,15 @@
 import { renderHook, waitFor } from '@testing-library/react';
-import { setupServer } from 'msw/node';
-import React from 'react';
-import { Provider } from 'react-redux';
+import { rest } from 'msw';
+import { SetupServer, setupServer } from 'msw/node';
+import { TestProvider } from 'test/helpers/TestProvider';
 
 import 'whatwg-fetch';
 
-import { DataSourceJsonData, DataSourceSettings } from '@grafana/data';
-import { config, setBackendSrv } from '@grafana/runtime';
+import { DataSourceSettings } from '@grafana/data';
+import { setBackendSrv } from '@grafana/runtime';
 import { backendSrv } from 'app/core/services/backend_srv';
 import { AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types';
 
-import { mockDataSource, mockDataSourcesStore, mockStore } from '../mocks';
 import { mockAlertmanagersResponse } from '../mocks/alertmanagerApi';
 
 import { useExternalDataSourceAlertmanagers } from './useExternalAmSelector';
@@ -31,46 +30,42 @@ afterAll(() => {
 });
 
 describe('useExternalDataSourceAlertmanagers', () => {
-  it('Should merge data sources information from config and api responses', async () => {
+  it('Should get the correct data source settings', async () => {
     // Arrange
-    const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' });
-
-    config.datasources = {
-      'External Alertmanager': dsInstanceSettings,
-    };
-
-    const store = mockDataSourcesStore({
-      dataSources: [dsSettings],
-    });
-
+    setupAlertmanagerDataSource(server, { url: 'http://grafana.com' });
     mockAlertmanagersResponse(server, { data: { activeAlertManagers: [], droppedAlertManagers: [] } });
 
-    const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
-
     // Act
-    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
+    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper: TestProvider });
     await waitFor(() => {
       // Assert
       const { current } = result;
 
       expect(current).toHaveLength(1);
-      expect(current[0].dataSource.uid).toBe('1');
-      expect(current[0].url).toBe('http://grafana.com');
+      expect(current[0].dataSourceSettings.uid).toBe('1');
+      expect(current[0].dataSourceSettings.url).toBe('http://grafana.com');
+    });
+  });
+
+  it('Should have uninterested state if data source does not want alerts', async () => {
+    // Arrange
+    setupAlertmanagerDataSource(server, { url: 'http://grafana.com', jsonData: { handleGrafanaManagedAlerts: false } });
+    mockAlertmanagersResponse(server, { data: { activeAlertManagers: [], droppedAlertManagers: [] } });
+
+    // Act
+    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper: TestProvider });
+    await waitFor(() => {
+      // Assert
+      const { current } = result;
+
+      expect(current).toHaveLength(1);
+      expect(current[0].status).toBe('uninterested');
     });
   });
 
   it('Should have active state if available in the activeAlertManagers', async () => {
     // Arrange
-    const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' });
-
-    config.datasources = {
-      'External Alertmanager': dsInstanceSettings,
-    };
-
-    const store = mockStore((state) => {
-      state.dataSources.dataSources = [dsSettings];
-    });
-
+    setupAlertmanagerDataSource(server, { url: 'http://grafana.com' });
     mockAlertmanagersResponse(server, {
       data: {
         activeAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }],
@@ -78,32 +73,20 @@ describe('useExternalDataSourceAlertmanagers', () => {
       },
     });
 
-    const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
-
     // Act
-    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
+    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper: TestProvider });
     await waitFor(() => {
       // Assert
       const { current } = result;
 
       expect(current).toHaveLength(1);
       expect(current[0].status).toBe('active');
-      expect(current[0].statusInconclusive).toBe(false);
     });
   });
 
   it('Should have dropped state if available in the droppedAlertManagers', async () => {
     // Arrange
-    const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' });
-
-    config.datasources = {
-      'External Alertmanager': dsInstanceSettings,
-    };
-
-    const store = mockStore((state) => {
-      state.dataSources.dataSources = [dsSettings];
-    });
-
+    setupAlertmanagerDataSource(server, { url: 'http://grafana.com' });
     mockAlertmanagersResponse(server, {
       data: {
         activeAlertManagers: [],
@@ -111,10 +94,8 @@ describe('useExternalDataSourceAlertmanagers', () => {
       },
     });
 
-    const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
-
     // Act
-    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
+    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper: TestProvider });
 
     await waitFor(() => {
       // Assert
@@ -122,22 +103,12 @@ describe('useExternalDataSourceAlertmanagers', () => {
 
       expect(current).toHaveLength(1);
       expect(current[0].status).toBe('dropped');
-      expect(current[0].statusInconclusive).toBe(false);
     });
   });
 
   it('Should have pending state if not available neither in dropped nor in active alertManagers', async () => {
     // Arrange
-    const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource();
-
-    config.datasources = {
-      'External Alertmanager': dsInstanceSettings,
-    };
-
-    const store = mockStore((state) => {
-      state.dataSources.dataSources = [dsSettings];
-    });
-
+    setupAlertmanagerDataSource(server);
     mockAlertmanagersResponse(server, {
       data: {
         activeAlertManagers: [],
@@ -145,10 +116,8 @@ describe('useExternalDataSourceAlertmanagers', () => {
       },
     });
 
-    const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
-
     // Act
-    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
+    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper: TestProvider });
 
     await waitFor(() => {
       // Assert
@@ -156,22 +125,12 @@ describe('useExternalDataSourceAlertmanagers', () => {
 
       expect(current).toHaveLength(1);
       expect(current[0].status).toBe('pending');
-      expect(current[0].statusInconclusive).toBe(false);
     });
   });
 
   it('Should match Alertmanager url when datasource url does not have protocol specified', async () => {
     // Arrange
-    const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'localhost:9093' });
-
-    config.datasources = {
-      'External Alertmanager': dsInstanceSettings,
-    };
-
-    const store = mockStore((state) => {
-      state.dataSources.dataSources = [dsSettings];
-    });
-
+    setupAlertmanagerDataSource(server, { url: 'localhost:9093' });
     mockAlertmanagersResponse(server, {
       data: {
         activeAlertManagers: [{ url: 'http://localhost:9093/api/v2/alerts' }],
@@ -179,10 +138,8 @@ describe('useExternalDataSourceAlertmanagers', () => {
       },
     });
 
-    const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
-
     // Act
-    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
+    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper: TestProvider });
 
     await waitFor(() => {
       // Assert
@@ -190,11 +147,34 @@ describe('useExternalDataSourceAlertmanagers', () => {
 
       expect(current).toHaveLength(1);
       expect(current[0].status).toBe('active');
-      expect(current[0].url).toBe('localhost:9093');
+      expect(current[0].dataSourceSettings.url).toBe('localhost:9093');
     });
   });
 
-  it('Should have inconclusive state when there are many Alertmanagers of the same URL', async () => {
+  it('Should have inconclusive state when there are many Alertmanagers of the same URL on both active and inactive', async () => {
+    // Arrange
+    mockAlertmanagersResponse(server, {
+      data: {
+        activeAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }],
+        droppedAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }],
+      },
+    });
+
+    setupAlertmanagerDataSource(server, { url: 'http://grafana.com' });
+
+    // Act
+    const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), {
+      wrapper: TestProvider,
+    });
+
+    await waitFor(() => {
+      // Assert
+      expect(result.current).toHaveLength(1);
+      expect(result.current[0].status).toBe('inconclusive');
+    });
+  });
+
+  it('Should have not have inconclusive state when all Alertmanagers of the same URL are active', async () => {
     // Arrange
     mockAlertmanagersResponse(server, {
       data: {
@@ -203,72 +183,40 @@ describe('useExternalDataSourceAlertmanagers', () => {
       },
     });
 
-    const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' });
-
-    config.datasources = {
-      'External Alertmanager': dsInstanceSettings,
-    };
-
-    const store = mockStore((state) => {
-      state.dataSources.dataSources = [dsSettings];
-    });
-
-    const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
+    setupAlertmanagerDataSource(server, { url: 'http://grafana.com' });
 
     // Act
     const { result } = renderHook(() => useExternalDataSourceAlertmanagers(), {
-      wrapper,
+      wrapper: TestProvider,
     });
 
     await waitFor(() => {
       // Assert
       expect(result.current).toHaveLength(1);
       expect(result.current[0].status).toBe('active');
-      expect(result.current[0].statusInconclusive).toBe(true);
     });
   });
 });
 
-function setupAlertmanagerDataSource(partialDsSettings?: Partial<DataSourceSettings<AlertManagerDataSourceJsonData>>) {
+function setupAlertmanagerDataSource(
+  server: SetupServer,
+  partialDsSettings?: Partial<DataSourceSettings<AlertManagerDataSourceJsonData>>
+) {
   const dsCommonConfig = {
     uid: '1',
     name: 'External Alertmanager',
     type: 'alertmanager',
-    jsonData: { handleGrafanaManagedAlerts: true } as AlertManagerDataSourceJsonData,
+    jsonData: { handleGrafanaManagedAlerts: true },
   };
 
-  const dsInstanceSettings = mockDataSource(dsCommonConfig);
-
-  const dsSettings = mockApiDataSource({
+  const dsSettings = {
     ...dsCommonConfig,
     ...partialDsSettings,
-  });
-
-  return { dsSettings, dsInstanceSettings };
-}
-
-function mockApiDataSource(partial: Partial<DataSourceSettings<DataSourceJsonData, {}>> = {}) {
-  const dsSettings: DataSourceSettings<DataSourceJsonData, {}> = {
-    uid: '1',
-    id: 1,
-    name: '',
-    url: '',
-    type: '',
-    access: '',
-    orgId: 1,
-    typeLogoUrl: '',
-    typeName: '',
-    user: '',
-    database: '',
-    basicAuth: false,
-    isDefault: false,
-    basicAuthUser: '',
-    jsonData: { handleGrafanaManagedAlerts: true } as AlertManagerDataSourceJsonData,
-    secureJsonFields: {},
-    readOnly: false,
-    withCredentials: false,
-    ...partial,
   };
 
-  return dsSettings;
+  server.use(
+    rest.get('/api/datasources', (_req, res, ctx) => {
+      return res(ctx.json([dsSettings]));
+    })
+  );
 }
diff --git a/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts b/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts
index 8ffe85bb8cb..8cbe7228698 100644
--- a/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts
+++ b/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts
@@ -1,76 +1,114 @@
-import { countBy, keyBy } from 'lodash';
-import { createSelector } from 'reselect';
-
-import { DataSourceInstanceSettings, DataSourceJsonData, DataSourceSettings } from '@grafana/data';
-import { AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types';
-import { StoreState, useSelector } from 'app/types';
+import { DataSourceSettings } from '@grafana/data';
+import { AlertManagerDataSourceJsonData, ExternalAlertmanagers } from 'app/plugins/datasource/alertmanager/types';
 
 import { alertmanagerApi } from '../api/alertmanagerApi';
-import { getAlertManagerDataSources } from '../utils/datasource';
+import { dataSourcesApi } from '../api/dataSourcesApi';
+import { isAlertmanagerDataSource } from '../utils/datasource';
 
-export interface ExternalDataSourceAM {
-  dataSource: DataSourceInstanceSettings<AlertManagerDataSourceJsonData>;
-  url?: string;
-  status: 'active' | 'pending' | 'dropped';
-  statusInconclusive?: boolean;
+type ConnectionStatus = 'active' | 'pending' | 'dropped' | 'inconclusive' | 'uninterested' | 'unknown';
+
+export interface ExternalAlertmanagerDataSourceWithStatus {
+  dataSourceSettings: DataSourceSettings<AlertManagerDataSourceJsonData>;
+  status: ConnectionStatus;
 }
 
-export function useExternalDataSourceAlertmanagers(): ExternalDataSourceAM[] {
-  const { useGetExternalAlertmanagersQuery } = alertmanagerApi;
-  const { currentData: discoveredAlertmanagers } = useGetExternalAlertmanagersQuery();
+/**
+ * Returns all configured Alertmanager data sources and their connection status with the internal ruler
+ */
+export function useExternalDataSourceAlertmanagers(): ExternalAlertmanagerDataSourceWithStatus[] {
+  // firstly we'll fetch the settings for all datasources and filter for "alertmanager" type
+  const { alertmanagerDataSources } = dataSourcesApi.endpoints.getAllDataSourceSettings.useQuery(undefined, {
+    refetchOnReconnect: true,
+    selectFromResult: (result) => {
+      const alertmanagerDataSources = result.currentData?.filter(isAlertmanagerDataSource) ?? [];
+      return { ...result, alertmanagerDataSources };
+    },
+  });
 
-  const externalDsAlertManagers = getAlertManagerDataSources().filter((ds) => ds.jsonData.handleGrafanaManagedAlerts);
-
-  const alertmanagerDatasources = useSelector(
-    createSelector(
-      (state: StoreState) => state.dataSources.dataSources.filter((ds) => ds.type === 'alertmanager'),
-      (datasources) => keyBy(datasources, (ds) => ds.uid)
-    )
+  // we'll also fetch the configuration for which Alertmanagers we are forwarding Grafana-managed alerts too
+  // @TODO use polling when we have one or more alertmanagers in pending state
+  const { currentData: externalAlertmanagers } = alertmanagerApi.endpoints.getExternalAlertmanagers.useQuery(
+    undefined,
+    { refetchOnReconnect: true }
   );
 
-  const droppedAMUrls = countBy(discoveredAlertmanagers?.droppedAlertManagers, (x) => x.url);
-  const activeAMUrls = countBy(discoveredAlertmanagers?.activeAlertManagers, (x) => x.url);
+  if (!alertmanagerDataSources) {
+    return [];
+  }
 
-  return externalDsAlertManagers.map<ExternalDataSourceAM>((dsAm) => {
-    const dsSettings = alertmanagerDatasources[dsAm.uid];
-
-    if (!dsSettings) {
-      return {
-        dataSource: dsAm,
-        status: 'pending',
-      };
-    }
-
-    const amUrl = getDataSourceUrlWithProtocol(dsSettings);
-    const amStatusUrl = `${amUrl}/api/v2/alerts`;
-
-    const matchingDroppedUrls = droppedAMUrls[amStatusUrl] ?? 0;
-    const matchingActiveUrls = activeAMUrls[amStatusUrl] ?? 0;
-
-    const isDropped = matchingDroppedUrls > 0;
-    const isActive = matchingActiveUrls > 0;
-
-    // Multiple Alertmanagers of the same URL may exist (e.g. with different credentials)
-    // Alertmanager response only contains URLs, so in case of duplication, we are not able
-    // to distinguish which is which, resulting in an inconclusive status.
-    const isStatusInconclusive = matchingDroppedUrls + matchingActiveUrls > 1;
-
-    const status = isDropped ? 'dropped' : isActive ? 'active' : 'pending';
+  return alertmanagerDataSources.map<ExternalAlertmanagerDataSourceWithStatus>((dataSourceSettings) => {
+    const status = externalAlertmanagers
+      ? determineAlertmanagerConnectionStatus(externalAlertmanagers, dataSourceSettings)
+      : 'unknown';
 
     return {
-      dataSource: dsAm,
-      url: dsSettings.url,
+      dataSourceSettings,
       status,
-      statusInconclusive: isStatusInconclusive,
     };
   });
 }
 
-function getDataSourceUrlWithProtocol<T extends DataSourceJsonData>(dsSettings: DataSourceSettings<T>) {
-  const hasProtocol = new RegExp('^[^:]*://').test(dsSettings.url);
-  if (!hasProtocol) {
-    return `http://${dsSettings.url}`; // Grafana append http protocol if there is no any
+// using the information from /api/v1/ngalert/alertmanagers we should derive the connection status of a single data source
+function determineAlertmanagerConnectionStatus(
+  externalAlertmanagers: ExternalAlertmanagers,
+  dataSourceSettings: DataSourceSettings<AlertManagerDataSourceJsonData>
+): ConnectionStatus {
+  const isInterestedInAlerts = dataSourceSettings.jsonData.handleGrafanaManagedAlerts;
+  if (!isInterestedInAlerts) {
+    return 'uninterested';
   }
 
-  return dsSettings.url;
+  const isActive =
+    externalAlertmanagers?.activeAlertManagers.some((am) => {
+      return isAlertmanagerMatchByURL(dataSourceSettings.url, am.url);
+    }) ?? [];
+
+  const isDropped =
+    externalAlertmanagers?.droppedAlertManagers.some((am) => {
+      return isAlertmanagerMatchByURL(dataSourceSettings.url, am.url);
+    }) ?? [];
+
+  // the Alertmanager is being adopted (pending) if it is interested in handling alerts but not in either "active" or "dropped"
+  const isPending = !isActive && !isDropped;
+  if (isPending) {
+    return 'pending';
+  }
+
+  // Multiple Alertmanagers of the same URL may exist (e.g. with different credentials)
+  // Alertmanager response only contains URLs, so when the URL exists in both active and dropped, we are not able
+  // to distinguish which is which, resulting in an inconclusive status.
+  const isInconclusive = isActive && isDropped;
+  if (isInconclusive) {
+    return 'inconclusive';
+  }
+
+  // if we get here, it's neither "uninterested", nor "inconclusive" nor "pending"
+  if (isActive) {
+    return 'active';
+  } else if (isDropped) {
+    return 'dropped';
+  }
+
+  return 'unknown';
+}
+
+// the vanilla Alertmanager and Mimir Alertmanager mount their API endpoints on different sub-paths
+// Cortex also uses the same paths as Mimir
+const MIMIR_ALERTMANAGER_PATH = '/alertmanager/api/v2/alerts';
+const VANILLA_ALERTMANAGER_PATH = '/api/v2/alerts';
+
+// when using the Mimir Alertmanager, those paths are mounted under "/alertmanager"
+function isAlertmanagerMatchByURL(dataSourceUrl: string, alertmanagerUrl: string) {
+  const normalizedUrl = normalizeDataSourceURL(dataSourceUrl);
+
+  const prometheusAlertmanagerMatch = alertmanagerUrl === `${normalizedUrl}${VANILLA_ALERTMANAGER_PATH}`;
+  const mimirAlertmanagerMatch = alertmanagerUrl === `${normalizedUrl}${MIMIR_ALERTMANAGER_PATH}`;
+
+  return prometheusAlertmanagerMatch || mimirAlertmanagerMatch;
+}
+
+// Grafana prepends the http protocol if there isn't one, but it doesn't store that in the datasource settings
+function normalizeDataSourceURL(url: string) {
+  const hasProtocol = new RegExp('^[^:]*://').test(url);
+  return hasProtocol ? url : `http://${url}`;
 }
diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts
index 1c6f623090f..88a216b58fb 100644
--- a/public/app/features/alerting/unified/mocks.ts
+++ b/public/app/features/alerting/unified/mocks.ts
@@ -602,19 +602,6 @@ export const grantUserPermissions = (permissions: AccessControlAction[]) => {
     .mockImplementation((action) => permissions.includes(action as AccessControlAction));
 };
 
-export function mockDataSourcesStore(partial?: Partial<StoreState['dataSources']>) {
-  const defaultState = configureStore().getState();
-  const store = configureStore({
-    ...defaultState,
-    dataSources: {
-      ...defaultState.dataSources,
-      ...partial,
-    },
-  });
-
-  return store;
-}
-
 export function mockUnifiedAlertingStore(unifiedAlerting?: Partial<StoreState['unifiedAlerting']>) {
   const defaultState = configureStore().getState();
 
diff --git a/public/app/features/alerting/unified/utils/config.ts b/public/app/features/alerting/unified/utils/config.ts
index 9472a8b543e..d5f7817561d 100644
--- a/public/app/features/alerting/unified/utils/config.ts
+++ b/public/app/features/alerting/unified/utils/config.ts
@@ -1,12 +1,9 @@
 import { DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data';
 import { config } from '@grafana/runtime';
-import { AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types';
 
 import { isValidPrometheusDuration, parsePrometheusDuration } from './time';
 
-export function getAllDataSources(): Array<
-  DataSourceInstanceSettings<DataSourceJsonData | AlertManagerDataSourceJsonData>
-> {
+export function getAllDataSources(): Array<DataSourceInstanceSettings<DataSourceJsonData>> {
   return Object.values(config.datasources);
 }
 
diff --git a/public/app/features/alerting/unified/utils/datasource.ts b/public/app/features/alerting/unified/utils/datasource.ts
index de43e658649..e2f70cb8586 100644
--- a/public/app/features/alerting/unified/utils/datasource.ts
+++ b/public/app/features/alerting/unified/utils/datasource.ts
@@ -1,4 +1,4 @@
-import { DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data';
+import { DataSourceInstanceSettings, DataSourceJsonData, DataSourceSettings } from '@grafana/data';
 import { getDataSourceSrv } from '@grafana/runtime';
 import { contextSrv } from 'app/core/services/context_srv';
 import {
@@ -51,12 +51,22 @@ export function getRulesDataSource(rulesSourceName: string) {
 
 export function getAlertManagerDataSources() {
   return getAllDataSources()
-    .filter(
-      (ds): ds is DataSourceInstanceSettings<AlertManagerDataSourceJsonData> => ds.type === DataSourceType.Alertmanager
-    )
+    .filter(isAlertmanagerDataSourceInstance)
     .sort((a, b) => a.name.localeCompare(b.name));
 }
 
+export function isAlertmanagerDataSourceInstance(
+  dataSource: DataSourceInstanceSettings
+): dataSource is DataSourceInstanceSettings<AlertManagerDataSourceJsonData> {
+  return dataSource.type === DataSourceType.Alertmanager;
+}
+
+export function isAlertmanagerDataSource(
+  dataSource: DataSourceSettings
+): dataSource is DataSourceSettings<AlertManagerDataSourceJsonData> {
+  return dataSource.type === DataSourceType.Alertmanager;
+}
+
 export function getExternalDsAlertManagers() {
   return getAlertManagerDataSources().filter((ds) => ds.jsonData.handleGrafanaManagedAlerts);
 }
@@ -205,10 +215,9 @@ export function getDataSourceByName(name: string): DataSourceInstanceSettings<Da
 }
 
 export function getAlertmanagerDataSourceByName(name: string) {
-  return getAllDataSources().find(
-    (source): source is DataSourceInstanceSettings<AlertManagerDataSourceJsonData> =>
-      source.name === name && source.type === 'alertmanager'
-  );
+  return getAllDataSources()
+    .filter(isAlertmanagerDataSourceInstance)
+    .find((source) => source.name === name);
 }
 
 export function getRulesSourceByName(name: string): RulesSource | undefined {
diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts
index 1b0017f3601..c6e4739353d 100644
--- a/public/app/features/alerting/unified/utils/misc.ts
+++ b/public/app/features/alerting/unified/utils/misc.ts
@@ -1,6 +1,6 @@
 import { sortBy } from 'lodash';
 
-import { UrlQueryMap, Labels, DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data';
+import { UrlQueryMap, Labels } from '@grafana/data';
 import { GrafanaEdition } from '@grafana/data/src/types/config';
 import { config } from '@grafana/runtime';
 import { DataSourceRef } from '@grafana/schema';
@@ -137,8 +137,8 @@ export function makeLabelBasedSilenceLink(alertManagerSourceName: string, labels
   return createUrl('/alerting/silence/new', silenceUrlParams);
 }
 
-export function makeDataSourceLink<T extends DataSourceJsonData>(dataSource: DataSourceInstanceSettings<T>) {
-  return createUrl(`/datasources/edit/${dataSource.uid}`);
+export function makeDataSourceLink(uid: string) {
+  return createUrl(`/datasources/edit/${uid}`);
 }
 
 export function makeFolderLink(folderUID: string): string {