diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 85f00b549ff..26e1538146b 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -36,21 +36,23 @@ import * as analytics from './Analytics'; import RuleList from './RuleList'; import { discoverFeatures } from './api/buildInfo'; import { fetchRules } from './api/prometheus'; +import * as apiRuler from './api/ruler'; import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler'; import { MockDataSourceSrv, + getPotentiallyPausedRulerRules, grantUserPermissions, mockDataSource, + mockFolder, mockPromAlert, mockPromAlertingRule, mockPromRecordingRule, mockPromRuleGroup, mockPromRuleNamespace, + mockRulerGrafanaRule, pausedPromRules, - getPotentiallyPausedRulerRules, somePromRules, someRulerRules, - mockFolder, } from './mocks'; import * as config from './utils/config'; import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; @@ -79,6 +81,7 @@ jest.mock('app/core/core', () => ({ jest.spyOn(analytics, 'logInfo'); jest.spyOn(config, 'getAllDataSources'); jest.spyOn(actions, 'rulesInSameGroupHaveInvalidFor').mockReturnValue([]); +jest.spyOn(apiRuler, 'rulerUrlBuilder'); const mocks = { getAllDataSourcesMock: jest.mocked(config.getAllDataSources), @@ -93,6 +96,7 @@ const mocks = { deleteGroup: jest.mocked(deleteRulerRulesGroup), deleteNamespace: jest.mocked(deleteNamespace), setRulerRuleGroup: jest.mocked(setRulerRuleGroup), + rulerBuilderMock: jest.mocked(apiRuler.rulerUrlBuilder), }, }; @@ -187,6 +191,11 @@ const configureMockServer = () => { message: 'rule group updated successfully', updated: ['foo', 'bar', 'baz'], }); + mockAlertRuleApi(server).rulerRuleGroup(GRAFANA_RULES_SOURCE_NAME, 'NAMESPACE_UID', 'groupPaused', { + name: 'group-1', + interval: '1m', + rules: [mockRulerGrafanaRule()], + }); }; beforeAll(() => { @@ -634,6 +643,13 @@ describe('RuleList', () => { mocks.api.fetchRules.mockImplementation((sourceName) => 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 () => { diff --git a/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx b/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx index 06ad1985efb..6f312ef9f01 100644 --- a/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx +++ b/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx @@ -2,10 +2,13 @@ import { produce } from 'immer'; import React from 'react'; import { Menu } from '@grafana/ui'; +import { useAppNotification } from 'app/core/copy/appNotification'; import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi'; import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from 'app/features/alerting/unified/utils/rules'; import { CombinedRule } from 'app/types/unified-alerting'; +import { grafanaRulerConfig } from '../hooks/useCombinedRule'; + interface Props { rule: CombinedRule; /** @@ -19,7 +22,11 @@ interface Props { * and triggering API call to do so */ 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 isPaused = isGrafanaRulerRule(rule.rulerRule) && isGrafanaRulerRulePaused(rule.rulerRule); const icon = isPaused ? 'play' : 'pause'; @@ -33,21 +40,32 @@ const MenuItemPauseRule = ({ rule, onPauseChange }: Props) => { return; } 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(); + + if (!targetGroup) { + notifyApp.error( + `Failed to ${newIsPaused ? 'pause' : 'resume'} the rule. Could not get the target group to update the rule.` + ); + return; + } // Parse the rules into correct format for API - const modifiedRules = group.rules.map((groupRule) => { - if (!(isGrafanaRulerRule(groupRule.rulerRule) && groupRule.rulerRule.grafana_alert.uid === ruleUid)) { - return groupRule.rulerRule!; + const modifiedRules = targetGroup.rules.map((groupRule) => { + if (!(isGrafanaRulerRule(groupRule) && groupRule.grafana_alert.uid === ruleUid)) { + return groupRule; } - - return produce(groupRule.rulerRule, (updatedGroupRule) => { + return produce(groupRule, (updatedGroupRule) => { updatedGroupRule.grafana_alert.is_paused = newIsPaused; }); }); const payload = { - interval: group.interval!, - name: group.name, + interval: targetGroup.interval!, + name: targetGroup.name, rules: modifiedRules, };