mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Allow editing of provisioned mutable Alertmanagers (#87385)
This commit is contained in:
parent
8173bd89bf
commit
19e8dca09c
@ -24,8 +24,8 @@ const ui = {
|
|||||||
builtInAlertmanagerSection: byText('Built-in Alertmanager'),
|
builtInAlertmanagerSection: byText('Built-in Alertmanager'),
|
||||||
otherAlertmanagerSection: byText('Other Alertmanagers'),
|
otherAlertmanagerSection: byText('Other Alertmanagers'),
|
||||||
|
|
||||||
|
alertmanagerCard: (name: string) => byTestId(`alertmanager-card-${name}`),
|
||||||
builtInAlertmanagerCard: byTestId('alertmanager-card-Grafana built-in'),
|
builtInAlertmanagerCard: byTestId('alertmanager-card-Grafana built-in'),
|
||||||
otherAlertmanagerCard: (name: string) => byTestId(`alertmanager-card-${name}`),
|
|
||||||
|
|
||||||
statusReceiving: byText(/receiving grafana-managed alerts/i),
|
statusReceiving: byText(/receiving grafana-managed alerts/i),
|
||||||
statusNotReceiving: byText(/not receiving/i),
|
statusNotReceiving: byText(/not receiving/i),
|
||||||
@ -34,7 +34,11 @@ const ui = {
|
|||||||
editConfigurationButton: byRole('button', { name: /edit configuration/i }),
|
editConfigurationButton: byRole('button', { name: /edit configuration/i }),
|
||||||
saveConfigurationButton: byRole('button', { name: /save/i }),
|
saveConfigurationButton: byRole('button', { name: /save/i }),
|
||||||
|
|
||||||
|
enableButton: byRole('button', { name: 'Enable' }),
|
||||||
|
disableButton: byRole('button', { name: 'Disable' }),
|
||||||
|
|
||||||
versionsTab: byRole('tab', { name: /versions/i }),
|
versionsTab: byRole('tab', { name: /versions/i }),
|
||||||
|
provisionedBadge: byText(/^Provisioned$/),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Alerting settings', () => {
|
describe('Alerting settings', () => {
|
||||||
@ -59,7 +63,7 @@ describe('Alerting settings', () => {
|
|||||||
// check external altermanagers
|
// check external altermanagers
|
||||||
DataSourcesResponse.forEach((ds) => {
|
DataSourcesResponse.forEach((ds) => {
|
||||||
// get the card for datasource
|
// get the card for datasource
|
||||||
const card = ui.otherAlertmanagerCard(ds.name).get();
|
const card = ui.alertmanagerCard(ds.name).get();
|
||||||
|
|
||||||
// expect link to data source, provisioned badge, type, and status
|
// expect link to data source, provisioned badge, type, and status
|
||||||
expect(within(card).getByRole('link', { name: ds.name })).toBeInTheDocument();
|
expect(within(card).getByRole('link', { name: ds.name })).toBeInTheDocument();
|
||||||
@ -120,4 +124,26 @@ describe('Alerting settings', () => {
|
|||||||
expect(screen.getByText(/last applied/i)).toBeInTheDocument();
|
expect(screen.getByText(/last applied/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should correctly render provisioned data sources', async () => {
|
||||||
|
render(<SettingsPage />);
|
||||||
|
|
||||||
|
// wait for loading to be done
|
||||||
|
await waitFor(() => expect(ui.builtInAlertmanagerSection.get()).toBeInTheDocument());
|
||||||
|
|
||||||
|
// provisioned alertmanager card
|
||||||
|
const provisionedCard = ui.alertmanagerCard('Provisioned Mimir-based Alertmanager').get();
|
||||||
|
expect(ui.provisionedBadge.get(provisionedCard)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// should still be editable
|
||||||
|
const editConfigButton = ui.editConfigurationButton.get(provisionedCard);
|
||||||
|
expect(editConfigButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
// enable / disable should not be avaiable when provisioned
|
||||||
|
const enableButton = ui.enableButton.query(provisionedCard);
|
||||||
|
const disableButton = ui.disableButton.query(provisionedCard);
|
||||||
|
|
||||||
|
expect(enableButton).not.toBeInTheDocument();
|
||||||
|
expect(disableButton).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -76,6 +76,7 @@ export function AlertmanagerCard({
|
|||||||
{/* we'll use the "tags" area to append buttons and actions */}
|
{/* we'll use the "tags" area to append buttons and actions */}
|
||||||
<Card.Tags>
|
<Card.Tags>
|
||||||
<Stack direction="row" gap={1}>
|
<Stack direction="row" gap={1}>
|
||||||
|
{/* ⚠️ provisioned Data sources cannot have their "enable" / "disable" actions but we should still allow editing of the configuration */}
|
||||||
<Button onClick={onEditConfiguration} icon={readOnly ? 'eye' : 'edit'} variant="secondary" fill="outline">
|
<Button onClick={onEditConfiguration} icon={readOnly ? 'eye' : 'edit'} variant="secondary" fill="outline">
|
||||||
{readOnly ? 'View configuration' : 'Edit configuration'}
|
{readOnly ? 'View configuration' : 'Edit configuration'}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -14,7 +14,7 @@ import { AlertmanagerProvider } from '../../state/AlertmanagerContext';
|
|||||||
import AlertmanagerConfig from './AlertmanagerConfig';
|
import AlertmanagerConfig from './AlertmanagerConfig';
|
||||||
import {
|
import {
|
||||||
EXTERNAL_VANILLA_ALERTMANAGER_UID,
|
EXTERNAL_VANILLA_ALERTMANAGER_UID,
|
||||||
PROVISIONED_VANILLA_ALERTMANAGER_UID,
|
PROVISIONED_MIMIR_ALERTMANAGER_UID,
|
||||||
setupGrafanaManagedServer,
|
setupGrafanaManagedServer,
|
||||||
setupVanillaAlertmanagerServer,
|
setupVanillaAlertmanagerServer,
|
||||||
} from './__mocks__/server';
|
} from './__mocks__/server';
|
||||||
@ -96,11 +96,11 @@ describe('vanilla Alertmanager', () => {
|
|||||||
expect(ui.resetButton.query()).not.toBeInTheDocument();
|
expect(ui.resetButton.query()).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be read-only when provisioned Alertmanager', async () => {
|
it('should not be read-only when Mimir Alertmanager', async () => {
|
||||||
renderConfiguration(PROVISIONED_VANILLA_ALERTMANAGER_UID, {});
|
renderConfiguration(PROVISIONED_MIMIR_ALERTMANAGER_UID, {});
|
||||||
|
|
||||||
expect(ui.cancelButton.get()).toBeInTheDocument();
|
expect(ui.cancelButton.get()).toBeInTheDocument();
|
||||||
expect(ui.saveButton.query()).not.toBeInTheDocument();
|
expect(ui.saveButton.get()).toBeInTheDocument();
|
||||||
expect(ui.resetButton.query()).not.toBeInTheDocument();
|
expect(ui.resetButton.get()).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -9,11 +9,7 @@ import { Alert, Button, CodeEditor, ConfirmModal, Stack, useStyles2 } from '@gra
|
|||||||
import { reportFormErrors } from '../../Analytics';
|
import { reportFormErrors } from '../../Analytics';
|
||||||
import { useAlertmanagerConfig } from '../../hooks/useAlertmanagerConfig';
|
import { useAlertmanagerConfig } from '../../hooks/useAlertmanagerConfig';
|
||||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||||
import {
|
import { GRAFANA_RULES_SOURCE_NAME, isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource';
|
||||||
GRAFANA_RULES_SOURCE_NAME,
|
|
||||||
isProvisionedDataSource,
|
|
||||||
isVanillaPrometheusAlertManagerDataSource,
|
|
||||||
} from '../../utils/datasource';
|
|
||||||
import { Spacer } from '../Spacer';
|
import { Spacer } from '../Spacer';
|
||||||
|
|
||||||
export interface FormValues {
|
export interface FormValues {
|
||||||
@ -32,9 +28,9 @@ export default function AlertmanagerConfig({ alertmanagerName, onDismiss, onSave
|
|||||||
const { loading: isSaving, error: savingError } = useUnifiedAlertingSelector((state) => state.saveAMConfig);
|
const { loading: isSaving, error: savingError } = useUnifiedAlertingSelector((state) => state.saveAMConfig);
|
||||||
const [showResetConfirmation, setShowResetConfirmation] = useState(false);
|
const [showResetConfirmation, setShowResetConfirmation] = useState(false);
|
||||||
|
|
||||||
|
// ⚠️ provisioned data sources should not prevent the configuration from being edited
|
||||||
const immutableDataSource = alertmanagerName ? isVanillaPrometheusAlertManagerDataSource(alertmanagerName) : false;
|
const immutableDataSource = alertmanagerName ? isVanillaPrometheusAlertManagerDataSource(alertmanagerName) : false;
|
||||||
const provisionedDataSource = isProvisionedDataSource(alertmanagerName);
|
const readOnly = immutableDataSource;
|
||||||
const readOnly = immutableDataSource || provisionedDataSource;
|
|
||||||
|
|
||||||
const isGrafanaManagedAlertmanager = alertmanagerName === GRAFANA_RULES_SOURCE_NAME;
|
const isGrafanaManagedAlertmanager = alertmanagerName === GRAFANA_RULES_SOURCE_NAME;
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
@ -7,6 +7,7 @@ import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
|
|||||||
import { ExternalAlertmanagerDataSourceWithStatus } from '../../hooks/useExternalAmSelector';
|
import { ExternalAlertmanagerDataSourceWithStatus } from '../../hooks/useExternalAmSelector';
|
||||||
import {
|
import {
|
||||||
isAlertmanagerDataSourceInterestedInAlerts,
|
isAlertmanagerDataSourceInterestedInAlerts,
|
||||||
|
isProvisionedDataSource,
|
||||||
isVanillaPrometheusAlertManagerDataSource,
|
isVanillaPrometheusAlertManagerDataSource,
|
||||||
} from '../../utils/datasource';
|
} from '../../utils/datasource';
|
||||||
import { createUrl } from '../../utils/url';
|
import { createUrl } from '../../utils/url';
|
||||||
@ -44,9 +45,9 @@ export const ExternalAlertmanagers = ({ onEditConfiguration }: Props) => {
|
|||||||
const { status } = alertmanager;
|
const { status } = alertmanager;
|
||||||
|
|
||||||
const isReceiving = isReceivingGrafanaAlerts(alertmanager);
|
const isReceiving = isReceivingGrafanaAlerts(alertmanager);
|
||||||
const isProvisioned = alertmanager.dataSourceSettings.readOnly === true;
|
const isProvisioned = isProvisionedDataSource(alertmanager.dataSourceSettings);
|
||||||
const isReadOnly =
|
const isReadOnly = isVanillaPrometheusAlertManagerDataSource(alertmanager.dataSourceSettings.name);
|
||||||
isProvisioned || isVanillaPrometheusAlertManagerDataSource(alertmanager.dataSourceSettings.name);
|
|
||||||
const detailHref = createUrl(DATASOURCES_ROUTES.Edit.replace(/:uid/gi, uid));
|
const detailHref = createUrl(DATASOURCES_ROUTES.Edit.replace(/:uid/gi, uid));
|
||||||
|
|
||||||
const handleEditConfiguration = () => onEditConfiguration(name);
|
const handleEditConfiguration = () => onEditConfiguration(name);
|
||||||
|
@ -19,6 +19,26 @@
|
|||||||
},
|
},
|
||||||
"readOnly": false
|
"readOnly": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": 183,
|
||||||
|
"uid": "xPVD2XISx",
|
||||||
|
"orgId": 1,
|
||||||
|
"name": "Provisioned Mimir-based Alertmanager",
|
||||||
|
"type": "alertmanager",
|
||||||
|
"typeName": "Alertmanager",
|
||||||
|
"typeLogoUrl": "public/app/plugins/datasource/prometheus/img/prometheus_logo.svg",
|
||||||
|
"access": "proxy",
|
||||||
|
"url": "http://foo.bar:9090/",
|
||||||
|
"user": "",
|
||||||
|
"database": "",
|
||||||
|
"basicAuth": false,
|
||||||
|
"isDefault": false,
|
||||||
|
"jsonData": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"implementation": "mimir"
|
||||||
|
},
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": 160,
|
"id": 160,
|
||||||
"uid": "iETbvsT4z",
|
"uid": "iETbvsT4z",
|
||||||
|
@ -23,7 +23,7 @@ export { vanillaAlertmanagerConfig as VanillaAlertmanagerConfiguration };
|
|||||||
export { history as alertmanagerConfigurationHistory };
|
export { history as alertmanagerConfigurationHistory };
|
||||||
|
|
||||||
export const EXTERNAL_VANILLA_ALERTMANAGER_UID = 'vanilla-alertmanager';
|
export const EXTERNAL_VANILLA_ALERTMANAGER_UID = 'vanilla-alertmanager';
|
||||||
export const PROVISIONED_VANILLA_ALERTMANAGER_UID = 'provisioned-alertmanager';
|
export const PROVISIONED_MIMIR_ALERTMANAGER_UID = 'provisioned-alertmanager';
|
||||||
|
|
||||||
jest.spyOn(config, 'getAllDataSources');
|
jest.spyOn(config, 'getAllDataSources');
|
||||||
|
|
||||||
@ -40,9 +40,9 @@ const mockDataSources = {
|
|||||||
implementation: AlertManagerImplementation.prometheus,
|
implementation: AlertManagerImplementation.prometheus,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[PROVISIONED_VANILLA_ALERTMANAGER_UID]: mockDataSource<AlertManagerDataSourceJsonData>({
|
[PROVISIONED_MIMIR_ALERTMANAGER_UID]: mockDataSource<AlertManagerDataSourceJsonData>({
|
||||||
uid: PROVISIONED_VANILLA_ALERTMANAGER_UID,
|
uid: PROVISIONED_MIMIR_ALERTMANAGER_UID,
|
||||||
name: PROVISIONED_VANILLA_ALERTMANAGER_UID,
|
name: PROVISIONED_MIMIR_ALERTMANAGER_UID,
|
||||||
type: DataSourceType.Alertmanager,
|
type: DataSourceType.Alertmanager,
|
||||||
jsonData: {
|
jsonData: {
|
||||||
// this is a mutable data source type but we're making it readOnly
|
// this is a mutable data source type but we're making it readOnly
|
||||||
@ -70,7 +70,7 @@ export function setupVanillaAlertmanagerServer(server: SetupServerApi) {
|
|||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
createVanillaAlertmanagerConfigurationHandler(EXTERNAL_VANILLA_ALERTMANAGER_UID),
|
createVanillaAlertmanagerConfigurationHandler(EXTERNAL_VANILLA_ALERTMANAGER_UID),
|
||||||
...createAlertmanagerConfigurationHandlers(PROVISIONED_VANILLA_ALERTMANAGER_UID)
|
...createAlertmanagerConfigurationHandlers(PROVISIONED_MIMIR_ALERTMANAGER_UID)
|
||||||
);
|
);
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
|
@ -210,8 +210,8 @@ export function isVanillaPrometheusAlertManagerDataSource(name: string): boolean
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isProvisionedDataSource(name: string): boolean {
|
export function isProvisionedDataSource(dataSource: DataSourceSettings): boolean {
|
||||||
return getAlertmanagerDataSourceByName(name)?.readOnly === true;
|
return dataSource.readOnly === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isGrafanaRulesSource(
|
export function isGrafanaRulesSource(
|
||||||
|
Loading…
Reference in New Issue
Block a user