mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add Alertmanager choice warning (#55311)
This commit is contained in:
@@ -6,6 +6,9 @@ import { Alert, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { AlertmanagerChoice } from '../../../plugins/datasource/alertmanager/types';
|
||||
|
||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||
import { AlertGroup } from './components/alert-groups/AlertGroup';
|
||||
@@ -17,10 +20,13 @@ import { useGroupedAlerts } from './hooks/useGroupedAlerts';
|
||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||
import { fetchAlertGroupsAction } from './state/actions';
|
||||
import { NOTIFICATIONS_POLL_INTERVAL_MS } from './utils/constants';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||
import { getFiltersFromUrlParams } from './utils/misc';
|
||||
import { initialAsyncRequestState } from './utils/redux';
|
||||
|
||||
const AlertGroups = () => {
|
||||
const { useGetAlertmanagerChoiceQuery } = alertmanagerApi;
|
||||
|
||||
const alertManagers = useAlertManagersByPermission('instance');
|
||||
const [alertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||
const dispatch = useDispatch();
|
||||
@@ -28,6 +34,8 @@ const AlertGroups = () => {
|
||||
const { groupBy = [] } = getFiltersFromUrlParams(queryParams);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const { currentData: alertmanagerChoice } = useGetAlertmanagerChoiceQuery();
|
||||
|
||||
const alertGroups = useUnifiedAlertingSelector((state) => state.amAlertGroups);
|
||||
const {
|
||||
loading,
|
||||
@@ -38,6 +46,9 @@ const AlertGroups = () => {
|
||||
const groupedAlerts = useGroupedAlerts(results, groupBy);
|
||||
const filteredAlertGroups = useFilteredAmGroups(groupedAlerts);
|
||||
|
||||
const grafanaAmDeliveryDisabled =
|
||||
alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME && alertmanagerChoice === AlertmanagerChoice.External;
|
||||
|
||||
useEffect(() => {
|
||||
function fetchNotifications() {
|
||||
if (alertManagerSourceName) {
|
||||
@@ -68,6 +79,14 @@ const AlertGroups = () => {
|
||||
{error.message || 'Unknown error'}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{grafanaAmDeliveryDisabled && (
|
||||
<Alert title="Grafana alerts are not delivered to Grafana Alertmanager">
|
||||
Grafana is configured to send alerts to external alertmanagers only. No alerts are expected to be available
|
||||
here for the selected Alertmanager.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{results &&
|
||||
filteredAlertGroups.map((group, index) => {
|
||||
return (
|
||||
|
||||
@@ -8,8 +8,10 @@ import { useDispatch } from 'app/types';
|
||||
|
||||
import { useCleanup } from '../../../core/hooks/useCleanup';
|
||||
|
||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
||||
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||
import { ProvisionedResource, ProvisioningAlert } from './components/Provisioning';
|
||||
import { AmRootRoute } from './components/amroutes/AmRootRoute';
|
||||
@@ -26,10 +28,12 @@ import { initialAsyncRequestState } from './utils/redux';
|
||||
|
||||
const AmRoutes = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { useGetAlertmanagerChoiceQuery } = alertmanagerApi;
|
||||
const styles = useStyles2(getStyles);
|
||||
const [isRootRouteEditMode, setIsRootRouteEditMode] = useState(false);
|
||||
const alertManagers = useAlertManagersByPermission('notification');
|
||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||
const { currentData: alertmanagerChoice } = useGetAlertmanagerChoiceQuery();
|
||||
|
||||
const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs);
|
||||
|
||||
@@ -127,6 +131,10 @@ const AmRoutes = () => {
|
||||
{resultError.message || 'Unknown error.'}
|
||||
</Alert>
|
||||
)}
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager={alertManagerSourceName}
|
||||
alertmanagerChoice={alertmanagerChoice}
|
||||
/>
|
||||
{isProvisioned && <ProvisioningAlert resource={ProvisionedResource.RootNotificationPolicy} />}
|
||||
{resultLoading && <LoadingPlaceholder text="Loading Alertmanager config..." />}
|
||||
{result && !resultLoading && !resultError && (
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
import { render, waitFor, within, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { setupServer } from 'msw/node';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||
import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testing-library-selector';
|
||||
|
||||
import { locationService, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { locationService, setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { interceptLinkClicks } from 'app/core/navigation/patch/interceptLinkClicks';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import store from 'app/core/store';
|
||||
import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types';
|
||||
import {
|
||||
AlertmanagerChoice,
|
||||
AlertManagerDataSourceJsonData,
|
||||
AlertManagerImplementation,
|
||||
} from 'app/plugins/datasource/alertmanager/types';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { AccessControlAction, ContactPointsState } from 'app/types';
|
||||
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import Receivers from './Receivers';
|
||||
import { updateAlertManagerConfig, fetchAlertManagerConfig, fetchStatus, testReceivers } from './api/alertmanager';
|
||||
import { fetchAlertManagerConfig, fetchStatus, testReceivers, updateAlertManagerConfig } from './api/alertmanager';
|
||||
import { discoverAlertmanagerFeatures } from './api/buildInfo';
|
||||
import { fetchNotifiers } from './api/grafana';
|
||||
import * as receiversApi from './api/receiversApi';
|
||||
@@ -26,6 +34,7 @@ import {
|
||||
someCloudAlertManagerStatus,
|
||||
someGrafanaAlertManagerConfig,
|
||||
} from './mocks';
|
||||
import { mockAlertmanagerChoiceResponse } from './mocks/alertmanagerApi';
|
||||
import { grafanaNotifiersMock } from './mocks/grafana-notifiers';
|
||||
import { getAllDataSources } from './utils/config';
|
||||
import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from './utils/constants';
|
||||
@@ -135,7 +144,19 @@ document.addEventListener('click', interceptLinkClicks);
|
||||
const emptyContactPointsState: ContactPointsState = { receivers: {}, errorCount: 0 };
|
||||
|
||||
describe('Receivers', () => {
|
||||
const server = setupServer();
|
||||
|
||||
beforeAll(() => {
|
||||
setBackendSrv(backendSrv);
|
||||
server.listen({ onUnhandledRequest: 'error' });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
server.resetHandlers();
|
||||
jest.resetAllMocks();
|
||||
mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
|
||||
mocks.api.fetchNotifiers.mockResolvedValue(grafanaNotifiersMock);
|
||||
@@ -160,6 +181,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Template and receiver tables are rendered, alertmanager can be selected, no notification errors', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mocks.api.fetchConfig.mockImplementation((name) =>
|
||||
Promise.resolve(name === GRAFANA_RULES_SOURCE_NAME ? someGrafanaAlertManagerConfig : someCloudAlertManagerConfig)
|
||||
);
|
||||
@@ -204,6 +226,8 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Grafana receiver can be tested', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
|
||||
await renderReceivers();
|
||||
@@ -260,6 +284,8 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Grafana receiver can be created', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
await renderReceivers();
|
||||
@@ -322,6 +348,8 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Hides create contact point button for users without permission', () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
mocks.contextSrv.hasAccess.mockImplementation((action) =>
|
||||
@@ -336,6 +364,8 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Cloud alertmanager receiver can be edited', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
|
||||
mocks.api.fetchConfig.mockResolvedValue(someCloudAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
await renderReceivers('CloudManager');
|
||||
@@ -430,6 +460,8 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Prometheus Alertmanager receiver cannot be edited', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
|
||||
mocks.api.fetchStatus.mockResolvedValue({
|
||||
...someCloudAlertManagerStatus,
|
||||
config: someCloudAlertManagerConfig.alertmanager_config,
|
||||
@@ -467,6 +499,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Loads config from status endpoint if there is no user config', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
// loading an empty config with make it fetch config from status endpoint
|
||||
mocks.api.fetchConfig.mockResolvedValue({
|
||||
template_files: {},
|
||||
@@ -488,6 +521,8 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Shows an empty config when config returns an error and the AM supports lazy config initialization', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
|
||||
mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: true });
|
||||
mocks.api.fetchConfig.mockRejectedValue({ message: 'alertmanager storage object not found' });
|
||||
|
||||
@@ -500,8 +535,10 @@ describe('Receivers', () => {
|
||||
expect(receiversTable).toBeInTheDocument();
|
||||
expect(ui.newContactPointButton.get()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Contact points state', () => {
|
||||
it('Should render error notifications when there are some points state ', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
|
||||
@@ -573,6 +610,7 @@ describe('Receivers', () => {
|
||||
expect(byText('OK').getAll(criticalDetailTable)).toHaveLength(2);
|
||||
});
|
||||
it('Should render no attempt message when there are some points state with null lastNotifyAttempt, and "-" in null values', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
|
||||
@@ -649,6 +687,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Should not render error notifications when fetching contact points state raises 404 error ', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ import { NavModelItem, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, LoadingPlaceholder, withErrorBoundary, useStyles2, Icon, Stack } from '@grafana/ui';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { ContactPointsState } from '../../../types/alerting';
|
||||
import { ContactPointsState } from '../../../types';
|
||||
|
||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
||||
import { useGetContactPointsState } from './api/receiversApi';
|
||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
||||
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||
import { EditReceiverView } from './components/receivers/EditReceiverView';
|
||||
import { EditTemplateView } from './components/receivers/EditTemplateView';
|
||||
@@ -48,14 +50,16 @@ function NotificationError({ errorCount }: NotificationErrorProps) {
|
||||
);
|
||||
}
|
||||
|
||||
type PageType = 'receivers' | 'templates' | 'global-config';
|
||||
|
||||
const Receivers = () => {
|
||||
const { useGetAlertmanagerChoiceQuery } = alertmanagerApi;
|
||||
|
||||
const alertManagers = useAlertManagersByPermission('notification');
|
||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||
const dispatch = useDispatch();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
type PageType = 'receivers' | 'templates' | 'global-config';
|
||||
|
||||
const { id, type } = useParams<{ id?: string; type?: PageType }>();
|
||||
const location = useLocation();
|
||||
const isRoot = location.pathname.endsWith('/alerting/notifications');
|
||||
@@ -87,31 +91,15 @@ const Receivers = () => {
|
||||
dispatch(fetchGrafanaNotifiersAction());
|
||||
}
|
||||
}, [alertManagerSourceName, dispatch, receiverTypes]);
|
||||
|
||||
const contactPointsState: ContactPointsState = useGetContactPointsState(alertManagerSourceName ?? '');
|
||||
const integrationsErrorCount = contactPointsState?.errorCount ?? 0;
|
||||
|
||||
const { data: alertmanagerChoice } = useGetAlertmanagerChoiceQuery();
|
||||
|
||||
const disableAmSelect = !isRoot;
|
||||
|
||||
let pageNav: NavModelItem | undefined;
|
||||
if (type === 'receivers' || type === 'templates') {
|
||||
const objectText = type === 'receivers' ? 'contact point' : 'message template';
|
||||
if (id) {
|
||||
pageNav = {
|
||||
text: id,
|
||||
subTitle: `Edit the settings for a specific ${objectText}`,
|
||||
};
|
||||
} else {
|
||||
pageNav = {
|
||||
text: `New ${objectText}`,
|
||||
subTitle: `Create a new ${objectText} for your notifications`,
|
||||
};
|
||||
}
|
||||
} else if (type === 'global-config') {
|
||||
pageNav = {
|
||||
text: 'Global config',
|
||||
subTitle: 'Manage your global configuration',
|
||||
};
|
||||
}
|
||||
let pageNav = getPageNavigationModel(type, id);
|
||||
|
||||
if (!alertManagerSourceName) {
|
||||
return isRoot ? (
|
||||
@@ -141,6 +129,10 @@ const Receivers = () => {
|
||||
{error.message || 'Unknown error.'}
|
||||
</Alert>
|
||||
)}
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
alertmanagerChoice={alertmanagerChoice}
|
||||
currentAlertmanager={alertManagerSourceName}
|
||||
/>
|
||||
{loading && !config && <LoadingPlaceholder text="loading configuration..." />}
|
||||
{config && !error && (
|
||||
<Switch>
|
||||
@@ -184,6 +176,30 @@ const Receivers = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function getPageNavigationModel(type: PageType | undefined, id: string | undefined) {
|
||||
let pageNav: NavModelItem | undefined;
|
||||
if (type === 'receivers' || type === 'templates') {
|
||||
const objectText = type === 'receivers' ? 'contact point' : 'message template';
|
||||
if (id) {
|
||||
pageNav = {
|
||||
text: id,
|
||||
subTitle: `Edit the settings for a specific ${objectText}`,
|
||||
};
|
||||
} else {
|
||||
pageNav = {
|
||||
text: `New ${objectText}`,
|
||||
subTitle: `Create a new ${objectText} for your notifications`,
|
||||
};
|
||||
}
|
||||
} else if (type === 'global-config') {
|
||||
pageNav = {
|
||||
text: 'Global config',
|
||||
subTitle: 'Manage your global configuration',
|
||||
};
|
||||
}
|
||||
return pageNav;
|
||||
}
|
||||
|
||||
export default withErrorBoundary(Receivers, { style: 'page' });
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
|
||||
@@ -5,9 +5,11 @@ import { Alert, withErrorBoundary } from '@grafana/ui';
|
||||
import { Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
||||
import { featureDiscoveryApi } from './api/featureDiscoveryApi';
|
||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
||||
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||
import SilencesEditor from './components/silences/SilencesEditor';
|
||||
import SilencesTable from './components/silences/SilencesTable';
|
||||
@@ -24,6 +26,7 @@ const Silences = () => {
|
||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { useGetAlertmanagerChoiceQuery } = alertmanagerApi;
|
||||
const silences = useUnifiedAlertingSelector((state) => state.silences);
|
||||
const alertsRequests = useUnifiedAlertingSelector((state) => state.amAlerts);
|
||||
const alertsRequest = alertManagerSourceName
|
||||
@@ -39,6 +42,8 @@ const Silences = () => {
|
||||
{ skip: !alertManagerSourceName }
|
||||
);
|
||||
|
||||
const { currentData: alertmanagerChoice } = useGetAlertmanagerChoiceQuery();
|
||||
|
||||
useEffect(() => {
|
||||
function fetchAll() {
|
||||
if (alertManagerSourceName) {
|
||||
@@ -79,6 +84,10 @@ const Silences = () => {
|
||||
onChange={setAlertManagerSourceName}
|
||||
dataSources={alertManagers}
|
||||
/>
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager={alertManagerSourceName}
|
||||
alertmanagerChoice={alertmanagerChoice}
|
||||
/>
|
||||
|
||||
{mimirLazyInitError && (
|
||||
<Alert title="The selected Alertmanager has no configuration" severity="warning">
|
||||
|
||||
@@ -16,5 +16,6 @@ const backendSrvBaseQuery = (): BaseQueryFn<BackendSrvRequest> => async (request
|
||||
export const alertingApi = createApi({
|
||||
reducerPath: 'alertingApi',
|
||||
baseQuery: backendSrvBaseQuery(),
|
||||
tagTypes: ['AlertmanagerChoice'],
|
||||
endpoints: () => ({}),
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
AlertManagerCortexConfig,
|
||||
AlertmanagerGroup,
|
||||
AlertmanagerStatus,
|
||||
ExternalAlertmanagerConfig,
|
||||
ExternalAlertmanagersResponse,
|
||||
Matcher,
|
||||
Receiver,
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
TestReceiversAlert,
|
||||
TestReceiversPayload,
|
||||
TestReceiversResult,
|
||||
ExternalAlertmanagerConfig,
|
||||
} from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
|
||||
37
public/app/features/alerting/unified/api/alertmanagerApi.ts
Normal file
37
public/app/features/alerting/unified/api/alertmanagerApi.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
AlertmanagerChoice,
|
||||
ExternalAlertmanagerConfig,
|
||||
ExternalAlertmanagers,
|
||||
ExternalAlertmanagersResponse,
|
||||
} from '../../../../plugins/datasource/alertmanager/types';
|
||||
|
||||
import { alertingApi } from './alertingApi';
|
||||
|
||||
export interface AlertmanagersChoiceResponse {
|
||||
alertmanagersChoice: AlertmanagerChoice;
|
||||
}
|
||||
|
||||
export const alertmanagerApi = alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
getAlertmanagerChoice: build.query<AlertmanagerChoice, void>({
|
||||
query: () => ({ url: '/api/v1/ngalert' }),
|
||||
providesTags: ['AlertmanagerChoice'],
|
||||
transformResponse: (response: AlertmanagersChoiceResponse) => response.alertmanagersChoice,
|
||||
}),
|
||||
|
||||
getExternalAlertmanagerConfig: build.query<ExternalAlertmanagerConfig, void>({
|
||||
query: () => ({ url: '/api/v1/ngalert/admin_config' }),
|
||||
providesTags: ['AlertmanagerChoice'],
|
||||
}),
|
||||
|
||||
getExternalAlertmanagers: build.query<ExternalAlertmanagers, void>({
|
||||
query: () => ({ url: '/api/v1/ngalert/alertmanagers' }),
|
||||
transformResponse: (response: ExternalAlertmanagersResponse) => response.data,
|
||||
}),
|
||||
|
||||
saveExternalAlertmanagersConfig: build.mutation<{ message: string }, ExternalAlertmanagerConfig>({
|
||||
query: (config) => ({ url: '/api/v1/ngalert/admin_config', method: 'POST', data: config }),
|
||||
invalidatesTags: ['AlertmanagerChoice'],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
|
||||
import { GrafanaAlertmanagerDeliveryWarning } from './GrafanaAlertmanagerDeliveryWarning';
|
||||
|
||||
describe('GrafanaAlertmanagerDeliveryWarning', () => {
|
||||
describe('When AlertmanagerChoice set to External', () => {
|
||||
it('Should not render when the datasource is not Grafana', () => {
|
||||
const { container } = render(
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager="custom-alertmanager"
|
||||
alertmanagerChoice={AlertmanagerChoice.External}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Should render warning when the datasource is Grafana', () => {
|
||||
const { container } = render(
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager={GRAFANA_RULES_SOURCE_NAME}
|
||||
alertmanagerChoice={AlertmanagerChoice.External}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container).toHaveTextContent('Grafana alerts are not delivered to Grafana Alertmanager');
|
||||
});
|
||||
});
|
||||
|
||||
it.each([AlertmanagerChoice.All, AlertmanagerChoice.Internal])(
|
||||
'Should not render when datasource is Grafana and Alertmanager choice is %s',
|
||||
(choice) => {
|
||||
const { container } = render(
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager={GRAFANA_RULES_SOURCE_NAME}
|
||||
alertmanagerChoice={choice}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data/src';
|
||||
import { Alert, useStyles2 } from '@grafana/ui/src';
|
||||
|
||||
import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
|
||||
interface GrafanaAlertmanagerDeliveryWarningProps {
|
||||
alertmanagerChoice?: AlertmanagerChoice;
|
||||
currentAlertmanager: string;
|
||||
}
|
||||
|
||||
export function GrafanaAlertmanagerDeliveryWarning({
|
||||
alertmanagerChoice,
|
||||
currentAlertmanager,
|
||||
}: GrafanaAlertmanagerDeliveryWarningProps) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (currentAlertmanager !== GRAFANA_RULES_SOURCE_NAME) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (alertmanagerChoice !== AlertmanagerChoice.External) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert title="Grafana alerts are not delivered to Grafana Alertmanager">
|
||||
Grafana is configured to send alerts to external Alertmanagers only. Changing Grafana Alertmanager configuration
|
||||
will not affect delivery of your alerts!
|
||||
<div className={styles.adminHint}>
|
||||
You can change the configuration on the Alerting Admin page. If you do not have access, contact your
|
||||
Administrator
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
adminHint: css`
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
font-weight: ${theme.typography.bodySmall.fontWeight};
|
||||
`,
|
||||
});
|
||||
@@ -17,15 +17,10 @@ import {
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { loadDataSources } from 'app/features/datasources/state/actions';
|
||||
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { useDispatch, useSelector } from 'app/types';
|
||||
import { StoreState } from 'app/types/store';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { alertmanagerApi } from '../../api/alertmanagerApi';
|
||||
import { useExternalAmSelector, useExternalDataSourceAlertmanagers } from '../../hooks/useExternalAmSelector';
|
||||
import {
|
||||
addExternalAlertmanagersAction,
|
||||
fetchExternalAlertmanagersAction,
|
||||
fetchExternalAlertmanagersConfigAction,
|
||||
} from '../../state/actions';
|
||||
|
||||
import { AddAlertManagerModal } from './AddAlertManagerModal';
|
||||
import { ExternalAlertmanagerDataSources } from './ExternalAlertmanagerDataSources';
|
||||
@@ -45,20 +40,23 @@ export const ExternalAlertmanagers = () => {
|
||||
const externalAlertManagers = useExternalAmSelector();
|
||||
const externalDsAlertManagers = useExternalDataSourceAlertmanagers();
|
||||
|
||||
const alertmanagersChoice = useSelector(
|
||||
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.alertmanagerConfig.result?.alertmanagersChoice
|
||||
);
|
||||
const {
|
||||
useSaveExternalAlertmanagersConfigMutation,
|
||||
useGetExternalAlertmanagerConfigQuery,
|
||||
useGetExternalAlertmanagersQuery,
|
||||
} = alertmanagerApi;
|
||||
|
||||
const [saveExternalAlertManagers] = useSaveExternalAlertmanagersConfigMutation();
|
||||
const { currentData: externalAlertmanagerConfig } = useGetExternalAlertmanagerConfigQuery();
|
||||
|
||||
// Just to refresh the status periodically
|
||||
useGetExternalAlertmanagersQuery(undefined, { pollingInterval: 5000 });
|
||||
|
||||
const alertmanagersChoice = externalAlertmanagerConfig?.alertmanagersChoice;
|
||||
const theme = useTheme2();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchExternalAlertmanagersAction());
|
||||
dispatch(fetchExternalAlertmanagersConfigAction());
|
||||
dispatch(loadDataSources());
|
||||
const interval = setInterval(() => dispatch(fetchExternalAlertmanagersAction()), 5000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const onDelete = useCallback(
|
||||
@@ -69,15 +67,15 @@ export const ExternalAlertmanagers = () => {
|
||||
.map((am) => {
|
||||
return am.url;
|
||||
});
|
||||
dispatch(
|
||||
addExternalAlertmanagersAction({
|
||||
alertmanagers: newList,
|
||||
alertmanagersChoice: alertmanagersChoice ?? AlertmanagerChoice.All,
|
||||
})
|
||||
);
|
||||
|
||||
saveExternalAlertManagers({
|
||||
alertmanagers: newList,
|
||||
alertmanagersChoice: alertmanagersChoice ?? AlertmanagerChoice.All,
|
||||
});
|
||||
|
||||
setDeleteModalState({ open: false, index: 0 });
|
||||
},
|
||||
[externalAlertManagers, dispatch, alertmanagersChoice]
|
||||
[externalAlertManagers, saveExternalAlertManagers, alertmanagersChoice]
|
||||
);
|
||||
|
||||
const onEdit = useCallback(() => {
|
||||
@@ -108,18 +106,14 @@ export const ExternalAlertmanagers = () => {
|
||||
}, [setModalState]);
|
||||
|
||||
const onChangeAlertmanagerChoice = (alertmanagersChoice: AlertmanagerChoice) => {
|
||||
dispatch(
|
||||
addExternalAlertmanagersAction({ alertmanagers: externalAlertManagers.map((am) => am.url), alertmanagersChoice })
|
||||
);
|
||||
saveExternalAlertManagers({ alertmanagers: externalAlertManagers.map((am) => am.url), alertmanagersChoice });
|
||||
};
|
||||
|
||||
const onChangeAlertmanagers = (alertmanagers: string[]) => {
|
||||
dispatch(
|
||||
addExternalAlertmanagersAction({
|
||||
alertmanagers,
|
||||
alertmanagersChoice: alertmanagersChoice ?? AlertmanagerChoice.All,
|
||||
})
|
||||
);
|
||||
saveExternalAlertManagers({
|
||||
alertmanagers,
|
||||
alertmanagersChoice: alertmanagersChoice ?? AlertmanagerChoice.All,
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
|
||||
@@ -1,21 +1,58 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { setupServer } from 'msw/node';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import { DataSourceJsonData, DataSourceSettings } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { AlertmanagerChoice, AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { mockDataSource, mockDataSourcesStore, mockStore } from '../mocks';
|
||||
import { mockAlertmanagerConfigResponse, mockAlertmanagersResponse } from '../mocks/alertmanagerApi';
|
||||
|
||||
import { useExternalAmSelector, useExternalDataSourceAlertmanagers } from './useExternalAmSelector';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen({ onUnhandledRequest: 'error' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
describe('useExternalAmSelector', () => {
|
||||
it('should have one in pending', () => {
|
||||
const store = createMockStoreState([], [], ['some/url/to/am']);
|
||||
it('should have one in pending', async () => {
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
});
|
||||
mockAlertmanagerConfigResponse(server, {
|
||||
alertmanagers: ['some/url/to/am'],
|
||||
alertmanagersChoice: AlertmanagerChoice.All,
|
||||
});
|
||||
const store = mockStore(() => null);
|
||||
|
||||
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
|
||||
const { result } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
const alertmanagers = result.current;
|
||||
const { result, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
@@ -26,15 +63,24 @@ describe('useExternalAmSelector', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have one active, one pending', () => {
|
||||
const store = createMockStoreState(
|
||||
[{ url: 'some/url/to/am/api/v2/alerts' }],
|
||||
[],
|
||||
['some/url/to/am', 'some/url/to/am1']
|
||||
);
|
||||
it('should have one active, one pending', async () => {
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [{ url: 'some/url/to/am/api/v2/alerts' }],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
});
|
||||
mockAlertmanagerConfigResponse(server, {
|
||||
alertmanagers: ['some/url/to/am', 'some/url/to/am1'],
|
||||
alertmanagersChoice: AlertmanagerChoice.All,
|
||||
});
|
||||
const store = mockStore(() => null);
|
||||
|
||||
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
|
||||
const { result } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
const alertmanagers = result.current;
|
||||
const { result, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
@@ -50,15 +96,24 @@ describe('useExternalAmSelector', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have two active', () => {
|
||||
const store = createMockStoreState(
|
||||
[{ url: 'some/url/to/am/api/v2/alerts' }, { url: 'some/url/to/am1/api/v2/alerts' }],
|
||||
[],
|
||||
['some/url/to/am', 'some/url/to/am1']
|
||||
);
|
||||
it('should have two active', async () => {
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [{ url: 'some/url/to/am/api/v2/alerts' }, { url: 'some/url/to/am1/api/v2/alerts' }],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
});
|
||||
mockAlertmanagerConfigResponse(server, {
|
||||
alertmanagers: ['some/url/to/am', 'some/url/to/am1'],
|
||||
alertmanagersChoice: AlertmanagerChoice.All,
|
||||
});
|
||||
const store = mockStore(() => null);
|
||||
|
||||
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
|
||||
const { result } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
const alertmanagers = result.current;
|
||||
const { result, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
@@ -74,16 +129,25 @@ describe('useExternalAmSelector', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have one active, one dropped, one pending', () => {
|
||||
const store = createMockStoreState(
|
||||
[{ url: 'some/url/to/am/api/v2/alerts' }],
|
||||
[{ url: 'some/dropped/url/api/v2/alerts' }],
|
||||
['some/url/to/am', 'some/url/to/am1']
|
||||
);
|
||||
it('should have one active, one dropped, one pending', async () => {
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [{ url: 'some/url/to/am/api/v2/alerts' }],
|
||||
droppedAlertManagers: [{ url: 'some/dropped/url/api/v2/alerts' }],
|
||||
},
|
||||
});
|
||||
mockAlertmanagerConfigResponse(server, {
|
||||
alertmanagers: ['some/url/to/am', 'some/url/to/am1'],
|
||||
alertmanagersChoice: AlertmanagerChoice.All,
|
||||
});
|
||||
const store = mockStore(() => null);
|
||||
|
||||
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
const { result } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
const alertmanagers = result.current;
|
||||
const { result, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
url: 'some/url/to/am',
|
||||
@@ -103,20 +167,29 @@ describe('useExternalAmSelector', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('The number of alert managers should match config entries when there are multiple entries of the same url', () => {
|
||||
const store = createMockStoreState(
|
||||
[
|
||||
{ url: 'same/url/to/am/api/v2/alerts' },
|
||||
{ url: 'same/url/to/am/api/v2/alerts' },
|
||||
{ url: 'same/url/to/am/api/v2/alerts' },
|
||||
],
|
||||
[],
|
||||
['same/url/to/am', 'same/url/to/am', 'same/url/to/am']
|
||||
);
|
||||
it('The number of alert managers should match config entries when there are multiple entries of the same url', async () => {
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [
|
||||
{ url: 'same/url/to/am/api/v2/alerts' },
|
||||
{ url: 'same/url/to/am/api/v2/alerts' },
|
||||
{ url: 'same/url/to/am/api/v2/alerts' },
|
||||
],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
});
|
||||
mockAlertmanagerConfigResponse(server, {
|
||||
alertmanagers: ['same/url/to/am', 'same/url/to/am', 'same/url/to/am'],
|
||||
alertmanagersChoice: AlertmanagerChoice.All,
|
||||
});
|
||||
const store = mockStore(() => null);
|
||||
|
||||
const wrapper = ({ children }: React.PropsWithChildren<{}>) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
const { result } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
const alertmanagers = result.current;
|
||||
const { result, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
|
||||
expect(alertmanagers.length).toBe(3);
|
||||
expect(alertmanagers).toEqual([
|
||||
@@ -140,7 +213,7 @@ describe('useExternalAmSelector', () => {
|
||||
});
|
||||
|
||||
describe('useExternalDataSourceAlertmanagers', () => {
|
||||
it('Should merge data sources information from config and api responses', () => {
|
||||
it('Should merge data sources information from config and api responses', async () => {
|
||||
// Arrange
|
||||
const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' });
|
||||
|
||||
@@ -152,20 +225,23 @@ describe('useExternalDataSourceAlertmanagers', () => {
|
||||
dataSources: [dsSettings],
|
||||
});
|
||||
|
||||
mockAlertmanagersResponse(server, { data: { activeAlertManagers: [], droppedAlertManagers: [] } });
|
||||
|
||||
const wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
// Act
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
await waitForNextUpdate();
|
||||
|
||||
// Assert
|
||||
const { current } = result;
|
||||
|
||||
expect(current).toHaveLength(1);
|
||||
expect(current[0].dataSource.uid).toBe('1');
|
||||
expect(current[0].url).toBe('http://grafana.com');
|
||||
});
|
||||
|
||||
it('Should have active state if available in the activeAlertManagers', () => {
|
||||
it('Should have active state if available in the activeAlertManagers', async () => {
|
||||
// Arrange
|
||||
const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' });
|
||||
|
||||
@@ -175,28 +251,30 @@ describe('useExternalDataSourceAlertmanagers', () => {
|
||||
|
||||
const store = mockStore((state) => {
|
||||
state.dataSources.dataSources = [dsSettings];
|
||||
state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = {
|
||||
data: {
|
||||
activeAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
// Act
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
await waitForNextUpdate();
|
||||
|
||||
// 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', () => {
|
||||
it('Should have dropped state if available in the droppedAlertManagers', async () => {
|
||||
// Arrange
|
||||
const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' });
|
||||
|
||||
@@ -206,28 +284,30 @@ describe('useExternalDataSourceAlertmanagers', () => {
|
||||
|
||||
const store = mockStore((state) => {
|
||||
state.dataSources.dataSources = [dsSettings];
|
||||
state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = {
|
||||
data: {
|
||||
activeAlertManagers: [],
|
||||
droppedAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [],
|
||||
droppedAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }],
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
// Act
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
await waitForNextUpdate();
|
||||
|
||||
// Assert
|
||||
const { current } = result;
|
||||
|
||||
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', () => {
|
||||
it('Should have pending state if not available neither in dropped nor in active alertManagers', async () => {
|
||||
// Arrange
|
||||
const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource();
|
||||
|
||||
@@ -237,28 +317,30 @@ describe('useExternalDataSourceAlertmanagers', () => {
|
||||
|
||||
const store = mockStore((state) => {
|
||||
state.dataSources.dataSources = [dsSettings];
|
||||
state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = {
|
||||
data: {
|
||||
activeAlertManagers: [],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
// Act
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
await waitForNextUpdate();
|
||||
|
||||
// Assert
|
||||
const { current } = result;
|
||||
|
||||
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', () => {
|
||||
it('Should match Alertmanager url when datasource url does not have protocol specified', async () => {
|
||||
// Arrange
|
||||
const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'localhost:9093' });
|
||||
|
||||
@@ -268,29 +350,38 @@ describe('useExternalDataSourceAlertmanagers', () => {
|
||||
|
||||
const store = mockStore((state) => {
|
||||
state.dataSources.dataSources = [dsSettings];
|
||||
state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = {
|
||||
data: {
|
||||
activeAlertManagers: [{ url: 'http://localhost:9093/api/v2/alerts' }],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [{ url: 'http://localhost:9093/api/v2/alerts' }],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
// Act
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
await waitForNextUpdate();
|
||||
|
||||
// Assert
|
||||
const { current } = result;
|
||||
|
||||
expect(current).toHaveLength(1);
|
||||
expect(current[0].status).toBe('active');
|
||||
expect(current[0].url).toBe('localhost:9093');
|
||||
});
|
||||
|
||||
it('Should have inconclusive state when there are many Alertmanagers of the same URL', () => {
|
||||
it('Should have inconclusive state when there are many Alertmanagers of the same URL', async () => {
|
||||
// Arrange
|
||||
mockAlertmanagersResponse(server, {
|
||||
data: {
|
||||
activeAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }, { url: 'http://grafana.com/api/v2/alerts' }],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
});
|
||||
|
||||
const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' });
|
||||
|
||||
config.datasources = {
|
||||
@@ -299,28 +390,21 @@ describe('useExternalDataSourceAlertmanagers', () => {
|
||||
|
||||
const store = mockStore((state) => {
|
||||
state.dataSources.dataSources = [dsSettings];
|
||||
state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = {
|
||||
data: {
|
||||
activeAlertManagers: [
|
||||
{ url: 'http://grafana.com/api/v2/alerts' },
|
||||
{ url: 'http://grafana.com/api/v2/alerts' },
|
||||
],
|
||||
droppedAlertManagers: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
// Act
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(() => useExternalDataSourceAlertmanagers(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
// Assert
|
||||
expect(current).toHaveLength(1);
|
||||
expect(current[0].status).toBe('active');
|
||||
expect(current[0].statusInconclusive).toBe(true);
|
||||
expect(result.current).toHaveLength(1);
|
||||
expect(result.current[0].status).toBe('active');
|
||||
expect(result.current[0].statusInconclusive).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -367,22 +451,3 @@ function mockApiDataSource(partial: Partial<DataSourceSettings<DataSourceJsonDat
|
||||
|
||||
return dsSettings;
|
||||
}
|
||||
|
||||
const createMockStoreState = (
|
||||
activeAlertmanagers: Array<{ url: string }>,
|
||||
droppedAlertmanagers: Array<{ url: string }>,
|
||||
alertmanagerConfig: string[]
|
||||
) => {
|
||||
return mockStore((state) => {
|
||||
state.unifiedAlerting.externalAlertmanagers.alertmanagerConfig.result = {
|
||||
alertmanagers: alertmanagerConfig,
|
||||
alertmanagersChoice: AlertmanagerChoice.All,
|
||||
};
|
||||
state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = {
|
||||
data: {
|
||||
activeAlertManagers: activeAlertmanagers,
|
||||
droppedAlertManagers: droppedAlertmanagers,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,20 +4,17 @@ import { DataSourceInstanceSettings, DataSourceJsonData, DataSourceSettings } fr
|
||||
import { AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import { alertmanagerApi } from '../api/alertmanagerApi';
|
||||
import { getAlertManagerDataSources } from '../utils/datasource';
|
||||
|
||||
import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector';
|
||||
|
||||
const SUFFIX_REGEX = /\/api\/v[1|2]\/alerts/i;
|
||||
type AlertmanagerConfig = { url: string; status: string; actualUrl: string };
|
||||
|
||||
export function useExternalAmSelector(): AlertmanagerConfig[] | [] {
|
||||
const discoveredAlertmanagers = useSelector(
|
||||
(state) => state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result?.data
|
||||
);
|
||||
const alertmanagerConfig = useSelector(
|
||||
(state) => state.unifiedAlerting.externalAlertmanagers.alertmanagerConfig.result?.alertmanagers
|
||||
);
|
||||
const { useGetExternalAlertmanagersQuery, useGetExternalAlertmanagerConfigQuery } = alertmanagerApi;
|
||||
|
||||
const { currentData: discoveredAlertmanagers } = useGetExternalAlertmanagersQuery();
|
||||
const { currentData: alertmanagerConfig } = useGetExternalAlertmanagerConfigQuery();
|
||||
|
||||
if (!discoveredAlertmanagers || !alertmanagerConfig) {
|
||||
return [];
|
||||
@@ -30,7 +27,7 @@ export function useExternalAmSelector(): AlertmanagerConfig[] | [] {
|
||||
actualUrl: am.url,
|
||||
}));
|
||||
|
||||
for (const url of alertmanagerConfig) {
|
||||
for (const url of alertmanagerConfig.alertmanagers) {
|
||||
if (discoveredAlertmanagers.activeAlertManagers.length === 0) {
|
||||
enabledAlertmanagers.push({
|
||||
url: url,
|
||||
@@ -66,6 +63,9 @@ export interface ExternalDataSourceAM {
|
||||
}
|
||||
|
||||
export function useExternalDataSourceAlertmanagers(): ExternalDataSourceAM[] {
|
||||
const { useGetExternalAlertmanagersQuery } = alertmanagerApi;
|
||||
const { currentData: discoveredAlertmanagers } = useGetExternalAlertmanagersQuery();
|
||||
|
||||
const externalDsAlertManagers = getAlertManagerDataSources().filter((ds) => ds.jsonData.handleGrafanaManagedAlerts);
|
||||
|
||||
const alertmanagerDatasources = useSelector((state) =>
|
||||
@@ -75,10 +75,6 @@ export function useExternalDataSourceAlertmanagers(): ExternalDataSourceAM[] {
|
||||
)
|
||||
);
|
||||
|
||||
const discoveredAlertmanagers = useUnifiedAlertingSelector(
|
||||
(state) => state.externalAlertmanagers.discoveredAlertmanagers.result?.data
|
||||
);
|
||||
|
||||
const droppedAMUrls = countBy(discoveredAlertmanagers?.droppedAlertManagers, (x) => x.url);
|
||||
const activeAMUrls = countBy(discoveredAlertmanagers?.activeAlertManagers, (x) => x.url);
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { rest } from 'msw';
|
||||
import { SetupServerApi } from 'msw/node';
|
||||
|
||||
import {
|
||||
ExternalAlertmanagerConfig,
|
||||
ExternalAlertmanagersResponse,
|
||||
} from '../../../../plugins/datasource/alertmanager/types';
|
||||
import { AlertmanagersChoiceResponse } from '../api/alertmanagerApi';
|
||||
|
||||
export function mockAlertmanagerChoiceResponse(server: SetupServerApi, respose: AlertmanagersChoiceResponse) {
|
||||
server.use(rest.get('/api/v1/ngalert', (req, res, ctx) => res(ctx.status(200), ctx.json(respose))));
|
||||
}
|
||||
|
||||
export function mockAlertmanagersResponse(server: SetupServerApi, response: ExternalAlertmanagersResponse) {
|
||||
server.use(rest.get('/api/v1/ngalert/alertmanagers', (req, res, ctx) => res(ctx.status(200), ctx.json(response))));
|
||||
}
|
||||
|
||||
export function mockAlertmanagerConfigResponse(server: SetupServerApi, response: ExternalAlertmanagerConfig) {
|
||||
server.use(rest.get('/api/v1/ngalert/admin_config', (req, res, ctx) => res(ctx.status(200), ctx.json(response))));
|
||||
}
|
||||
Reference in New Issue
Block a user