Alerting: Fix deleting rules when silencing/resuming rule from a panel alert tab (#87710)

* Fix deleting rules when silencing/resuming rule from a panel alert tab

* fix test and return early when no group target is found

* address pr review comment
This commit is contained in:
Sonia Aguilar 2024-05-14 10:01:08 +02:00 committed by GitHub
parent 194039c429
commit 6b1a662f6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 44 additions and 10 deletions

View File

@ -36,21 +36,23 @@ import * as analytics from './Analytics';
import RuleList from './RuleList'; import RuleList from './RuleList';
import { discoverFeatures } from './api/buildInfo'; import { discoverFeatures } from './api/buildInfo';
import { fetchRules } from './api/prometheus'; import { fetchRules } from './api/prometheus';
import * as apiRuler from './api/ruler';
import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler'; import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
import { import {
MockDataSourceSrv, MockDataSourceSrv,
getPotentiallyPausedRulerRules,
grantUserPermissions, grantUserPermissions,
mockDataSource, mockDataSource,
mockFolder,
mockPromAlert, mockPromAlert,
mockPromAlertingRule, mockPromAlertingRule,
mockPromRecordingRule, mockPromRecordingRule,
mockPromRuleGroup, mockPromRuleGroup,
mockPromRuleNamespace, mockPromRuleNamespace,
mockRulerGrafanaRule,
pausedPromRules, pausedPromRules,
getPotentiallyPausedRulerRules,
somePromRules, somePromRules,
someRulerRules, someRulerRules,
mockFolder,
} from './mocks'; } from './mocks';
import * as config from './utils/config'; import * as config from './utils/config';
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
@ -79,6 +81,7 @@ jest.mock('app/core/core', () => ({
jest.spyOn(analytics, 'logInfo'); jest.spyOn(analytics, 'logInfo');
jest.spyOn(config, 'getAllDataSources'); jest.spyOn(config, 'getAllDataSources');
jest.spyOn(actions, 'rulesInSameGroupHaveInvalidFor').mockReturnValue([]); jest.spyOn(actions, 'rulesInSameGroupHaveInvalidFor').mockReturnValue([]);
jest.spyOn(apiRuler, 'rulerUrlBuilder');
const mocks = { const mocks = {
getAllDataSourcesMock: jest.mocked(config.getAllDataSources), getAllDataSourcesMock: jest.mocked(config.getAllDataSources),
@ -93,6 +96,7 @@ const mocks = {
deleteGroup: jest.mocked(deleteRulerRulesGroup), deleteGroup: jest.mocked(deleteRulerRulesGroup),
deleteNamespace: jest.mocked(deleteNamespace), deleteNamespace: jest.mocked(deleteNamespace),
setRulerRuleGroup: jest.mocked(setRulerRuleGroup), setRulerRuleGroup: jest.mocked(setRulerRuleGroup),
rulerBuilderMock: jest.mocked(apiRuler.rulerUrlBuilder),
}, },
}; };
@ -187,6 +191,11 @@ const configureMockServer = () => {
message: 'rule group updated successfully', message: 'rule group updated successfully',
updated: ['foo', 'bar', 'baz'], updated: ['foo', 'bar', 'baz'],
}); });
mockAlertRuleApi(server).rulerRuleGroup(GRAFANA_RULES_SOURCE_NAME, 'NAMESPACE_UID', 'groupPaused', {
name: 'group-1',
interval: '1m',
rules: [mockRulerGrafanaRule()],
});
}; };
beforeAll(() => { beforeAll(() => {
@ -634,6 +643,13 @@ describe('RuleList', () => {
mocks.api.fetchRules.mockImplementation((sourceName) => mocks.api.fetchRules.mockImplementation((sourceName) =>
Promise.resolve(sourceName === 'grafana' ? pausedPromRules('grafana') : []) Promise.resolve(sourceName === 'grafana' ? pausedPromRules('grafana') : [])
); );
mocks.api.rulerBuilderMock.mockReturnValue({
rules: () => ({ path: `api/ruler/${GRAFANA_RULES_SOURCE_NAME}/api/v1/rules` }),
namespace: () => ({ path: 'ruler' }),
namespaceGroup: () => ({
path: `api/ruler/${GRAFANA_RULES_SOURCE_NAME}/api/v1/rules/NAMESPACE_UID/groupPaused`,
}),
});
}); });
test('resuming paused alert rule', async () => { test('resuming paused alert rule', async () => {

View File

@ -2,10 +2,13 @@ import { produce } from 'immer';
import React from 'react'; import React from 'react';
import { Menu } from '@grafana/ui'; import { Menu } from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi'; import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi';
import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from 'app/features/alerting/unified/utils/rules'; import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from 'app/features/alerting/unified/utils/rules';
import { CombinedRule } from 'app/types/unified-alerting'; import { CombinedRule } from 'app/types/unified-alerting';
import { grafanaRulerConfig } from '../hooks/useCombinedRule';
interface Props { interface Props {
rule: CombinedRule; rule: CombinedRule;
/** /**
@ -19,7 +22,11 @@ interface Props {
* and triggering API call to do so * and triggering API call to do so
*/ */
const MenuItemPauseRule = ({ rule, onPauseChange }: Props) => { const MenuItemPauseRule = ({ rule, onPauseChange }: Props) => {
const { group } = rule; // we need to fetch the group again, as maybe the group has been filtered
const [getGroup] = alertRuleApi.endpoints.rulerRuleGroup.useLazyQuery();
const notifyApp = useAppNotification();
// Add any dependencies here
const [updateRule] = alertRuleApi.endpoints.updateRule.useMutation(); const [updateRule] = alertRuleApi.endpoints.updateRule.useMutation();
const isPaused = isGrafanaRulerRule(rule.rulerRule) && isGrafanaRulerRulePaused(rule.rulerRule); const isPaused = isGrafanaRulerRule(rule.rulerRule) && isGrafanaRulerRulePaused(rule.rulerRule);
const icon = isPaused ? 'play' : 'pause'; const icon = isPaused ? 'play' : 'pause';
@ -33,21 +40,32 @@ const MenuItemPauseRule = ({ rule, onPauseChange }: Props) => {
return; return;
} }
const ruleUid = rule.rulerRule.grafana_alert.uid; const ruleUid = rule.rulerRule.grafana_alert.uid;
const targetGroup = await getGroup({
rulerConfig: grafanaRulerConfig,
namespace: rule.namespace.uid || rule.rulerRule.grafana_alert.namespace_uid,
group: rule.group.name,
}).unwrap();
// Parse the rules into correct format for API if (!targetGroup) {
const modifiedRules = group.rules.map((groupRule) => { notifyApp.error(
if (!(isGrafanaRulerRule(groupRule.rulerRule) && groupRule.rulerRule.grafana_alert.uid === ruleUid)) { `Failed to ${newIsPaused ? 'pause' : 'resume'} the rule. Could not get the target group to update the rule.`
return groupRule.rulerRule!; );
return;
} }
return produce(groupRule.rulerRule, (updatedGroupRule) => { // Parse the rules into correct format for API
const modifiedRules = targetGroup.rules.map((groupRule) => {
if (!(isGrafanaRulerRule(groupRule) && groupRule.grafana_alert.uid === ruleUid)) {
return groupRule;
}
return produce(groupRule, (updatedGroupRule) => {
updatedGroupRule.grafana_alert.is_paused = newIsPaused; updatedGroupRule.grafana_alert.is_paused = newIsPaused;
}); });
}); });
const payload = { const payload = {
interval: group.interval!, interval: targetGroup.interval!,
name: group.name, name: targetGroup.name,
rules: modifiedRules, rules: modifiedRules,
}; };