Alerting: Use monaco editor for admin page (#69514)

pull/69720/head
Gilles De Mey 3 years ago committed by GitHub
parent a91de30f99
commit 8a8b5da1ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 33
      public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx
  2. 109
      public/app/features/alerting/unified/components/admin/ConfigEditor.tsx
  3. 20
      public/app/features/alerting/unified/components/admin/__snapshots__/AlertmanagerConfig.test.tsx.snap

@ -78,7 +78,7 @@ const ui = {
confirmButton: byRole('button', { name: /Yes, reset configuration/ }), confirmButton: byRole('button', { name: /Yes, reset configuration/ }),
resetButton: byRole('button', { name: /Reset configuration/ }), resetButton: byRole('button', { name: /Reset configuration/ }),
saveButton: byRole('button', { name: /Save/ }), saveButton: byRole('button', { name: /Save/ }),
configInput: byLabelText<HTMLTextAreaElement>(/Configuration/), configInput: byLabelText(/Code editor container/),
readOnlyConfig: byTestId('readonly-config'), readOnlyConfig: byTestId('readonly-config'),
}; };
@ -107,37 +107,29 @@ describe('Admin config', () => {
expect(ui.confirmButton.query()).not.toBeInTheDocument(); expect(ui.confirmButton.query()).not.toBeInTheDocument();
}); });
it('Edit and save alertmanager config', async () => { it('Editable alertmanager config', async () => {
let savedConfig: AlertManagerCortexConfig | undefined = undefined; let savedConfig: AlertManagerCortexConfig | undefined = undefined;
const defaultConfig = { const defaultConfig = {
template_files: { template_files: {},
foo: 'bar', alertmanager_config: {
route: {
receiver: 'old one',
},
}, },
alertmanager_config: {},
};
const newConfig = {
template_files: {
bar: 'baz',
},
alertmanager_config: {},
}; };
mocks.api.fetchConfig.mockImplementation(() => Promise.resolve(savedConfig ?? defaultConfig)); mocks.api.fetchConfig.mockImplementation(() => Promise.resolve(savedConfig ?? defaultConfig));
mocks.api.updateAlertManagerConfig.mockResolvedValue(); mocks.api.updateAlertManagerConfig.mockResolvedValue();
renderAdminPage(dataSources.alertManager.name); renderAdminPage(dataSources.alertManager.name);
const input = await ui.configInput.find();
expect(input.value).toEqual(JSON.stringify(defaultConfig, null, 2)); await ui.configInput.find();
await userEvent.clear(input);
// What is this regex replace doing? in userEvent v13, '{' and '[' are special characters.
// To get the literal character, you have to escape them by typing '{{' or '[['.
// See https://github.com/testing-library/user-event/issues/584.
await userEvent.type(input, JSON.stringify(newConfig, null, 2).replace(/[{[]/g, '$&$&'));
await userEvent.click(ui.saveButton.get()); await userEvent.click(ui.saveButton.get());
await waitFor(() => expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled()); await waitFor(() => expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled());
expect(mocks.api.updateAlertManagerConfig.mock.lastCall).toMatchSnapshot();
await waitFor(() => expect(mocks.api.fetchConfig).toHaveBeenCalledTimes(3)); await waitFor(() => expect(mocks.api.fetchConfig).toHaveBeenCalledTimes(3));
expect(input.value).toEqual(JSON.stringify(newConfig, null, 2));
}); });
it('Read-only when using Prometheus Alertmanager', async () => { it('Read-only when using Prometheus Alertmanager', async () => {
@ -148,7 +140,6 @@ describe('Admin config', () => {
renderAdminPage(dataSources.promAlertManager.name); renderAdminPage(dataSources.promAlertManager.name);
await ui.readOnlyConfig.find(); await ui.readOnlyConfig.find();
expect(ui.configInput.query()).not.toBeInTheDocument();
expect(ui.resetButton.query()).not.toBeInTheDocument(); expect(ui.resetButton.query()).not.toBeInTheDocument();
expect(ui.saveButton.query()).not.toBeInTheDocument(); expect(ui.saveButton.query()).not.toBeInTheDocument();

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Button, ConfirmModal, Field, Form, HorizontalGroup, TextArea } from '@grafana/ui'; import { Button, CodeEditor, ConfirmModal, Field, Form, HorizontalGroup } from '@grafana/ui';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
@ -31,33 +31,46 @@ export const ConfigEditor = ({
}: ConfigEditorProps) => { }: ConfigEditorProps) => {
return ( return (
<Form defaultValues={defaultValues} onSubmit={onSubmit} key={defaultValues.configJSON}> <Form defaultValues={defaultValues} onSubmit={onSubmit} key={defaultValues.configJSON}>
{({ register, errors }) => ( {({ errors, setValue, register }) => {
<> register('configJSON', {
{!readOnly && ( required: { value: true, message: 'Required' },
<> validate: (value: string) => {
<Field try {
disabled={loading} JSON.parse(value);
label="Configuration" return true;
invalid={!!errors.configJSON} } catch (e) {
error={errors.configJSON?.message} return e instanceof Error ? e.message : 'JSON is invalid';
> }
<TextArea },
{...register('configJSON', { });
required: { value: true, message: 'Required.' },
validate: (v) => {
try {
JSON.parse(v);
return true;
} catch (e) {
return e instanceof Error ? e.message : 'Invalid JSON.';
}
},
})}
id="configuration"
rows={25}
/>
</Field>
return (
<>
<Field
disabled={loading}
label="Configuration"
invalid={!!errors.configJSON}
error={errors.configJSON?.message}
data-testid={readOnly ? 'readonly-config' : 'config'}
>
<CodeEditor
language="json"
width="100%"
height={500}
showLineNumbers={true}
value={defaultValues.configJSON}
showMiniMap={false}
onSave={(value) => {
setValue('configJSON', value);
}}
onBlur={(value) => {
setValue('configJSON', value);
}}
readOnly={readOnly}
/>
</Field>
{!readOnly && (
<HorizontalGroup> <HorizontalGroup>
<Button type="submit" variant="primary" disabled={loading}> <Button type="submit" variant="primary" disabled={loading}>
Save configuration Save configuration
@ -68,29 +81,25 @@ export const ConfigEditor = ({
</Button> </Button>
)} )}
</HorizontalGroup> </HorizontalGroup>
</> )}
)}
{readOnly && ( {Boolean(showConfirmDeleteAMConfig) && onConfirmReset && onDismiss && (
<Field label="Configuration"> <ConfirmModal
<pre data-testid="readonly-config">{defaultValues.configJSON}</pre> isOpen={true}
</Field> title="Reset Alertmanager configuration"
)} body={`Are you sure you want to reset configuration ${
{Boolean(showConfirmDeleteAMConfig) && onConfirmReset && onDismiss && ( alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME
<ConfirmModal ? 'for the Grafana Alertmanager'
isOpen={true} : `for "${alertManagerSourceName}"`
title="Reset Alertmanager configuration" }? Contact points and notification policies will be reset to their defaults.`}
body={`Are you sure you want to reset configuration ${ confirmText="Yes, reset configuration"
alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME onConfirm={onConfirmReset}
? 'for the Grafana Alertmanager' onDismiss={onDismiss}
: `for "${alertManagerSourceName}"` />
}? Contact points and notification policies will be reset to their defaults.`} )}
confirmText="Yes, reset configuration" </>
onConfirm={onConfirmReset} );
onDismiss={onDismiss} }}
/>
)}
</>
)}
</Form> </Form>
); );
}; };

@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Admin config Editable alertmanager config 1`] = `
[
"CloudManager",
{
"alertmanager_config": {
"receivers": [
{
"name": "default ",
},
],
"route": {
"receiver": "old one",
},
},
"template_files": {},
},
]
`;
Loading…
Cancel
Save