mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: don't copy original rule uid when cloning (#70645)
* Prevent using original rule uid when cloning * Use uid from url instead of a new param in the alert rule form * Add function to clone rule and tests for it * Fix lint & tests --------- Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>
This commit is contained in:
parent
85a8315920
commit
e03f61fe26
@ -10,12 +10,25 @@ import { config, setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
|
||||
import 'whatwg-fetch';
|
||||
import { RuleWithLocation } from 'app/types/unified-alerting';
|
||||
|
||||
import { RulerGrafanaRuleDTO } from '../../../types/unified-alerting-dto';
|
||||
import {
|
||||
RulerAlertingRuleDTO,
|
||||
RulerGrafanaRuleDTO,
|
||||
RulerRecordingRuleDTO,
|
||||
RulerRuleDTO,
|
||||
} from '../../../types/unified-alerting-dto';
|
||||
|
||||
import { CloneRuleEditor } from './CloneRuleEditor';
|
||||
import { cloneRuleDefinition, CloneRuleEditor } from './CloneRuleEditor';
|
||||
import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
|
||||
import { mockDataSource, MockDataSourceSrv, mockRulerAlertingRule, mockRulerGrafanaRule, mockStore } from './mocks';
|
||||
import {
|
||||
mockDataSource,
|
||||
MockDataSourceSrv,
|
||||
mockRulerAlertingRule,
|
||||
mockRulerGrafanaRule,
|
||||
mockRulerRuleGroup,
|
||||
mockStore,
|
||||
} from './mocks';
|
||||
import { mockAlertmanagerConfigResponse } from './mocks/alertmanagerApi';
|
||||
import { mockSearchApiResponse } from './mocks/grafanaApi';
|
||||
import { mockRulerRulesApiResponse, mockRulerRulesGroupApiResponse } from './mocks/rulerApi';
|
||||
@ -222,4 +235,125 @@ describe('CloneRuleEditor', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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<RulerGrafanaRuleDTO> = {
|
||||
ruleSourceName: 'my-prom-ds',
|
||||
namespace: 'namespace-one',
|
||||
group: mockRulerRuleGroup(),
|
||||
rule,
|
||||
};
|
||||
|
||||
const clonedRule: RuleWithLocation<RulerRuleDTO> = 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<RulerAlertingRuleDTO> = {
|
||||
ruleSourceName: 'my-prom-ds',
|
||||
namespace: 'namespace-one',
|
||||
group: mockRulerRuleGroup(),
|
||||
rule,
|
||||
};
|
||||
|
||||
const clonedRule: RuleWithLocation<RulerRuleDTO> = 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<RulerRecordingRuleDTO> = {
|
||||
ruleSourceName: 'my-prom-ds',
|
||||
namespace: 'namespace-one',
|
||||
group: mockRulerRuleGroup(),
|
||||
rule,
|
||||
};
|
||||
|
||||
const clonedRule: RuleWithLocation<RulerRuleDTO> = 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<RulerGrafanaRuleDTO> = {
|
||||
ruleSourceName: 'my-prom-ds',
|
||||
namespace: 'namespace-one',
|
||||
group: mockRulerRuleGroup(),
|
||||
rule,
|
||||
};
|
||||
|
||||
const clonedRule: RuleWithLocation<RulerRuleDTO> = 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<RulerGrafanaRuleDTO> = {
|
||||
ruleSourceName: 'my-prom-ds',
|
||||
namespace: 'namespace-one',
|
||||
group: mockRulerRuleGroup(),
|
||||
rule,
|
||||
};
|
||||
|
||||
const clonedRule: RuleWithLocation<RulerRuleDTO> = 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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import { locationService } from '@grafana/runtime/src';
|
||||
import { Alert, LoadingPlaceholder } from '@grafana/ui/src';
|
||||
|
||||
import { useDispatch } from '../../../types';
|
||||
import { RuleIdentifier } from '../../../types/unified-alerting';
|
||||
import { RuleIdentifier, RuleWithLocation } from '../../../types/unified-alerting';
|
||||
import { RulerRuleDTO } from '../../../types/unified-alerting-dto';
|
||||
|
||||
import { AlertRuleForm } from './components/rule-editor/AlertRuleForm';
|
||||
@ -30,18 +30,9 @@ export function CloneRuleEditor({ sourceRuleId }: { sourceRuleId: RuleIdentifier
|
||||
}
|
||||
|
||||
if (rule) {
|
||||
const ruleClone = cloneDeep(rule);
|
||||
changeRuleName(
|
||||
ruleClone.rule,
|
||||
generateCopiedName(getRuleName(ruleClone.rule), ruleClone.group.rules.map(getRuleName))
|
||||
);
|
||||
const ruleClone = cloneRuleDefinition(rule);
|
||||
const formPrefill = rulerRuleToFormValues(ruleClone);
|
||||
|
||||
// Provisioned alert rules have provisioned alert group which cannot be used in UI
|
||||
if (isGrafanaRulerRule(rule.rule) && Boolean(rule.rule.grafana_alert.provenance)) {
|
||||
formPrefill.group = '';
|
||||
}
|
||||
|
||||
return <AlertRuleForm prefill={formPrefill} />;
|
||||
}
|
||||
|
||||
@ -74,3 +65,22 @@ function changeRuleName(rule: RulerRuleDTO, newName: string) {
|
||||
rule.record = newName;
|
||||
}
|
||||
}
|
||||
|
||||
export function cloneRuleDefinition(rule: RuleWithLocation<RulerRuleDTO>) {
|
||||
const ruleClone = cloneDeep(rule);
|
||||
changeRuleName(
|
||||
ruleClone.rule,
|
||||
generateCopiedName(getRuleName(ruleClone.rule), ruleClone.group.rules.map(getRuleName))
|
||||
);
|
||||
|
||||
if (isGrafanaRulerRule(ruleClone.rule)) {
|
||||
ruleClone.rule.grafana_alert.uid = '';
|
||||
|
||||
// Provisioned alert rules have provisioned alert group which cannot be used in UI
|
||||
if (Boolean(ruleClone.rule.grafana_alert.provenance)) {
|
||||
ruleClone.group = { name: '', rules: ruleClone.group.rules };
|
||||
}
|
||||
}
|
||||
|
||||
return ruleClone;
|
||||
}
|
||||
|
@ -62,5 +62,5 @@ export function ExistingRuleEditor({ identifier, id }: ExistingRuleEditorProps)
|
||||
return <AlertWarning title="Cannot edit rule">Sorry! You do not have permission to edit this rule.</AlertWarning>;
|
||||
}
|
||||
|
||||
return <AlertRuleForm existing={result} id={id} />;
|
||||
return <AlertRuleForm existing={result} />;
|
||||
}
|
||||
|
@ -163,7 +163,6 @@ describe('RuleEditor grafana managed rules', () => {
|
||||
is_paused: false,
|
||||
no_data_state: 'NoData',
|
||||
title: 'my great new rule',
|
||||
uid: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -72,10 +72,9 @@ const AlertRuleNameInput = () => {
|
||||
type Props = {
|
||||
existing?: RuleWithLocation;
|
||||
prefill?: Partial<RuleFormValues>; // Existing implies we modify existing rule. Prefill only provides default form values
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export const AlertRuleForm = ({ existing, prefill, id }: Props) => {
|
||||
export const AlertRuleForm = ({ existing, prefill }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const dispatch = useDispatch();
|
||||
const notifyApp = useAppNotification();
|
||||
@ -83,8 +82,9 @@ export const AlertRuleForm = ({ existing, prefill, id }: Props) => {
|
||||
const [showEditYaml, setShowEditYaml] = useState(false);
|
||||
const [evaluateEvery, setEvaluateEvery] = useState(existing?.group.interval ?? MINUTE);
|
||||
|
||||
const routeParams = useParams<{ type: string }>();
|
||||
const routeParams = useParams<{ type: string; id: string }>();
|
||||
const ruleType = translateRouteParamToRuleType(routeParams.type);
|
||||
const uidFromParams = routeParams.id;
|
||||
|
||||
const returnTo: string = (queryParams['returnTo'] as string | undefined) ?? '/alerting/list';
|
||||
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
|
||||
@ -252,7 +252,7 @@ export const AlertRuleForm = ({ existing, prefill, id }: Props) => {
|
||||
<CloudEvaluationBehavior />
|
||||
)}
|
||||
<DetailsStep />
|
||||
<NotificationsStep alertUid={id} />
|
||||
<NotificationsStep alertUid={uidFromParams} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -11,7 +11,6 @@ export enum RuleFormType {
|
||||
export interface RuleFormValues {
|
||||
// common
|
||||
name: string;
|
||||
uid: string;
|
||||
type?: RuleFormType;
|
||||
dataSourceName: string | null;
|
||||
group: string;
|
||||
|
@ -15,7 +15,6 @@ exports[`formValuesToRulerGrafanaRuleDTO should correctly convert rule form valu
|
||||
"is_paused": false,
|
||||
"no_data_state": "NoData",
|
||||
"title": "",
|
||||
"uid": "",
|
||||
},
|
||||
"labels": {
|
||||
"": "",
|
||||
@ -54,7 +53,6 @@ exports[`formValuesToRulerGrafanaRuleDTO should not save both instant and range
|
||||
"is_paused": false,
|
||||
"no_data_state": "NoData",
|
||||
"title": "",
|
||||
"uid": "",
|
||||
},
|
||||
"labels": {
|
||||
"": "",
|
||||
|
@ -131,7 +131,6 @@ export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): Postabl
|
||||
return {
|
||||
grafana_alert: {
|
||||
title: name,
|
||||
uid: values.uid,
|
||||
condition,
|
||||
no_data_state: noDataState,
|
||||
exec_err_state: execErrState,
|
||||
@ -157,7 +156,6 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF
|
||||
return {
|
||||
...defaultFormValues,
|
||||
name: ga.title,
|
||||
uid: ga.uid,
|
||||
type: RuleFormType.grafana,
|
||||
group: group.name,
|
||||
evaluateEvery: group.interval || defaultFormValues.evaluateEvery,
|
||||
|
Loading…
Reference in New Issue
Block a user