Alerting: Allow editing of provisioned mutable Alertmanagers (#87385)

This commit is contained in:
Gilles De Mey 2024-05-06 14:29:28 +02:00 committed by GitHub
parent 8173bd89bf
commit 19e8dca09c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 68 additions and 24 deletions

View File

@ -24,8 +24,8 @@ const ui = {
builtInAlertmanagerSection: byText('Built-in Alertmanager'),
otherAlertmanagerSection: byText('Other Alertmanagers'),
alertmanagerCard: (name: string) => byTestId(`alertmanager-card-${name}`),
builtInAlertmanagerCard: byTestId('alertmanager-card-Grafana built-in'),
otherAlertmanagerCard: (name: string) => byTestId(`alertmanager-card-${name}`),
statusReceiving: byText(/receiving grafana-managed alerts/i),
statusNotReceiving: byText(/not receiving/i),
@ -34,7 +34,11 @@ const ui = {
editConfigurationButton: byRole('button', { name: /edit configuration/i }),
saveConfigurationButton: byRole('button', { name: /save/i }),
enableButton: byRole('button', { name: 'Enable' }),
disableButton: byRole('button', { name: 'Disable' }),
versionsTab: byRole('tab', { name: /versions/i }),
provisionedBadge: byText(/^Provisioned$/),
};
describe('Alerting settings', () => {
@ -59,7 +63,7 @@ describe('Alerting settings', () => {
// check external altermanagers
DataSourcesResponse.forEach((ds) => {
// 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(within(card).getByRole('link', { name: ds.name })).toBeInTheDocument();
@ -120,4 +124,26 @@ describe('Alerting settings', () => {
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();
});
});

View File

@ -76,6 +76,7 @@ export function AlertmanagerCard({
{/* we'll use the "tags" area to append buttons and actions */}
<Card.Tags>
<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">
{readOnly ? 'View configuration' : 'Edit configuration'}
</Button>

View File

@ -14,7 +14,7 @@ import { AlertmanagerProvider } from '../../state/AlertmanagerContext';
import AlertmanagerConfig from './AlertmanagerConfig';
import {
EXTERNAL_VANILLA_ALERTMANAGER_UID,
PROVISIONED_VANILLA_ALERTMANAGER_UID,
PROVISIONED_MIMIR_ALERTMANAGER_UID,
setupGrafanaManagedServer,
setupVanillaAlertmanagerServer,
} from './__mocks__/server';
@ -96,11 +96,11 @@ describe('vanilla Alertmanager', () => {
expect(ui.resetButton.query()).not.toBeInTheDocument();
});
it('should be read-only when provisioned Alertmanager', async () => {
renderConfiguration(PROVISIONED_VANILLA_ALERTMANAGER_UID, {});
it('should not be read-only when Mimir Alertmanager', async () => {
renderConfiguration(PROVISIONED_MIMIR_ALERTMANAGER_UID, {});
expect(ui.cancelButton.get()).toBeInTheDocument();
expect(ui.saveButton.query()).not.toBeInTheDocument();
expect(ui.resetButton.query()).not.toBeInTheDocument();
expect(ui.saveButton.get()).toBeInTheDocument();
expect(ui.resetButton.get()).toBeInTheDocument();
});
});

View File

@ -9,11 +9,7 @@ import { Alert, Button, CodeEditor, ConfirmModal, Stack, useStyles2 } from '@gra
import { reportFormErrors } from '../../Analytics';
import { useAlertmanagerConfig } from '../../hooks/useAlertmanagerConfig';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import {
GRAFANA_RULES_SOURCE_NAME,
isProvisionedDataSource,
isVanillaPrometheusAlertManagerDataSource,
} from '../../utils/datasource';
import { GRAFANA_RULES_SOURCE_NAME, isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource';
import { Spacer } from '../Spacer';
export interface FormValues {
@ -32,9 +28,9 @@ export default function AlertmanagerConfig({ alertmanagerName, onDismiss, onSave
const { loading: isSaving, error: savingError } = useUnifiedAlertingSelector((state) => state.saveAMConfig);
const [showResetConfirmation, setShowResetConfirmation] = useState(false);
// ⚠️ provisioned data sources should not prevent the configuration from being edited
const immutableDataSource = alertmanagerName ? isVanillaPrometheusAlertManagerDataSource(alertmanagerName) : false;
const provisionedDataSource = isProvisionedDataSource(alertmanagerName);
const readOnly = immutableDataSource || provisionedDataSource;
const readOnly = immutableDataSource;
const isGrafanaManagedAlertmanager = alertmanagerName === GRAFANA_RULES_SOURCE_NAME;
const styles = useStyles2(getStyles);

View File

@ -7,6 +7,7 @@ import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
import { ExternalAlertmanagerDataSourceWithStatus } from '../../hooks/useExternalAmSelector';
import {
isAlertmanagerDataSourceInterestedInAlerts,
isProvisionedDataSource,
isVanillaPrometheusAlertManagerDataSource,
} from '../../utils/datasource';
import { createUrl } from '../../utils/url';
@ -44,9 +45,9 @@ export const ExternalAlertmanagers = ({ onEditConfiguration }: Props) => {
const { status } = alertmanager;
const isReceiving = isReceivingGrafanaAlerts(alertmanager);
const isProvisioned = alertmanager.dataSourceSettings.readOnly === true;
const isReadOnly =
isProvisioned || isVanillaPrometheusAlertManagerDataSource(alertmanager.dataSourceSettings.name);
const isProvisioned = isProvisionedDataSource(alertmanager.dataSourceSettings);
const isReadOnly = isVanillaPrometheusAlertManagerDataSource(alertmanager.dataSourceSettings.name);
const detailHref = createUrl(DATASOURCES_ROUTES.Edit.replace(/:uid/gi, uid));
const handleEditConfiguration = () => onEditConfiguration(name);

View File

@ -19,6 +19,26 @@
},
"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,
"uid": "iETbvsT4z",

View File

@ -23,7 +23,7 @@ export { vanillaAlertmanagerConfig as VanillaAlertmanagerConfiguration };
export { history as alertmanagerConfigurationHistory };
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');
@ -40,9 +40,9 @@ const mockDataSources = {
implementation: AlertManagerImplementation.prometheus,
},
}),
[PROVISIONED_VANILLA_ALERTMANAGER_UID]: mockDataSource<AlertManagerDataSourceJsonData>({
uid: PROVISIONED_VANILLA_ALERTMANAGER_UID,
name: PROVISIONED_VANILLA_ALERTMANAGER_UID,
[PROVISIONED_MIMIR_ALERTMANAGER_UID]: mockDataSource<AlertManagerDataSourceJsonData>({
uid: PROVISIONED_MIMIR_ALERTMANAGER_UID,
name: PROVISIONED_MIMIR_ALERTMANAGER_UID,
type: DataSourceType.Alertmanager,
jsonData: {
// this is a mutable data source type but we're making it readOnly
@ -70,7 +70,7 @@ export function setupVanillaAlertmanagerServer(server: SetupServerApi) {
server.use(
createVanillaAlertmanagerConfigurationHandler(EXTERNAL_VANILLA_ALERTMANAGER_UID),
...createAlertmanagerConfigurationHandlers(PROVISIONED_VANILLA_ALERTMANAGER_UID)
...createAlertmanagerConfigurationHandlers(PROVISIONED_MIMIR_ALERTMANAGER_UID)
);
return server;

View File

@ -210,8 +210,8 @@ export function isVanillaPrometheusAlertManagerDataSource(name: string): boolean
);
}
export function isProvisionedDataSource(name: string): boolean {
return getAlertmanagerDataSourceByName(name)?.readOnly === true;
export function isProvisionedDataSource(dataSource: DataSourceSettings): boolean {
return dataSource.readOnly === true;
}
export function isGrafanaRulesSource(