diff --git a/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx b/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx index 6b9ef72b8be..489aa792332 100644 --- a/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx +++ b/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx @@ -7,8 +7,8 @@ import { } from 'app/features/alerting/unified/utils/rules'; import { CombinedRule } from 'app/types/unified-alerting'; +import { usePauseRuleInGroup } from '../hooks/ruleGroup/usePauseAlertRule'; import { isLoading } from '../hooks/useAsync'; -import { usePauseRuleInGroup } from '../hooks/useProduceNewRuleGroup'; import { stringifyErrorLike } from '../utils/misc'; interface Props { diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index ccaf73da0a8..c587a9d87b2 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -27,7 +27,7 @@ import { trackAlertRuleFormCancelled, trackAlertRuleFormSaved, } from '../../../Analytics'; -import { useDeleteRuleFromGroup } from '../../../hooks/useProduceNewRuleGroup'; +import { useDeleteRuleFromGroup } from '../../../hooks/ruleGroup/useDeleteRuleFromGroup'; import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; import { saveRuleFormAction } from '../../../state/actions'; import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; diff --git a/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx b/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx index 75e37e95b7f..fcd813f71c6 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx @@ -5,7 +5,7 @@ import { ConfirmModal } from '@grafana/ui'; import { dispatch } from 'app/store/store'; import { CombinedRule } from 'app/types/unified-alerting'; -import { useDeleteRuleFromGroup } from '../../hooks/useProduceNewRuleGroup'; +import { useDeleteRuleFromGroup } from '../../hooks/ruleGroup/useDeleteRuleFromGroup'; import { fetchPromAndRulerRulesAction } from '../../state/actions'; import { getRuleGroupLocationFromCombinedRule } from '../../utils/rules'; diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx index 59caf5d775d..93df670343b 100644 --- a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx +++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx @@ -10,12 +10,12 @@ import { dispatch } from 'app/store/store'; import { CombinedRuleGroup, CombinedRuleNamespace, RuleGroupIdentifier } from 'app/types/unified-alerting'; import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; -import { anyOfRequestState } from '../../hooks/useAsync'; import { - useMoveRuleGroup, - useRenameRuleGroup, useUpdateRuleGroupConfiguration, -} from '../../hooks/useProduceNewRuleGroup'; + useRenameRuleGroup, + useMoveRuleGroup, +} from '../../hooks/ruleGroup/useUpdateRuleGroup'; +import { anyOfRequestState } from '../../hooks/useAsync'; import { fetchRulerRulesAction, rulesInSameGroupHaveInvalidFor } from '../../state/actions'; import { checkEvaluationIntervalGlobalLimit } from '../../utils/config'; import { getRulesSourceName, GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useDeleteRuleFromGroup.test.tsx.snap b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useDeleteRuleFromGroup.test.tsx.snap new file mode 100644 index 00000000000..e354c61a29e --- /dev/null +++ b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useDeleteRuleFromGroup.test.tsx.snap @@ -0,0 +1,156 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`delete rule should be able to delete a Data source managed rule 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/mockCombinedNamespace/mockCombinedRuleGroup?subtype=cortex", + }, + { + "body": { + "name": "group-1", + "rules": [ + { + "alert": "r1", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "foo": "bar", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/mockCombinedNamespace?subtype=cortex", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/mockCombinedNamespace/mockCombinedRuleGroup?subtype=cortex", + }, +] +`; + +exports[`delete rule should be able to delete a Grafana managed rule 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/NAMESPACE_UID/mockCombinedRuleGroup?subtype=cortex", + }, + { + "body": { + "name": "group-1", + "rules": [ + { + "annotations": {}, + "for": "", + "grafana_alert": { + "condition": "", + "data": [], + "exec_err_state": "Error", + "namespace_uid": "NAMESPACE_UID", + "no_data_state": "NoData", + "rule_group": "my-group", + "title": "my rule", + "uid": "r1", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/NAMESPACE_UID?subtype=cortex", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/NAMESPACE_UID/mockCombinedRuleGroup?subtype=cortex", + }, +] +`; + +exports[`delete rule should delete the entire group if no more rules are left 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/mockCombinedRuleGroup?subtype=cortex", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "DELETE", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/mockCombinedRuleGroup?subtype=cortex", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/mockCombinedRuleGroup?subtype=cortex", + }, +] +`; diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/usePauseAlertRule.test.tsx.snap b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/usePauseAlertRule.test.tsx.snap new file mode 100644 index 00000000000..89ee35e898a --- /dev/null +++ b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/usePauseAlertRule.test.tsx.snap @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`pause rule should be able to pause a rule 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/grafana-group-1?subtype=cortex", + }, + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": true, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/grafana-group-1?subtype=cortex", + }, +] +`; diff --git a/public/app/features/alerting/unified/hooks/__snapshots__/useProduceNewRuleGroup.test.tsx.snap b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleGroup.test.tsx.snap similarity index 58% rename from public/app/features/alerting/unified/hooks/__snapshots__/useProduceNewRuleGroup.test.tsx.snap rename to public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleGroup.test.tsx.snap index 4cd3f9c52df..62c6e987364 100644 --- a/public/app/features/alerting/unified/hooks/__snapshots__/useProduceNewRuleGroup.test.tsx.snap +++ b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleGroup.test.tsx.snap @@ -1,247 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`delete rule should be able to delete a Data source managed rule 1`] = ` -[ - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "GET", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/mockCombinedNamespace/mockCombinedRuleGroup?subtype=cortex", - }, - { - "body": { - "name": "group-1", - "rules": [ - { - "alert": "r1", - "annotations": { - "summary": "test alert", - }, - "expr": "up = 1", - "labels": { - "foo": "bar", - }, - }, - ], - }, - "headers": [ - [ - "content-type", - "application/json", - ], - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "POST", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/mockCombinedNamespace?subtype=cortex", - }, - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "GET", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/mockCombinedNamespace/mockCombinedRuleGroup?subtype=cortex", - }, -] -`; - -exports[`delete rule should be able to delete a Grafana managed rule 1`] = ` -[ - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "GET", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/NAMESPACE_UID/mockCombinedRuleGroup?subtype=cortex", - }, - { - "body": { - "name": "group-1", - "rules": [ - { - "annotations": {}, - "for": "", - "grafana_alert": { - "condition": "", - "data": [], - "exec_err_state": "Error", - "namespace_uid": "NAMESPACE_UID", - "no_data_state": "NoData", - "rule_group": "my-group", - "title": "my rule", - "uid": "r1", - }, - "labels": {}, - }, - ], - }, - "headers": [ - [ - "content-type", - "application/json", - ], - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "POST", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/NAMESPACE_UID?subtype=cortex", - }, - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "GET", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/NAMESPACE_UID/mockCombinedRuleGroup?subtype=cortex", - }, -] -`; - -exports[`delete rule should delete the entire group if no more rules are left 1`] = ` -[ - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "GET", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/mockCombinedRuleGroup?subtype=cortex", - }, - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "DELETE", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/mockCombinedRuleGroup?subtype=cortex", - }, - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "GET", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/mockCombinedRuleGroup?subtype=cortex", - }, -] -`; - -exports[`pause rule should be able to pause a rule 1`] = ` -[ - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "GET", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/grafana-group-1?subtype=cortex", - }, - { - "body": { - "interval": "1m", - "name": "grafana-group-1", - "rules": [ - { - "annotations": { - "summary": "Test alert", - }, - "for": "5m", - "grafana_alert": { - "condition": "A", - "data": [ - { - "datasourceUid": "datasource-uid", - "model": { - "datasource": { - "type": "prometheus", - "uid": "datasource-uid", - }, - "expression": "vector(1)", - "queryType": "alerting", - "refId": "A", - }, - "queryType": "alerting", - "refId": "A", - "relativeTimeRange": { - "from": 1000, - "to": 2000, - }, - }, - ], - "exec_err_state": "Error", - "is_paused": true, - "namespace_uid": "uuid020c61ef", - "no_data_state": "NoData", - "rule_group": "grafana-group-1", - "title": "Grafana-rule", - "uid": "4d7125fee983", - }, - "labels": { - "region": "nasa", - "severity": "critical", - }, - }, - ], - }, - "headers": [ - [ - "content-type", - "application/json", - ], - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "POST", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", - }, - { - "body": "", - "headers": [ - [ - "accept", - "application/json, text/plain, */*", - ], - ], - "method": "GET", - "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/grafana-group-1?subtype=cortex", - }, -] -`; - exports[`useUpdateRuleGroupConfiguration should be able to move a Data Source managed rule group 1`] = ` [ { diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/useDeleteRuleFromGroup.test.tsx b/public/app/features/alerting/unified/hooks/ruleGroup/useDeleteRuleFromGroup.test.tsx new file mode 100644 index 00000000000..7ede0ce5ce1 --- /dev/null +++ b/public/app/features/alerting/unified/hooks/ruleGroup/useDeleteRuleFromGroup.test.tsx @@ -0,0 +1,152 @@ +import userEvent from '@testing-library/user-event'; +import { HttpResponse } from 'msw'; +import { render } from 'test/test-utils'; +import { byRole, byText } from 'testing-library-selector'; + +import { setBackendSrv } from '@grafana/runtime'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { AccessControlAction } from 'app/types'; +import { CombinedRule } from 'app/types/unified-alerting'; + +import server, { setupMswServer } from '../../mockApi'; +import { + mockCombinedRule, + mockGrafanaRulerRule, + mockRulerRuleGroup, + mockRulerAlertingRule, + mockRulerRecordingRule, + grantUserPermissions, +} from '../../mocks'; +import { grafanaRulerRule } from '../../mocks/grafanaRulerApi'; +import { setUpdateRulerRuleNamespaceHandler, setRulerRuleGroupHandler } from '../../mocks/server/configure'; +import { captureRequests, serializeRequests } from '../../mocks/server/events'; +import { rulerRuleGroupHandler, updateRulerRuleNamespaceHandler } from '../../mocks/server/handlers/mimirRuler'; +import { getRuleGroupLocationFromCombinedRule } from '../../utils/rules'; +import { SerializeState } from '../useAsync'; + +import { useDeleteRuleFromGroup } from './useDeleteRuleFromGroup'; + +setupMswServer(); + +beforeAll(() => { + setBackendSrv(backendSrv); + grantUserPermissions([AccessControlAction.AlertingRuleExternalRead, AccessControlAction.AlertingRuleRead]); +}); + +describe('delete rule', () => { + it('should be able to delete a Grafana managed rule', async () => { + const rules = [ + mockCombinedRule({ + name: 'r1', + rulerRule: mockGrafanaRulerRule({ uid: 'r1' }), + }), + mockCombinedRule({ + name: 'r2', + rulerRule: mockGrafanaRulerRule({ uid: 'r2' }), + }), + ]; + const group = mockRulerRuleGroup({ + name: 'group-1', + rules: [rules[0].rulerRule!, rules[1].rulerRule!], + }); + + const getGroup = rulerRuleGroupHandler({ + delay: 0, + response: HttpResponse.json(group), + }); + + const updateNamespace = updateRulerRuleNamespaceHandler({ + response: new HttpResponse(undefined, { status: 200 }), + }); + + server.use(getGroup, updateNamespace); + + const capture = captureRequests(); + + render(); + + await userEvent.click(byRole('button').get()); + + expect(await byText(/success/i).find()).toBeInTheDocument(); + + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + + it('should be able to delete a Data source managed rule', async () => { + setUpdateRulerRuleNamespaceHandler({ + response: new HttpResponse(undefined, { status: 200 }), + }); + + const rules = [ + mockCombinedRule({ + name: 'r1', + rulerRule: mockRulerAlertingRule({ alert: 'r1', labels: { foo: 'bar' } }), + }), + mockCombinedRule({ + name: 'r2', + rulerRule: mockRulerRecordingRule({ record: 'r2', labels: { bar: 'baz' } }), + }), + ]; + + const group = mockRulerRuleGroup({ + name: 'group-1', + rules: [rules[0].rulerRule!, rules[1].rulerRule!], + }); + + setRulerRuleGroupHandler({ + delay: 0, + response: HttpResponse.json(group), + }); + + const capture = captureRequests(); + + render(); + + await userEvent.click(byRole('button').get()); + + expect(await byText(/success/i).find()).toBeInTheDocument(); + + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + + it('should delete the entire group if no more rules are left', async () => { + const capture = captureRequests(); + + const combined = mockCombinedRule({ + rulerRule: grafanaRulerRule, + }); + + render(); + await userEvent.click(byRole('button').get()); + + expect(await byText(/success/i).find()).toBeInTheDocument(); + + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); +}); + +type DeleteTestComponentProps = { + rule: CombinedRule; +}; +const DeleteTestComponent = ({ rule }: DeleteTestComponentProps) => { + const [requestState, deleteRuleFromGroup] = useDeleteRuleFromGroup(); + + // always handle your errors! + const ruleGroupID = getRuleGroupLocationFromCombinedRule(rule); + const onClick = () => { + deleteRuleFromGroup.execute(ruleGroupID, rule.rulerRule!); + }; + + return ( + <> +