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
This commit is contained in:
Sonia Aguilar 2023-01-26 16:11:29 +01:00 committed by GitHub
parent ea1fcbb866
commit 8bbef70363
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 179 additions and 23 deletions

View File

@ -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 <Provider store={store}>{children}</Provider>;
};
}
describe('EditGroupModal component on cloud alert rules', () => {
it('Should show alert table in case of having some non-recording rules in the group', () => {
render(<EditCloudGroupModal {...defaultProps} />, {
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(<EditCloudGroupModal {...defaultProps} />, {
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(<EditCloudGroupModal {...defaultProps} sourceName={GRAFANA_DATASOURCE_NAME} />, {
wrapper: getProvidersWrapper(),
});
expect(screen.getByText(/alert1/i)).toBeInTheDocument();
});
});

View File

@ -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<AlertInfo>;
type AlertsWithForTableProps = DynamicTableItemProps<AlertInfo>;
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<FormValues>();
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 (
<Modal
className={styles.modal}
@ -431,17 +426,14 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement {
</Button>
</Modal.ButtonRow>
</div>
{rulerRuleRequests && (
{rulerRuleRequests && !hasSomeNoRecordingRules && <div>This group does not contain alert rules.</div>}
{rulerRuleRequests && hasSomeNoRecordingRules && (
<>
<div>List of rules that belong to this group</div>
<div className={styles.evalRequiredLabel}>
#Evaluations column represents the number of evaluations needed before alert starts firing.
</div>
<RulesForGroupTable
rulerRules={groupfoldersForSource?.result}
groupName={groupName}
folderName={nameSpaceName}
/>
<RulesForGroupTable rulesWithoutRecordingRules={rulesWithoutRecordingRules} />
</>
)}
</>

View File

@ -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<RulerAlertingRuleDTO> = {
...partial,
});
export const mockRulerRecordingRule = (partial: Partial<RulerRecordingRuleDTO> = {}): RulerAlertingRuleDTO => ({
alert: 'alert1',
expr: 'up = 1',
labels: {
severity: 'warning',
},
annotations: {
summary: 'test alert',
},
...partial,
});
export const mockRulerRuleGroup = (partial: Partial<RulerRuleGroupDTO> = {}): RulerRuleGroupDTO => ({
name: 'group1',
rules: [mockRulerAlertingRule()],