Alerting: Fix moving alert rules to a different folder (#44533)

* Prevent rule deletion when creation in a new folder fails

* Fix handling duplication of rules when moving between folders

* Add unit test for unique name generator

* Fix typo
This commit is contained in:
Konrad Lalik 2022-01-31 12:09:06 +01:00 committed by GitHub
parent aa22499b29
commit 88f3dff6d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 51 deletions

View File

@ -0,0 +1,31 @@
import { RulerRuleGroupDTO } from 'app/types/unified-alerting-dto';
import { getUniqueGroupName } from './actions';
describe('getUniqueGroupName', () => {
it('Should return the original value when there are no duplicates', () => {
// Arrange
const originalGroupName = 'file-system-out-of-space';
const existingGroups: RulerRuleGroupDTO[] = [];
// Act
const groupName = getUniqueGroupName(originalGroupName, existingGroups);
// Assert
expect(groupName).toBe(originalGroupName);
});
it('Should increment suffix counter until a unique name created', () => {
// Arrange
const originalGroupName = 'file-system-out-of-space';
const existingGroups: RulerRuleGroupDTO[] = [
{ name: 'file-system-out-of-space', rules: [] },
{ name: 'file-system-out-of-space-2', rules: [] },
];
// Act
const groupName = getUniqueGroupName(originalGroupName, existingGroups);
// Assert
expect(groupName).toBe('file-system-out-of-space-3');
});
});

View File

@ -343,60 +343,72 @@ async function saveLotexRule(values: RuleFormValues, existing?: RuleWithLocation
async function saveGrafanaRule(values: RuleFormValues, existing?: RuleWithLocation): Promise<RuleIdentifier> {
const { folder, evaluateEvery } = values;
const formRule = formValuesToRulerGrafanaRuleDTO(values);
if (folder) {
// updating an existing rule...
if (existing) {
// refetch it to be sure we have the latest
const freshExisting = await findEditableRule(ruleId.fromRuleWithLocation(existing));
if (!freshExisting) {
throw new Error('Rule not found.');
}
// if folder has changed, delete the old one
if (freshExisting.namespace !== folder.title) {
await deleteRule(freshExisting);
// if same folder, repost the group with updated rule
} else {
const uid = (freshExisting.rule as RulerGrafanaRuleDTO).grafana_alert.uid!;
formRule.grafana_alert.uid = uid;
await setRulerRuleGroup(GRAFANA_RULES_SOURCE_NAME, freshExisting.namespace, {
name: freshExisting.group.name,
interval: evaluateEvery,
rules: [formRule],
});
return { uid };
}
}
// if creating new rule or folder was changed, create rule in a new group
const existingNamespace = await fetchRulerRulesNamespace(GRAFANA_RULES_SOURCE_NAME, folder.title);
// set group name to rule name, but be super paranoid and check that this group does not already exist
let group = values.name;
let idx = 1;
while (!!existingNamespace.find((g) => g.name === group)) {
group = `${values.name}-${++idx}`;
}
const payload: PostableRulerRuleGroupDTO = {
name: group,
interval: evaluateEvery,
rules: [formRule],
};
await setRulerRuleGroup(GRAFANA_RULES_SOURCE_NAME, folder.title, payload);
// now refetch this group to get the uid, hah
const result = await fetchRulerRulesGroup(GRAFANA_RULES_SOURCE_NAME, folder.title, group);
const newUid = (result?.rules[0] as RulerGrafanaRuleDTO)?.grafana_alert?.uid;
if (newUid) {
return { uid: newUid };
} else {
throw new Error('Failed to fetch created rule.');
}
} else {
if (!folder) {
throw new Error('Folder must be specified');
}
// updating an existing rule...
if (existing) {
// refetch it to be sure we have the latest
const freshExisting = await findEditableRule(ruleId.fromRuleWithLocation(existing));
if (!freshExisting) {
throw new Error('Rule not found.');
}
// if same folder, repost the group with updated rule
if (freshExisting.namespace === folder.title) {
const uid = (freshExisting.rule as RulerGrafanaRuleDTO).grafana_alert.uid!;
formRule.grafana_alert.uid = uid;
await setRulerRuleGroup(GRAFANA_RULES_SOURCE_NAME, freshExisting.namespace, {
name: freshExisting.group.name,
interval: evaluateEvery,
rules: [formRule],
});
return { uid };
}
}
// if creating new rule or folder was changed, create rule in a new group
const targetFolderGroups = await fetchRulerRulesNamespace(GRAFANA_RULES_SOURCE_NAME, folder.title);
// set group name to rule name, but be super paranoid and check that this group does not already exist
const groupName = getUniqueGroupName(values.name, targetFolderGroups);
formRule.grafana_alert.title = groupName;
const payload: PostableRulerRuleGroupDTO = {
name: groupName,
interval: evaluateEvery,
rules: [formRule],
};
await setRulerRuleGroup(GRAFANA_RULES_SOURCE_NAME, folder.title, payload);
// now refetch this group to get the uid, hah
const result = await fetchRulerRulesGroup(GRAFANA_RULES_SOURCE_NAME, folder.title, groupName);
const newUid = (result?.rules[0] as RulerGrafanaRuleDTO)?.grafana_alert?.uid;
if (newUid) {
// if folder has changed, delete the old one
if (existing) {
const freshExisting = await findEditableRule(ruleId.fromRuleWithLocation(existing));
if (freshExisting && freshExisting.namespace !== folder.title) {
await deleteRule(freshExisting);
}
}
return { uid: newUid };
} else {
throw new Error('Failed to fetch created rule.');
}
}
export function getUniqueGroupName(currentGroupName: string, existingGroups: RulerRuleGroupDTO[]) {
let newGroupName = currentGroupName;
let idx = 1;
while (!!existingGroups.find((g) => g.name === newGroupName)) {
newGroupName = `${currentGroupName}-${++idx}`;
}
return newGroupName;
}
export const saveRuleFormAction = createAsyncThunk(