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. 31
      public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx
  2. 59
      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) => {
try {
JSON.parse(value);
return true;
} catch (e) {
return e instanceof Error ? e.message : 'JSON is invalid';
}
},
});
return (
<> <>
<Field <Field
disabled={loading} disabled={loading}
label="Configuration" label="Configuration"
invalid={!!errors.configJSON} invalid={!!errors.configJSON}
error={errors.configJSON?.message} error={errors.configJSON?.message}
data-testid={readOnly ? 'readonly-config' : 'config'}
> >
<TextArea <CodeEditor
{...register('configJSON', { language="json"
required: { value: true, message: 'Required.' }, width="100%"
validate: (v) => { height={500}
try { showLineNumbers={true}
JSON.parse(v); value={defaultValues.configJSON}
return true; showMiniMap={false}
} catch (e) { onSave={(value) => {
return e instanceof Error ? e.message : 'Invalid JSON.'; setValue('configJSON', value);
} }}
}, onBlur={(value) => {
})} setValue('configJSON', value);
id="configuration" }}
rows={25} readOnly={readOnly}
/> />
</Field> </Field>
{!readOnly && (
<HorizontalGroup> <HorizontalGroup>
<Button type="submit" variant="primary" disabled={loading}> <Button type="submit" variant="primary" disabled={loading}>
Save configuration Save configuration
@ -68,13 +81,8 @@ export const ConfigEditor = ({
</Button> </Button>
)} )}
</HorizontalGroup> </HorizontalGroup>
</>
)}
{readOnly && (
<Field label="Configuration">
<pre data-testid="readonly-config">{defaultValues.configJSON}</pre>
</Field>
)} )}
{Boolean(showConfirmDeleteAMConfig) && onConfirmReset && onDismiss && ( {Boolean(showConfirmDeleteAMConfig) && onConfirmReset && onDismiss && (
<ConfirmModal <ConfirmModal
isOpen={true} isOpen={true}
@ -90,7 +98,8 @@ export const ConfigEditor = ({
/> />
)} )}
</> </>
)} );
}}
</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