mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Remove url based external alertmanagers config (#57918)
* Remove URL-based alertmanagers from endpoint config * WIP * Add migration and alertmanagers from admin_configuration * Empty comment removed * set BasicAuth true when user is present in url * Remove Alertmanagers from GET /admin_config payload * Remove URL-based alertmanager configuration from UI * Fix new uid generation in external alertmanagers migration * Fix tests for URL-based external alertmanagers * Fix API tests * Add more tests, move migration code to separate file, and remove possible am duplicate urls * Fix edge cases in migration * Fix imports * Remove useless fields and fix created_at/updated_at retrieval Co-authored-by: George Robinson <george.robinson@grafana.com> Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
This commit is contained in:
@@ -1,139 +0,0 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, Field, FieldArray, Form, Icon, Input, Modal, useStyles2 } from '@grafana/ui';
|
||||
import { AlertmanagerUrl } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
alertmanagers: AlertmanagerUrl[];
|
||||
onChangeAlertmanagerConfig: (alertmanagers: string[]) => void;
|
||||
}
|
||||
|
||||
export const AddAlertManagerModal: FC<Props> = ({ alertmanagers, onChangeAlertmanagerConfig, onClose }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const defaultValues: Record<string, AlertmanagerUrl[]> = useMemo(
|
||||
() => ({
|
||||
alertmanagers: alertmanagers,
|
||||
}),
|
||||
[alertmanagers]
|
||||
);
|
||||
|
||||
const modalTitle = (
|
||||
<div className={styles.modalTitle}>
|
||||
<Icon name="bell" className={styles.modalIcon} />
|
||||
<h3>Add Alertmanager</h3>
|
||||
</div>
|
||||
);
|
||||
|
||||
const onSubmit = (values: Record<string, AlertmanagerUrl[]>) => {
|
||||
onChangeAlertmanagerConfig(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url)));
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title={modalTitle} isOpen={true} onDismiss={onClose} className={styles.modal}>
|
||||
<div className={styles.description}>
|
||||
We use a service discovery method to find existing Alertmanagers for a given URL.
|
||||
</div>
|
||||
<Form onSubmit={onSubmit} defaultValues={defaultValues}>
|
||||
{({ register, control, errors }) => (
|
||||
<div>
|
||||
<FieldArray control={control} name="alertmanagers">
|
||||
{({ fields, append, remove }) => (
|
||||
<div className={styles.fieldArray}>
|
||||
<div className={styles.bold}>Source url</div>
|
||||
<div className={styles.muted}>
|
||||
Authentication can be done via URL (e.g. user:password@myalertmanager.com) and only the Alertmanager
|
||||
v2 API is supported. The suffix is added internally, there is no need to specify it.
|
||||
</div>
|
||||
{fields.map((field, index) => {
|
||||
return (
|
||||
<Field
|
||||
invalid={!!errors?.alertmanagers?.[index]}
|
||||
error="Field is required"
|
||||
key={`${field.id}-${index}`}
|
||||
>
|
||||
<Input
|
||||
className={styles.input}
|
||||
defaultValue={field.url}
|
||||
{...register(`alertmanagers.${index}.url`, { required: true })}
|
||||
placeholder="http://localhost:9093"
|
||||
addonAfter={
|
||||
<Button
|
||||
aria-label="Remove alertmanager"
|
||||
type="button"
|
||||
onClick={() => remove(index)}
|
||||
variant="destructive"
|
||||
className={styles.destroyInputRow}
|
||||
>
|
||||
<Icon name="trash-alt" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
);
|
||||
})}
|
||||
<Button type="button" variant="secondary" onClick={() => append({ url: '' })}>
|
||||
Add URL
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</FieldArray>
|
||||
<div>
|
||||
<Button type="submit" onSubmit={() => onSubmit}>
|
||||
Add Alertmanagers
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
function cleanAlertmanagerUrl(url: string): string {
|
||||
return url.replace(/\/$/, '').replace(/\/api\/v[1|2]\/alerts/i, '');
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const muted = css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
`;
|
||||
return {
|
||||
description: cx(
|
||||
css`
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
`,
|
||||
muted
|
||||
),
|
||||
muted: muted,
|
||||
bold: css`
|
||||
font-weight: ${theme.typography.fontWeightBold};
|
||||
`,
|
||||
modal: css``,
|
||||
modalIcon: cx(
|
||||
muted,
|
||||
css`
|
||||
margin-right: ${theme.spacing(1)};
|
||||
`
|
||||
),
|
||||
modalTitle: css`
|
||||
display: flex;
|
||||
`,
|
||||
input: css`
|
||||
margin-bottom: ${theme.spacing(1)};
|
||||
margin-right: ${theme.spacing(1)};
|
||||
`,
|
||||
inputRow: css`
|
||||
display: flex;
|
||||
`,
|
||||
destroyInputRow: css`
|
||||
padding: ${theme.spacing(1)};
|
||||
`,
|
||||
fieldArray: css`
|
||||
margin-bottom: ${theme.spacing(4)};
|
||||
`,
|
||||
};
|
||||
};
|
||||
@@ -18,7 +18,7 @@ export function ExternalAlertmanagerDataSources({ alertmanagers, inactive }: Ext
|
||||
|
||||
return (
|
||||
<>
|
||||
<h5>Alertmanagers data sources</h5>
|
||||
<h5>Alertmanagers Receiving Grafana-managed alerts</h5>
|
||||
<div className={styles.muted}>
|
||||
Alertmanager data sources support a configuration setting that allows you to choose to send Grafana-managed
|
||||
alerts to that Alertmanager. <br />
|
||||
@@ -102,6 +102,8 @@ export function ExternalAMdataSourceCard({ alertmanager, inactive }: ExternalAMd
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => ({
|
||||
muted: css`
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
line-height: ${theme.typography.bodySmall.lineHeight};
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
externalHeading: css`
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
ConfirmModal,
|
||||
Field,
|
||||
HorizontalGroup,
|
||||
Icon,
|
||||
RadioButtonGroup,
|
||||
Tooltip,
|
||||
useStyles2,
|
||||
useTheme2,
|
||||
} from '@grafana/ui';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { Alert, Field, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
||||
import { loadDataSources } from 'app/features/datasources/state/actions';
|
||||
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { alertmanagerApi } from '../../api/alertmanagerApi';
|
||||
import { useExternalAmSelector, useExternalDataSourceAlertmanagers } from '../../hooks/useExternalAmSelector';
|
||||
import { useExternalDataSourceAlertmanagers } from '../../hooks/useExternalAmSelector';
|
||||
|
||||
import { AddAlertManagerModal } from './AddAlertManagerModal';
|
||||
import { ExternalAlertmanagerDataSources } from './ExternalAlertmanagerDataSources';
|
||||
|
||||
const alertmanagerChoices: Array<SelectableValue<AlertmanagerChoice>> = [
|
||||
@@ -34,10 +21,7 @@ const alertmanagerChoices: Array<SelectableValue<AlertmanagerChoice>> = [
|
||||
export const ExternalAlertmanagers = () => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const dispatch = useDispatch();
|
||||
const [modalState, setModalState] = useState({ open: false, payload: [{ url: '' }] });
|
||||
const [deleteModalState, setDeleteModalState] = useState({ open: false, index: 0 });
|
||||
|
||||
const externalAlertManagers = useExternalAmSelector();
|
||||
const externalDsAlertManagers = useExternalDataSourceAlertmanagers();
|
||||
|
||||
const {
|
||||
@@ -53,84 +37,15 @@ export const ExternalAlertmanagers = () => {
|
||||
useGetExternalAlertmanagersQuery(undefined, { pollingInterval: 5000 });
|
||||
|
||||
const alertmanagersChoice = externalAlertmanagerConfig?.alertmanagersChoice;
|
||||
const theme = useTheme2();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(loadDataSources());
|
||||
}, [dispatch]);
|
||||
|
||||
const onDelete = useCallback(
|
||||
(index: number) => {
|
||||
// to delete we need to filter the alertmanager from the list and repost
|
||||
const newList = (externalAlertManagers ?? [])
|
||||
.filter((am, i) => i !== index)
|
||||
.map((am) => {
|
||||
return am.url;
|
||||
});
|
||||
|
||||
saveExternalAlertManagers({
|
||||
alertmanagers: newList,
|
||||
alertmanagersChoice: alertmanagersChoice ?? AlertmanagerChoice.All,
|
||||
});
|
||||
|
||||
setDeleteModalState({ open: false, index: 0 });
|
||||
},
|
||||
[externalAlertManagers, saveExternalAlertManagers, alertmanagersChoice]
|
||||
);
|
||||
|
||||
const onEdit = useCallback(() => {
|
||||
const ams = externalAlertManagers ? [...externalAlertManagers] : [{ url: '' }];
|
||||
setModalState((state) => ({
|
||||
...state,
|
||||
open: true,
|
||||
payload: ams,
|
||||
}));
|
||||
}, [setModalState, externalAlertManagers]);
|
||||
|
||||
const onOpenModal = useCallback(() => {
|
||||
setModalState((state) => {
|
||||
const ams = externalAlertManagers ? [...externalAlertManagers, { url: '' }] : [{ url: '' }];
|
||||
return {
|
||||
...state,
|
||||
open: true,
|
||||
payload: ams,
|
||||
};
|
||||
});
|
||||
}, [externalAlertManagers]);
|
||||
|
||||
const onCloseModal = useCallback(() => {
|
||||
setModalState((state) => ({
|
||||
...state,
|
||||
open: false,
|
||||
}));
|
||||
}, [setModalState]);
|
||||
|
||||
const onChangeAlertmanagerChoice = (alertmanagersChoice: AlertmanagerChoice) => {
|
||||
saveExternalAlertManagers({ alertmanagers: externalAlertManagers.map((am) => am.url), alertmanagersChoice });
|
||||
saveExternalAlertManagers({ alertmanagersChoice });
|
||||
};
|
||||
|
||||
const onChangeAlertmanagers = (alertmanagers: string[]) => {
|
||||
saveExternalAlertManagers({
|
||||
alertmanagers,
|
||||
alertmanagersChoice: alertmanagersChoice ?? AlertmanagerChoice.All,
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return theme.colors.success.main;
|
||||
|
||||
case 'pending':
|
||||
return theme.colors.warning.main;
|
||||
|
||||
default:
|
||||
return theme.colors.error.main;
|
||||
}
|
||||
};
|
||||
|
||||
const noAlertmanagers = externalAlertManagers?.length === 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>External Alertmanagers</h4>
|
||||
@@ -142,15 +57,10 @@ export const ExternalAlertmanagers = () => {
|
||||
For more information, refer to our documentation.
|
||||
</Alert>
|
||||
|
||||
<ExternalAlertmanagerDataSources
|
||||
alertmanagers={externalDsAlertManagers}
|
||||
inactive={alertmanagersChoice === AlertmanagerChoice.Internal}
|
||||
/>
|
||||
|
||||
<div className={styles.amChoice}>
|
||||
<Field
|
||||
label="Send alerts to"
|
||||
description="Configures how the Grafana alert rule evaluation engine Alertmanager handles your alerts. Internal (Grafana built-in Alertmanager), External (All Alertmanagers configured above), or both."
|
||||
description="Configures how the Grafana alert rule evaluation engine Alertmanager handles your alerts. Internal (Grafana built-in Alertmanager), External (All Alertmanagers configured below), or both."
|
||||
>
|
||||
<RadioButtonGroup
|
||||
options={alertmanagerChoices}
|
||||
@@ -160,95 +70,10 @@ export const ExternalAlertmanagers = () => {
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<h5>Alertmanagers by URL</h5>
|
||||
<Alert severity="warning" title="Deprecation Notice">
|
||||
The URL-based configuration of Alertmanagers is deprecated and will be removed in Grafana 9.2.0.
|
||||
<br />
|
||||
Use Alertmanager data sources to configure your external Alertmanagers.
|
||||
</Alert>
|
||||
|
||||
<div className={styles.muted}>
|
||||
You can have your Grafana managed alerts be delivered to one or many external Alertmanager(s) in addition to the
|
||||
internal Alertmanager by specifying their URLs below.
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
{!noAlertmanagers && (
|
||||
<Button type="button" onClick={onOpenModal}>
|
||||
Add Alertmanager
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{noAlertmanagers ? (
|
||||
<EmptyListCTA
|
||||
title="You have not added any external alertmanagers"
|
||||
onClick={onOpenModal}
|
||||
buttonTitle="Add Alertmanager"
|
||||
buttonIcon="bell-slash"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<table className={cx('filter-table form-inline filter-table--hover', styles.table)}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Url</th>
|
||||
<th>Status</th>
|
||||
<th style={{ width: '2%' }}>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{externalAlertManagers?.map((am, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<span className={styles.url}>{am.url}</span>
|
||||
{am.actualUrl ? (
|
||||
<Tooltip content={`Discovered ${am.actualUrl} from ${am.url}`} theme="info">
|
||||
<Icon name="info-circle" />
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</td>
|
||||
<td>
|
||||
<Icon name="heart" style={{ color: getStatusColor(am.status) }} title={am.status} />
|
||||
</td>
|
||||
<td>
|
||||
<HorizontalGroup>
|
||||
<Button variant="secondary" type="button" onClick={onEdit} aria-label="Edit alertmanager">
|
||||
<Icon name="pen" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
aria-label="Remove alertmanager"
|
||||
type="button"
|
||||
onClick={() => setDeleteModalState({ open: true, index })}
|
||||
>
|
||||
<Icon name="trash-alt" />
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)}
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={deleteModalState.open}
|
||||
title="Remove Alertmanager"
|
||||
body="Are you sure you want to remove this Alertmanager"
|
||||
confirmText="Remove"
|
||||
onConfirm={() => onDelete(deleteModalState.index)}
|
||||
onDismiss={() => setDeleteModalState({ open: false, index: 0 })}
|
||||
<ExternalAlertmanagerDataSources
|
||||
alertmanagers={externalDsAlertManagers}
|
||||
inactive={alertmanagersChoice === AlertmanagerChoice.Internal}
|
||||
/>
|
||||
{modalState.open && (
|
||||
<AddAlertManagerModal
|
||||
onClose={onCloseModal}
|
||||
alertmanagers={modalState.payload}
|
||||
onChangeAlertmanagerConfig={onChangeAlertmanagers}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -257,9 +82,6 @@ export const getStyles = (theme: GrafanaTheme2) => ({
|
||||
url: css`
|
||||
margin-right: ${theme.spacing(1)};
|
||||
`,
|
||||
muted: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
actions: css`
|
||||
margin-top: ${theme.spacing(2)};
|
||||
display: flex;
|
||||
|
||||
@@ -8,12 +8,12 @@ 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 { AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { mockDataSource, mockDataSourcesStore, mockStore } from '../mocks';
|
||||
import { mockAlertmanagerConfigResponse, mockAlertmanagersResponse } from '../mocks/alertmanagerApi';
|
||||
import { mockAlertmanagersResponse } from '../mocks/alertmanagerApi';
|
||||
|
||||
import { useExternalAmSelector, useExternalDataSourceAlertmanagers } from './useExternalAmSelector';
|
||||
import { useExternalDataSourceAlertmanagers } from './useExternalAmSelector';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
@@ -34,184 +34,6 @@ afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
describe('useExternalAmSelector', () => {
|
||||
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, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
url: 'some/url/to/am',
|
||||
status: 'pending',
|
||||
actualUrl: '',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
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, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
url: 'some/url/to/am',
|
||||
actualUrl: 'some/url/to/am/api/v2/alerts',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
url: 'some/url/to/am1',
|
||||
actualUrl: '',
|
||||
status: 'pending',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
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, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
url: 'some/url/to/am',
|
||||
actualUrl: 'some/url/to/am/api/v2/alerts',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
url: 'some/url/to/am1',
|
||||
actualUrl: 'some/url/to/am1/api/v2/alerts',
|
||||
status: 'active',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
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, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
url: 'some/url/to/am',
|
||||
actualUrl: 'some/url/to/am/api/v2/alerts',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
url: 'some/url/to/am1',
|
||||
actualUrl: '',
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
url: 'some/dropped/url',
|
||||
actualUrl: 'some/dropped/url/api/v2/alerts',
|
||||
status: 'dropped',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
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, waitFor } = renderHook(() => useExternalAmSelector(), { wrapper });
|
||||
await waitFor(() => result.current.length > 0);
|
||||
|
||||
const { current: alertmanagers } = result;
|
||||
|
||||
expect(alertmanagers.length).toBe(3);
|
||||
expect(alertmanagers).toEqual([
|
||||
{
|
||||
url: 'same/url/to/am',
|
||||
actualUrl: 'same/url/to/am/api/v2/alerts',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
url: 'same/url/to/am',
|
||||
actualUrl: 'same/url/to/am/api/v2/alerts',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
url: 'same/url/to/am',
|
||||
actualUrl: 'same/url/to/am/api/v2/alerts',
|
||||
status: 'active',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useExternalDataSourceAlertmanagers', () => {
|
||||
it('Should merge data sources information from config and api responses', async () => {
|
||||
// Arrange
|
||||
|
||||
@@ -7,54 +7,6 @@ import { useSelector } from 'app/types';
|
||||
import { alertmanagerApi } from '../api/alertmanagerApi';
|
||||
import { getAlertManagerDataSources } from '../utils/datasource';
|
||||
|
||||
const SUFFIX_REGEX = /\/api\/v[1|2]\/alerts/i;
|
||||
type AlertmanagerConfig = { url: string; status: string; actualUrl: string };
|
||||
|
||||
export function useExternalAmSelector(): AlertmanagerConfig[] | [] {
|
||||
const { useGetExternalAlertmanagersQuery, useGetExternalAlertmanagerConfigQuery } = alertmanagerApi;
|
||||
|
||||
const { currentData: discoveredAlertmanagers } = useGetExternalAlertmanagersQuery();
|
||||
const { currentData: alertmanagerConfig } = useGetExternalAlertmanagerConfigQuery();
|
||||
|
||||
if (!discoveredAlertmanagers || !alertmanagerConfig) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const enabledAlertmanagers: AlertmanagerConfig[] = [];
|
||||
const droppedAlertmanagers: AlertmanagerConfig[] = discoveredAlertmanagers.droppedAlertManagers.map((am) => ({
|
||||
url: am.url.replace(SUFFIX_REGEX, ''),
|
||||
status: 'dropped',
|
||||
actualUrl: am.url,
|
||||
}));
|
||||
|
||||
for (const url of alertmanagerConfig.alertmanagers) {
|
||||
if (discoveredAlertmanagers.activeAlertManagers.length === 0) {
|
||||
enabledAlertmanagers.push({
|
||||
url: url,
|
||||
status: 'pending',
|
||||
actualUrl: '',
|
||||
});
|
||||
} else {
|
||||
const matchingActiveAM = discoveredAlertmanagers.activeAlertManagers.find(
|
||||
(am) => am.url === `${url}/api/v2/alerts`
|
||||
);
|
||||
matchingActiveAM
|
||||
? enabledAlertmanagers.push({
|
||||
url: matchingActiveAM.url.replace(SUFFIX_REGEX, ''),
|
||||
status: 'active',
|
||||
actualUrl: matchingActiveAM.url,
|
||||
})
|
||||
: enabledAlertmanagers.push({
|
||||
url: url,
|
||||
status: 'pending',
|
||||
actualUrl: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return [...enabledAlertmanagers, ...droppedAlertmanagers];
|
||||
}
|
||||
|
||||
export interface ExternalDataSourceAM {
|
||||
dataSource: DataSourceInstanceSettings<AlertManagerDataSourceJsonData>;
|
||||
url?: string;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { rest } from 'msw';
|
||||
import { SetupServerApi } from 'msw/node';
|
||||
|
||||
import {
|
||||
ExternalAlertmanagerConfig,
|
||||
ExternalAlertmanagersResponse,
|
||||
} from '../../../../plugins/datasource/alertmanager/types';
|
||||
import { ExternalAlertmanagersResponse } from '../../../../plugins/datasource/alertmanager/types';
|
||||
import { AlertmanagersChoiceResponse } from '../api/alertmanagerApi';
|
||||
|
||||
export function mockAlertmanagerChoiceResponse(server: SetupServerApi, respose: AlertmanagersChoiceResponse) {
|
||||
@@ -14,7 +11,3 @@ export function mockAlertmanagerChoiceResponse(server: SetupServerApi, 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))));
|
||||
}
|
||||
|
||||
@@ -286,7 +286,6 @@ export enum AlertmanagerChoice {
|
||||
}
|
||||
|
||||
export interface ExternalAlertmanagerConfig {
|
||||
alertmanagers: string[];
|
||||
alertmanagersChoice: AlertmanagerChoice;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user