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 { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
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 { useAppNotification } from 'app/core/copy/appNotification';
|
||||||
import { useCleanup } from 'app/core/hooks/useCleanup';
|
import { useCleanup } from 'app/core/hooks/useCleanup';
|
||||||
import { useDispatch } from 'app/types';
|
import { useDispatch } from 'app/types';
|
||||||
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
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 { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||||
import { rulesInSameGroupHaveInvalidFor, updateLotexNamespaceAndGroupAction } from '../../state/actions';
|
import { rulesInSameGroupHaveInvalidFor, updateLotexNamespaceAndGroupAction } from '../../state/actions';
|
||||||
import { checkEvaluationIntervalGlobalLimit } from '../../utils/config';
|
import { checkEvaluationIntervalGlobalLimit } from '../../utils/config';
|
||||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||||
import { initialAsyncRequestState } from '../../utils/redux';
|
import { initialAsyncRequestState } from '../../utils/redux';
|
||||||
import { isAlertingRulerRule, isGrafanaRulerRule } from '../../utils/rules';
|
import { isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from '../../utils/rules';
|
||||||
import { parsePrometheusDuration } from '../../utils/time';
|
import { parsePrometheusDuration } from '../../utils/time';
|
||||||
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
|
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
|
||||||
import { InfoIcon } from '../InfoIcon';
|
import { InfoIcon } from '../InfoIcon';
|
||||||
@ -122,24 +122,14 @@ export const safeParseDurationstr = (duration: string): number => {
|
|||||||
type AlertsWithForTableColumnProps = DynamicTableColumnProps<AlertInfo>;
|
type AlertsWithForTableColumnProps = DynamicTableColumnProps<AlertInfo>;
|
||||||
type AlertsWithForTableProps = DynamicTableItemProps<AlertInfo>;
|
type AlertsWithForTableProps = DynamicTableItemProps<AlertInfo>;
|
||||||
|
|
||||||
export const RulesForGroupTable = ({
|
export const RulesForGroupTable = ({ rulesWithoutRecordingRules }: { rulesWithoutRecordingRules: RulerRuleDTO[] }) => {
|
||||||
rulerRules,
|
|
||||||
groupName,
|
|
||||||
folderName,
|
|
||||||
}: {
|
|
||||||
rulerRules: RulerRulesConfigDTO | null | undefined;
|
|
||||||
groupName: string;
|
|
||||||
folderName: string;
|
|
||||||
}) => {
|
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const group = getGroupFromRuler(rulerRules, groupName, folderName);
|
|
||||||
const rules: RulerRuleDTO[] = group?.rules ?? [];
|
|
||||||
|
|
||||||
const { watch } = useFormContext<FormValues>();
|
const { watch } = useFormContext<FormValues>();
|
||||||
const currentInterval = watch('groupInterval');
|
const currentInterval = watch('groupInterval');
|
||||||
const unknownCurrentInterval = !Boolean(currentInterval);
|
const unknownCurrentInterval = !Boolean(currentInterval);
|
||||||
|
|
||||||
const rows: AlertsWithForTableProps[] = rules
|
const rows: AlertsWithForTableProps[] = rulesWithoutRecordingRules
|
||||||
.slice()
|
.slice()
|
||||||
.map((rule: RulerRuleDTO, index) => ({
|
.map((rule: RulerRuleDTO, index) => ({
|
||||||
id: index,
|
id: index,
|
||||||
@ -198,7 +188,7 @@ export const RulesForGroupTable = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface CombinedGroupAndNameSpace {
|
export interface CombinedGroupAndNameSpace {
|
||||||
namespace: CombinedRuleNamespace;
|
namespace: CombinedRuleNamespace;
|
||||||
group: CombinedRuleGroup;
|
group: CombinedRuleGroup;
|
||||||
}
|
}
|
||||||
@ -206,7 +196,7 @@ interface GroupAndNameSpaceNames {
|
|||||||
namespace: string;
|
namespace: string;
|
||||||
group: string;
|
group: string;
|
||||||
}
|
}
|
||||||
interface ModalProps {
|
export interface ModalProps {
|
||||||
nameSpaceAndGroup: CombinedGroupAndNameSpace | GroupAndNameSpaceNames;
|
nameSpaceAndGroup: CombinedGroupAndNameSpace | GroupAndNameSpaceNames;
|
||||||
sourceName: string;
|
sourceName: string;
|
||||||
groupInterval: string;
|
groupInterval: string;
|
||||||
@ -328,6 +318,11 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement {
|
|||||||
const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules);
|
const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules);
|
||||||
const groupfoldersForSource = rulerRuleRequests[sourceName];
|
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 (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className={styles.modal}
|
className={styles.modal}
|
||||||
@ -431,17 +426,14 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement {
|
|||||||
</Button>
|
</Button>
|
||||||
</Modal.ButtonRow>
|
</Modal.ButtonRow>
|
||||||
</div>
|
</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>List of rules that belong to this group</div>
|
||||||
<div className={styles.evalRequiredLabel}>
|
<div className={styles.evalRequiredLabel}>
|
||||||
#Evaluations column represents the number of evaluations needed before alert starts firing.
|
#Evaluations column represents the number of evaluations needed before alert starts firing.
|
||||||
</div>
|
</div>
|
||||||
<RulesForGroupTable
|
<RulesForGroupTable rulesWithoutRecordingRules={rulesWithoutRecordingRules} />
|
||||||
rulerRules={groupfoldersForSource?.result}
|
|
||||||
groupName={groupName}
|
|
||||||
folderName={nameSpaceName}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -41,6 +41,7 @@ import {
|
|||||||
PromRuleType,
|
PromRuleType,
|
||||||
RulerAlertingRuleDTO,
|
RulerAlertingRuleDTO,
|
||||||
RulerGrafanaRuleDTO,
|
RulerGrafanaRuleDTO,
|
||||||
|
RulerRecordingRuleDTO,
|
||||||
RulerRuleGroupDTO,
|
RulerRuleGroupDTO,
|
||||||
RulerRulesConfigDTO,
|
RulerRulesConfigDTO,
|
||||||
} from 'app/types/unified-alerting-dto';
|
} from 'app/types/unified-alerting-dto';
|
||||||
@ -134,6 +135,18 @@ export const mockRulerAlertingRule = (partial: Partial<RulerAlertingRuleDTO> = {
|
|||||||
...partial,
|
...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 => ({
|
export const mockRulerRuleGroup = (partial: Partial<RulerRuleGroupDTO> = {}): RulerRuleGroupDTO => ({
|
||||||
name: 'group1',
|
name: 'group1',
|
||||||
rules: [mockRulerAlertingRule()],
|
rules: [mockRulerAlertingRule()],
|
||||||
|
Loading…
Reference in New Issue
Block a user