import * as React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { getWrapper, render, waitFor, waitForElementToBeRemoved, within } from 'test/test-utils'; import { byRole, byTestId, byText } from 'testing-library-selector'; import { MIMIR_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants'; import { RuleWithLocation } from 'app/types/unified-alerting'; import { AccessControlAction } from '../../../types'; import { RulerAlertingRuleDTO, RulerGrafanaRuleDTO, RulerRecordingRuleDTO, RulerRuleDTO, } from '../../../types/unified-alerting-dto'; import { CloneRuleEditor, cloneRuleDefinition } from './CloneRuleEditor'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; import { setupMswServer } from './mockApi'; import { grantUserPermissions, mockDataSource, mockRulerAlertingRule, mockRulerGrafanaRule, mockRulerRuleGroup, } from './mocks'; import { grafanaRulerRule } from './mocks/grafanaRulerApi'; import { mockRulerRulesApiResponse, mockRulerRulesGroupApiResponse } from './mocks/rulerApi'; import { AlertingQueryRunner } from './state/AlertingQueryRunner'; import { setupDataSources } from './testSetup/datasources'; import { RuleFormValues } from './types/rule-form'; import { Annotation } from './utils/constants'; import { getDefaultFormValues } from './utils/rule-form'; import { hashRulerRule } from './utils/rule-id'; jest.mock('./components/rule-editor/ExpressionEditor', () => ({ // eslint-disable-next-line react/display-name ExpressionEditor: ({ value, onChange }: ExpressionEditorProps) => ( onChange(e.target.value)} /> ), })); jest.spyOn(AlertingQueryRunner.prototype, 'run').mockImplementation(() => Promise.resolve()); const server = setupMswServer(); const ui = { inputs: { name: byRole('textbox', { name: 'name' }), expr: byTestId('expr'), folderContainer: byTestId('folder-picker'), namespace: byTestId('namespace-picker'), group: byTestId('group-picker'), annotationValue: (idx: number) => byTestId(`annotation-value-${idx}`), labelValue: (idx: number) => byTestId(`label-value-${idx}`), }, loadingIndicator: byText('Loading the rule...'), }; const Providers = getWrapper({ renderWithRouter: true }); function Wrapper({ children }: React.PropsWithChildren<{}>) { const formApi = useForm({ defaultValues: getDefaultFormValues() }); return ( {children} ); } describe('CloneRuleEditor', function () { grantUserPermissions([AccessControlAction.AlertingRuleExternalRead]); describe('Grafana-managed rules', function () { it('should populate form values from the existing alert rule', async function () { setupDataSources(); render( , { wrapper: Wrapper } ); await waitForElementToBeRemoved(ui.loadingIndicator.query()); await waitFor(() => { expect(within(ui.inputs.group.get()).queryByTestId('Spinner')).not.toBeInTheDocument(); }); await waitFor(() => { expect(ui.inputs.name.get()).toHaveValue(`${grafanaRulerRule.grafana_alert.title} (copy)`); }); expect(ui.inputs.folderContainer.get()).toHaveTextContent('Folder A'); expect(ui.inputs.group.get()).toHaveTextContent(grafanaRulerRule.grafana_alert.rule_group); expect( byRole('listitem', { name: 'severity: critical', }).get() ).toBeInTheDocument(); expect( byRole('listitem', { name: 'region: nasa', }).get() ).toBeInTheDocument(); expect(ui.inputs.annotationValue(0).get()).toHaveTextContent(grafanaRulerRule.annotations[Annotation.summary]); }); }); describe('Cloud rules', function () { it('should populate form values from the existing alert rule', async function () { const dsSettings = mockDataSource({ name: 'my-prom-ds', uid: MIMIR_DATASOURCE_UID, }); setupDataSources(dsSettings); const originRule = mockRulerAlertingRule({ for: '1m', alert: 'First Ruler Rule', expr: 'vector(1) > 0', labels: { severity: 'critical', region: 'nasa' }, annotations: { [Annotation.summary]: 'This is a very important alert rule' }, }); mockRulerRulesApiResponse(server, MIMIR_DATASOURCE_UID, { 'namespace-one': [{ name: 'group1', interval: '20s', rules: [originRule] }], }); mockRulerRulesGroupApiResponse(server, MIMIR_DATASOURCE_UID, 'namespace-one', 'group1', { name: 'group1', interval: '20s', rules: [originRule], }); render( , { wrapper: Wrapper } ); await waitForElementToBeRemoved(ui.loadingIndicator.query()); await waitFor(() => { expect(ui.inputs.name.get()).toHaveValue('First Ruler Rule (copy)'); }); expect(ui.inputs.expr.get()).toHaveValue('vector(1) > 0'); expect(ui.inputs.namespace.get()).toHaveTextContent('namespace-one'); expect(ui.inputs.group.get()).toHaveTextContent('group1'); expect( byRole('listitem', { name: 'severity: critical', }).get() ).toBeInTheDocument(); expect( byRole('listitem', { name: 'region: nasa', }).get() ).toBeInTheDocument(); expect(ui.inputs.annotationValue(0).get()).toHaveTextContent('This is a very important alert rule'); }); }); describe('cloneRuleDefinition', () => { it("Should change the cloned rule's name accordingly for Grafana rules", () => { const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( { for: '1m', labels: { severity: 'critical', region: 'nasa' }, annotations: { [Annotation.summary]: 'This is a very important alert rule' }, }, { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [] } ); const originalRule: RuleWithLocation = { ruleSourceName: 'my-prom-ds', namespace: 'namespace-one', group: mockRulerRuleGroup(), rule, }; const clonedRule: RuleWithLocation = cloneRuleDefinition(originalRule); const grafanaRule: RulerGrafanaRuleDTO = clonedRule.rule as RulerGrafanaRuleDTO; expect(originalRule.rule.grafana_alert.title).toEqual('First Grafana Rule'); expect(grafanaRule.grafana_alert.title).toEqual('First Grafana Rule (copy)'); }); it("Should change the cloned rule's name accordingly for Ruler rules", () => { const rule: RulerAlertingRuleDTO = mockRulerAlertingRule({ for: '1m', alert: 'First Ruler Rule', expr: 'vector(1) > 0', labels: { severity: 'critical', region: 'nasa' }, annotations: { [Annotation.summary]: 'This is a very important alert rule' }, }); const originalRule: RuleWithLocation = { ruleSourceName: 'my-prom-ds', namespace: 'namespace-one', group: mockRulerRuleGroup(), rule, }; const clonedRule: RuleWithLocation = cloneRuleDefinition(originalRule); const alertingRule: RulerAlertingRuleDTO = clonedRule.rule as RulerAlertingRuleDTO; expect(originalRule.rule.alert).toEqual('First Ruler Rule'); expect(alertingRule.alert).toEqual('First Ruler Rule (copy)'); }); it("Should change the cloned rule's name accordingly for Recording rules", () => { const rule: RulerRecordingRuleDTO = { record: 'instance:node_num_cpu:sum', expr: 'count without (cpu) (count without (mode) (node_cpu_seconds_total{job="integrations/node_exporter"}))', labels: { type: 'cpu' }, }; const originalRule: RuleWithLocation = { ruleSourceName: 'my-prom-ds', namespace: 'namespace-one', group: mockRulerRuleGroup(), rule, }; const clonedRule: RuleWithLocation = cloneRuleDefinition(originalRule); const recordingRule: RulerRecordingRuleDTO = clonedRule.rule as RulerRecordingRuleDTO; expect(originalRule.rule.record).toEqual('instance:node_num_cpu:sum'); expect(recordingRule.record).toEqual('instance:node_num_cpu:sum (copy)'); }); it('Should remove the group for provisioned Grafana rules', () => { const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( { for: '1m', labels: { severity: 'critical', region: 'nasa' }, annotations: { [Annotation.summary]: 'This is a very important alert rule' }, }, { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [], provenance: 'foo' } ); const originalRule: RuleWithLocation = { ruleSourceName: 'my-prom-ds', namespace: 'namespace-one', group: mockRulerRuleGroup(), rule, }; const clonedRule: RuleWithLocation = cloneRuleDefinition(originalRule); expect(originalRule.group.name).toEqual('group1'); expect(clonedRule.group.name).toEqual(''); }); it('The cloned rule should not contain a UID property', () => { const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( { for: '1m', labels: { severity: 'critical', region: 'nasa' }, annotations: { [Annotation.summary]: 'This is a very important alert rule' }, }, { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [] } ); const originalRule: RuleWithLocation = { ruleSourceName: 'my-prom-ds', namespace: 'namespace-one', group: mockRulerRuleGroup(), rule, }; const clonedRule: RuleWithLocation = cloneRuleDefinition(originalRule); const grafanaRule: RulerGrafanaRuleDTO = clonedRule.rule as RulerGrafanaRuleDTO; expect(originalRule.rule.grafana_alert.uid).toEqual('grafana-rule-1'); expect(grafanaRule.grafana_alert.uid).toEqual(''); }); }); });