Alerting: Add validation for path separators in the rule group edit modal (#90887)

Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com>
This commit is contained in:
Gilles De Mey 2024-07-29 11:59:15 +02:00 committed by GitHub
parent c694c97651
commit 746e2eeee6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 45 deletions

View File

@ -1,6 +1,4 @@
import { render } from '@testing-library/react';
import * as React from 'react';
import { Provider } from 'react-redux';
import { render, screen, userEvent } from 'test/test-utils';
import { byLabelText, byTestId, byText, byTitle } from 'testing-library-selector';
import { CombinedRuleNamespace } from 'app/types/unified-alerting';
@ -13,8 +11,6 @@ import {
mockPromRecordingRule,
mockRulerAlertingRule,
mockRulerRecordingRule,
mockRulerRuleGroup,
mockStore,
} from '../../mocks';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
@ -31,29 +27,8 @@ const ui = {
tableRows: byTestId('row'),
noRulesText: byText('This group does not contain alert rules.'),
};
mockRulerRuleGroup({
name: 'group1',
rules: [
mockRulerRecordingRule({
record: 'instance:node_num_cpu:sum',
expr: 'count without (cpu) (count without (mode) (node_cpu_seconds_total{job="integrations/node_exporter"}))',
labels: { type: 'cpu' },
}),
mockRulerAlertingRule({ alert: 'nonRecordingRule' }),
],
});
jest.mock('app/types', () => ({
...jest.requireActual('app/types'),
useDispatch: () => jest.fn(),
}));
function getProvidersWrapper() {
return function Wrapper({ children }: React.PropsWithChildren<{}>) {
const store = mockStore(() => null);
return <Provider store={store}>{children}</Provider>;
};
}
const noop = () => jest.fn();
describe('EditGroupModal', () => {
it('Should disable all inputs but interval when intervalEditOnly is set', async () => {
@ -65,9 +40,7 @@ describe('EditGroupModal', () => {
const group = namespace.groups[0];
render(<EditCloudGroupModal namespace={namespace} group={group} intervalEditOnly onClose={() => jest.fn()} />, {
wrapper: getProvidersWrapper(),
});
render(<EditCloudGroupModal namespace={namespace} group={group} intervalEditOnly onClose={noop} />);
expect(await ui.input.namespace.find()).toHaveAttribute('readonly');
expect(ui.input.group.get()).toHaveAttribute('readonly');
@ -107,9 +80,7 @@ describe('EditGroupModal component on cloud alert rules', () => {
const group = promNs.groups[0];
render(<EditCloudGroupModal namespace={promNs} group={group} onClose={() => jest.fn()} />, {
wrapper: getProvidersWrapper(),
});
render(<EditCloudGroupModal namespace={promNs} group={group} onClose={noop} />);
expect(await ui.input.namespace.find()).toHaveValue('prometheus-ns');
expect(ui.input.namespace.get()).not.toHaveAttribute('readonly');
@ -128,9 +99,7 @@ describe('EditGroupModal component on cloud alert rules', () => {
const group = promNs.groups[0];
render(<EditCloudGroupModal namespace={promNs} group={group} onClose={jest.fn()} />, {
wrapper: getProvidersWrapper(),
});
render(<EditCloudGroupModal namespace={promNs} group={group} onClose={noop} />);
expect(ui.table.query()).not.toBeInTheDocument();
expect(await ui.noRulesText.find()).toBeInTheDocument();
});
@ -163,10 +132,11 @@ describe('EditGroupModal component on grafana-managed alert rules', () => {
const grafanaGroup1 = grafanaNamespace.groups[0];
const renderWithGrafanaGroup = () =>
render(<EditCloudGroupModal namespace={grafanaNamespace} group={grafanaGroup1} onClose={noop} />);
it('Should show alert table', async () => {
render(<EditCloudGroupModal namespace={grafanaNamespace} group={grafanaGroup1} onClose={jest.fn()} />, {
wrapper: getProvidersWrapper(),
});
renderWithGrafanaGroup();
expect(await ui.input.namespace.find()).toHaveValue('namespace1');
expect(ui.input.group.get()).toHaveValue('grafanaGroup1');
@ -178,18 +148,22 @@ describe('EditGroupModal component on grafana-managed alert rules', () => {
});
it('Should have folder input in readonly mode', async () => {
render(<EditCloudGroupModal namespace={grafanaNamespace} group={grafanaGroup1} onClose={jest.fn()} />, {
wrapper: getProvidersWrapper(),
});
renderWithGrafanaGroup();
expect(await ui.input.namespace.find()).toHaveAttribute('readonly');
});
it('Should not display folder link if no folderUrl provided', async () => {
render(<EditCloudGroupModal namespace={grafanaNamespace} group={grafanaGroup1} onClose={jest.fn()} />, {
wrapper: getProvidersWrapper(),
});
renderWithGrafanaGroup();
expect(await ui.input.namespace.find()).toHaveValue('namespace1');
expect(ui.folderLink.query()).not.toBeInTheDocument();
});
it('does not allow slashes in the group name', async () => {
const user = userEvent.setup();
renderWithGrafanaGroup();
await user.type(await ui.input.group.find(), 'group/with/slashes');
await user.click(ui.input.interval.get());
expect(await screen.findByText(/cannot contain \"\/\"/i)).toBeInTheDocument();
});
});

View File

@ -28,6 +28,7 @@ import { EvaluationIntervalLimitExceeded } from '../InvalidIntervalWarning';
import { decodeGrafanaNamespace, encodeGrafanaNamespace } from '../expressions/util';
import { EvaluationGroupQuickPick } from '../rule-editor/EvaluationGroupQuickPick';
import { MIN_TIME_RANGE_STEP_S } from '../rule-editor/GrafanaEvaluationBehavior';
import { checkForPathSeparator } from '../rule-editor/util';
const ITEMS_PER_PAGE = 10;
@ -299,6 +300,11 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement {
readOnly={intervalEditOnly || isGrafanaManagedGroup}
{...register('namespaceName', {
required: 'Namespace name is required.',
validate: {
// for Grafana-managed we do not validate the name of the folder because we use the UID anyway
pathSeparator: (namespaceName) =>
isGrafanaManagedGroup ? true : checkForPathSeparator(namespaceName),
},
})}
/>
</Field>
@ -331,6 +337,9 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement {
readOnly={intervalEditOnly}
{...register('groupName', {
required: 'Evaluation group name is required.',
validate: {
pathSeparator: (namespace) => checkForPathSeparator(namespace),
},
})}
/>
</Field>