mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
ea1fcbb866
commit
8bbef70363
@ -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();
|
||||
});
|
||||
});
|
@ -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} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@ -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()],
|
||||
|
Loading…
Reference in New Issue
Block a user