Alerting: Use monaco editor for admin page (#69514)
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+20
@@ -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": {},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user