From 8bbef70363b6690f93b64ec775c8e827ce013bb1 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Thu, 26 Jan 2023 16:11:29 +0100 Subject: [PATCH] Alerting: Fix recording rules being shown in the rules table on edit group modal (#62171) * Fix recording rules being shown in the rules table on edit group modal * Show no alerts message when there is not some non-recording rules and add tests * Move specific mock constants to the test file --- .../rules/EditRuleGroupModal.test.tsx | 151 ++++++++++++++++++ .../components/rules/EditRuleGroupModal.tsx | 38 ++--- public/app/features/alerting/unified/mocks.ts | 13 ++ 3 files changed, 179 insertions(+), 23 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx new file mode 100644 index 00000000000..f46fdb629a2 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx @@ -0,0 +1,151 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; +import { RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; + +import { + mockCombinedRule, + mockDataSource, + mockPromAlertingRule, + mockRulerAlertingRule, + mockRulerRecordingRule, + mockRulerRuleGroup, + mockStore, + someRulerRules, +} from '../../mocks'; +import { GRAFANA_DATASOURCE_NAME } from '../../utils/datasource'; + +import { CombinedGroupAndNameSpace, EditCloudGroupModal, ModalProps } from './EditRuleGroupModal'; + +const dsSettings = mockDataSource({ + name: 'Prometheus-1', + uid: 'Prometheus-1', +}); + +export const someCloudRulerRules: RulerRulesConfigDTO = { + namespace1: [ + mockRulerRuleGroup({ + name: 'group1', + rules: [ + mockRulerRecordingRule({ + record: 'instance:node_num_cpu:sum', + expr: 'count without (cpu) (count without (mode) (node_cpu_seconds_total{job="integrations/node_exporter"}))', + labels: { type: 'cpu' }, + }), + mockRulerAlertingRule({ alert: 'nonRecordingRule' }), + ], + }), + ], +}; + +export const onlyRecordingRulerRules: RulerRulesConfigDTO = { + namespace1: [ + mockRulerRuleGroup({ + name: 'group1', + rules: [ + mockRulerRecordingRule({ + 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 grafanaNamespace: CombinedRuleNamespace = { + name: 'namespace1', + rulesSource: dsSettings, + groups: [ + { + name: 'group1', + rules: [ + mockCombinedRule({ + namespace: { + groups: [], + name: 'namespace1', + rulesSource: mockDataSource(), + }, + promRule: mockPromAlertingRule(), + rulerRule: mockRulerAlertingRule(), + }), + ], + }, + ], +}; + +const group1: CombinedRuleGroup = { + name: 'group1', + rules: [ + mockCombinedRule({ + namespace: { + groups: [], + name: 'namespace1', + rulesSource: mockDataSource({ name: 'Prometheus-1' }), + }, + promRule: mockPromAlertingRule({ name: 'nonRecordingRule' }), + rulerRule: mockRulerAlertingRule({ alert: 'recordingRule' }), + }), + ], +}; + +const nameSpaceAndGroup: CombinedGroupAndNameSpace = { + namespace: grafanaNamespace, + group: group1, +}; +const defaultProps: ModalProps = { + nameSpaceAndGroup: nameSpaceAndGroup, + sourceName: 'Prometheus-1', + groupInterval: '1m', + onClose: jest.fn(), +}; + +jest.mock('app/types', () => ({ + ...jest.requireActual('app/types'), + useDispatch: () => jest.fn(), +})); + +function getProvidersWrapper(cloudRules?: RulerRulesConfigDTO) { + return function Wrapper({ children }: React.PropsWithChildren<{}>) { + const store = mockStore((store) => { + store.unifiedAlerting.rulerRules[GRAFANA_DATASOURCE_NAME] = { + loading: false, + dispatched: true, + result: someRulerRules, + }; + store.unifiedAlerting.rulerRules['Prometheus-1'] = { + loading: false, + dispatched: true, + result: cloudRules ?? someCloudRulerRules, + }; + }); + + return {children}; + }; +} + +describe('EditGroupModal component on cloud alert rules', () => { + it('Should show alert table in case of having some non-recording rules in the group', () => { + render(, { + wrapper: getProvidersWrapper(), + }); + expect(screen.getByText(/nonRecordingRule/i)).toBeInTheDocument(); + }); + it('Should not show alert table in case of not having some non-recording rules in the group', () => { + render(, { + wrapper: getProvidersWrapper(onlyRecordingRulerRules), + }); + expect(screen.queryByText(/nonRecordingRule/i)).not.toBeInTheDocument(); + expect(screen.getByText(/this group does not contain alert rules\./i)); + }); +}); +describe('EditGroupModal component on grafana-managed alert rules', () => { + it('Should show alert table', () => { + render(, { + wrapper: getProvidersWrapper(), + }); + expect(screen.getByText(/alert1/i)).toBeInTheDocument(); + }); +}); diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx index 3b02777b07a..d7fd81e8e40 100644 --- a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx +++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx @@ -4,19 +4,19 @@ import { FormProvider, RegisterOptions, useForm, useFormContext } from 'react-ho import { GrafanaTheme2 } from '@grafana/data'; import { Stack } from '@grafana/experimental'; -import { Modal, Button, Field, Input, useStyles2, Label, Badge } from '@grafana/ui'; +import { Badge, Button, Field, Input, Label, Modal, useStyles2 } from '@grafana/ui'; import { useAppNotification } from 'app/core/copy/appNotification'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { useDispatch } from 'app/types'; import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; -import { RulerRulesConfigDTO, RulerRuleGroupDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; +import { RulerRuleDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { rulesInSameGroupHaveInvalidFor, updateLotexNamespaceAndGroupAction } from '../../state/actions'; import { checkEvaluationIntervalGlobalLimit } from '../../utils/config'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { initialAsyncRequestState } from '../../utils/redux'; -import { isAlertingRulerRule, isGrafanaRulerRule } from '../../utils/rules'; +import { isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from '../../utils/rules'; import { parsePrometheusDuration } from '../../utils/time'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; import { InfoIcon } from '../InfoIcon'; @@ -122,24 +122,14 @@ export const safeParseDurationstr = (duration: string): number => { type AlertsWithForTableColumnProps = DynamicTableColumnProps; type AlertsWithForTableProps = DynamicTableItemProps; -export const RulesForGroupTable = ({ - rulerRules, - groupName, - folderName, -}: { - rulerRules: RulerRulesConfigDTO | null | undefined; - groupName: string; - folderName: string; -}) => { +export const RulesForGroupTable = ({ rulesWithoutRecordingRules }: { rulesWithoutRecordingRules: RulerRuleDTO[] }) => { const styles = useStyles2(getStyles); - const group = getGroupFromRuler(rulerRules, groupName, folderName); - const rules: RulerRuleDTO[] = group?.rules ?? []; const { watch } = useFormContext(); const currentInterval = watch('groupInterval'); const unknownCurrentInterval = !Boolean(currentInterval); - const rows: AlertsWithForTableProps[] = rules + const rows: AlertsWithForTableProps[] = rulesWithoutRecordingRules .slice() .map((rule: RulerRuleDTO, index) => ({ id: index, @@ -198,7 +188,7 @@ export const RulesForGroupTable = ({ ); }; -interface CombinedGroupAndNameSpace { +export interface CombinedGroupAndNameSpace { namespace: CombinedRuleNamespace; group: CombinedRuleGroup; } @@ -206,7 +196,7 @@ interface GroupAndNameSpaceNames { namespace: string; group: string; } -interface ModalProps { +export interface ModalProps { nameSpaceAndGroup: CombinedGroupAndNameSpace | GroupAndNameSpaceNames; sourceName: string; groupInterval: string; @@ -328,6 +318,11 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement { const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules); const groupfoldersForSource = rulerRuleRequests[sourceName]; + const groupWithRules = getGroupFromRuler(groupfoldersForSource?.result, groupName, nameSpaceName); + const rulesWithoutRecordingRules: RulerRuleDTO[] = + groupWithRules?.rules.filter((rule: RulerRuleDTO) => !isRecordingRulerRule(rule)) ?? []; + const hasSomeNoRecordingRules = rulesWithoutRecordingRules.length > 0; + return ( - {rulerRuleRequests && ( + {rulerRuleRequests && !hasSomeNoRecordingRules &&
This group does not contain alert rules.
} + {rulerRuleRequests && hasSomeNoRecordingRules && ( <>
List of rules that belong to this group
#Evaluations column represents the number of evaluations needed before alert starts firing.
- + )} diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 2148209f3f7..517f4142be5 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -41,6 +41,7 @@ import { PromRuleType, RulerAlertingRuleDTO, RulerGrafanaRuleDTO, + RulerRecordingRuleDTO, RulerRuleGroupDTO, RulerRulesConfigDTO, } from 'app/types/unified-alerting-dto'; @@ -134,6 +135,18 @@ export const mockRulerAlertingRule = (partial: Partial = { ...partial, }); +export const mockRulerRecordingRule = (partial: Partial = {}): RulerAlertingRuleDTO => ({ + alert: 'alert1', + expr: 'up = 1', + labels: { + severity: 'warning', + }, + annotations: { + summary: 'test alert', + }, + ...partial, +}); + export const mockRulerRuleGroup = (partial: Partial = {}): RulerRuleGroupDTO => ({ name: 'group1', rules: [mockRulerAlertingRule()],