mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Lotex alert groups removal (#45150)
* Add lotex group removal UI * Connect UI to delete group action * Add rules' refreshing after deletion of a group * Improve confirmation message * Add tests for RulesGroup * Remove redundant check
This commit is contained in:
parent
bb5a39faef
commit
d6c580e338
@ -0,0 +1,111 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { configureStore } from 'app/store/configureStore';
|
||||||
|
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { byTestId, byText } from 'testing-library-selector';
|
||||||
|
import { mockCombinedRule, mockDataSource } from '../../mocks';
|
||||||
|
import { RulesGroup } from './RulesGroup';
|
||||||
|
|
||||||
|
const hasRulerMock = jest.fn<boolean, any>();
|
||||||
|
jest.mock('../../hooks/useHasRuler', () => ({
|
||||||
|
useHasRuler: () => hasRulerMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => hasRulerMock.mockReset());
|
||||||
|
|
||||||
|
const ui = {
|
||||||
|
editGroupButton: byTestId('edit-group'),
|
||||||
|
deleteGroupButton: byTestId('delete-group'),
|
||||||
|
confirmDeleteModal: {
|
||||||
|
header: byText('Delete group'),
|
||||||
|
confirmButton: byText('Delete'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Rules group tests', () => {
|
||||||
|
const store = configureStore();
|
||||||
|
|
||||||
|
function renderRulesGroup(namespace: CombinedRuleNamespace, group: CombinedRuleGroup) {
|
||||||
|
return render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<RulesGroup group={group} namespace={namespace} expandAll={false} />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('When the datasource is grafana', () => {
|
||||||
|
const group: CombinedRuleGroup = {
|
||||||
|
name: 'TestGroup',
|
||||||
|
rules: [mockCombinedRule()],
|
||||||
|
};
|
||||||
|
|
||||||
|
const namespace: CombinedRuleNamespace = {
|
||||||
|
name: 'TestNamespace',
|
||||||
|
rulesSource: 'grafana',
|
||||||
|
groups: [group],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('Should hide delete and edit group buttons', () => {
|
||||||
|
// Act
|
||||||
|
renderRulesGroup(namespace, group);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(ui.deleteGroupButton.query()).not.toBeInTheDocument();
|
||||||
|
expect(ui.editGroupButton.query()).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('When the datasource is not grafana', () => {
|
||||||
|
const group: CombinedRuleGroup = {
|
||||||
|
name: 'TestGroup',
|
||||||
|
rules: [mockCombinedRule()],
|
||||||
|
};
|
||||||
|
|
||||||
|
const namespace: CombinedRuleNamespace = {
|
||||||
|
name: 'TestNamespace',
|
||||||
|
rulesSource: mockDataSource(),
|
||||||
|
groups: [group],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('When ruler enabled should display delete and edit group buttons', () => {
|
||||||
|
// Arrange
|
||||||
|
hasRulerMock.mockReturnValue(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderRulesGroup(namespace, group);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(hasRulerMock).toHaveBeenCalled();
|
||||||
|
expect(ui.deleteGroupButton.get()).toBeInTheDocument();
|
||||||
|
expect(ui.editGroupButton.get()).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('When ruler disabled should hide delete and edit group buttons', () => {
|
||||||
|
// Arrange
|
||||||
|
hasRulerMock.mockReturnValue(false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderRulesGroup(namespace, group);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(hasRulerMock).toHaveBeenCalled();
|
||||||
|
expect(ui.deleteGroupButton.query()).not.toBeInTheDocument();
|
||||||
|
expect(ui.editGroupButton.query()).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Delete button click should display confirmation modal', () => {
|
||||||
|
// Arrange
|
||||||
|
hasRulerMock.mockReturnValue(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderRulesGroup(namespace, group);
|
||||||
|
userEvent.click(ui.deleteGroupButton.get());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(ui.confirmDeleteModal.header.get()).toBeInTheDocument();
|
||||||
|
expect(ui.confirmDeleteModal.confirmButton.get()).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,18 +1,21 @@
|
|||||||
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
|
||||||
import React, { FC, useState, useEffect } from 'react';
|
|
||||||
import { HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui';
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { ConfirmModal, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
|
import kbn from 'app/core/utils/kbn';
|
||||||
|
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||||
|
import pluralize from 'pluralize';
|
||||||
|
import React, { FC, useEffect, useState } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useFolder } from '../../hooks/useFolder';
|
||||||
|
import { useHasRuler } from '../../hooks/useHasRuler';
|
||||||
|
import { deleteRulesGroupAction } from '../../state/actions';
|
||||||
|
import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource';
|
||||||
import { isGrafanaRulerRule } from '../../utils/rules';
|
import { isGrafanaRulerRule } from '../../utils/rules';
|
||||||
import { CollapseToggle } from '../CollapseToggle';
|
import { CollapseToggle } from '../CollapseToggle';
|
||||||
import { RulesTable } from './RulesTable';
|
|
||||||
import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource';
|
|
||||||
import { ActionIcon } from './ActionIcon';
|
import { ActionIcon } from './ActionIcon';
|
||||||
import { useHasRuler } from '../../hooks/useHasRuler';
|
|
||||||
import kbn from 'app/core/utils/kbn';
|
|
||||||
import { useFolder } from '../../hooks/useFolder';
|
|
||||||
import { RuleStats } from './RuleStats';
|
|
||||||
import { EditCloudGroupModal } from './EditCloudGroupModal';
|
import { EditCloudGroupModal } from './EditCloudGroupModal';
|
||||||
|
import { RulesTable } from './RulesTable';
|
||||||
|
import { RuleStats } from './RuleStats';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
namespace: CombinedRuleNamespace;
|
namespace: CombinedRuleNamespace;
|
||||||
@ -22,9 +25,11 @@ interface Props {
|
|||||||
|
|
||||||
export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }) => {
|
export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }) => {
|
||||||
const { rulesSource } = namespace;
|
const { rulesSource } = namespace;
|
||||||
|
const dispatch = useDispatch();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const [isEditingGroup, setIsEditingGroup] = useState(false);
|
const [isEditingGroup, setIsEditingGroup] = useState(false);
|
||||||
|
const [isDeletingGroup, setIsDeletingGroup] = useState(false);
|
||||||
const [isCollapsed, setIsCollapsed] = useState(!expandAll);
|
const [isCollapsed, setIsCollapsed] = useState(!expandAll);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -39,6 +44,11 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
|
|||||||
// group "is deleting" if rules source has ruler, but this group has no rules that are in ruler
|
// group "is deleting" if rules source has ruler, but this group has no rules that are in ruler
|
||||||
const isDeleting = hasRuler(rulesSource) && !group.rules.find((rule) => !!rule.rulerRule);
|
const isDeleting = hasRuler(rulesSource) && !group.rules.find((rule) => !!rule.rulerRule);
|
||||||
|
|
||||||
|
const deleteGroup = () => {
|
||||||
|
dispatch(deleteRulesGroupAction(namespace, group));
|
||||||
|
setIsDeletingGroup(false);
|
||||||
|
};
|
||||||
|
|
||||||
const actionIcons: React.ReactNode[] = [];
|
const actionIcons: React.ReactNode[] = [];
|
||||||
|
|
||||||
// for grafana, link to folder views
|
// for grafana, link to folder views
|
||||||
@ -88,6 +98,17 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
|
|||||||
onClick={() => setIsEditingGroup(true)}
|
onClick={() => setIsEditingGroup(true)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
actionIcons.push(
|
||||||
|
<ActionIcon
|
||||||
|
aria-label="delete rule group"
|
||||||
|
data-testid="delete-group"
|
||||||
|
key="delete-group"
|
||||||
|
icon="trash-alt"
|
||||||
|
tooltip="delete rule group"
|
||||||
|
onClick={() => setIsDeletingGroup(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupName = isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name;
|
const groupName = isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name;
|
||||||
@ -129,6 +150,22 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
|
|||||||
{isEditingGroup && (
|
{isEditingGroup && (
|
||||||
<EditCloudGroupModal group={group} namespace={namespace} onClose={() => setIsEditingGroup(false)} />
|
<EditCloudGroupModal group={group} namespace={namespace} onClose={() => setIsEditingGroup(false)} />
|
||||||
)}
|
)}
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isDeletingGroup}
|
||||||
|
title="Delete group"
|
||||||
|
body={
|
||||||
|
<div>
|
||||||
|
Deleting this group will permanently remove the group
|
||||||
|
<br />
|
||||||
|
and {group.rules.length} alert {pluralize('rule', group.rules.length)} belonging to it.
|
||||||
|
<br />
|
||||||
|
Are you sure you want to delete this group?
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onConfirm={deleteGroup}
|
||||||
|
onDismiss={() => setIsDeletingGroup(false)}
|
||||||
|
confirmText="Delete"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -12,7 +12,14 @@ import {
|
|||||||
TestReceiversAlert,
|
TestReceiversAlert,
|
||||||
} from 'app/plugins/datasource/alertmanager/types';
|
} from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { FolderDTO, NotifierDTO, ThunkResult } from 'app/types';
|
import { FolderDTO, NotifierDTO, ThunkResult } from 'app/types';
|
||||||
import { RuleIdentifier, RuleNamespace, RuleWithLocation, StateHistoryItem } from 'app/types/unified-alerting';
|
import {
|
||||||
|
CombinedRuleGroup,
|
||||||
|
CombinedRuleNamespace,
|
||||||
|
RuleIdentifier,
|
||||||
|
RuleNamespace,
|
||||||
|
RuleWithLocation,
|
||||||
|
StateHistoryItem,
|
||||||
|
} from 'app/types/unified-alerting';
|
||||||
import {
|
import {
|
||||||
PostableRulerRuleGroupDTO,
|
PostableRulerRuleGroupDTO,
|
||||||
RulerGrafanaRuleDTO,
|
RulerGrafanaRuleDTO,
|
||||||
@ -49,6 +56,7 @@ import {
|
|||||||
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
||||||
import {
|
import {
|
||||||
getAllRulesSourceNames,
|
getAllRulesSourceNames,
|
||||||
|
getRulesSourceName,
|
||||||
GRAFANA_RULES_SOURCE_NAME,
|
GRAFANA_RULES_SOURCE_NAME,
|
||||||
isGrafanaRulesSource,
|
isGrafanaRulesSource,
|
||||||
isVanillaPrometheusAlertManagerDataSource,
|
isVanillaPrometheusAlertManagerDataSource,
|
||||||
@ -263,6 +271,24 @@ async function deleteRule(ruleWithLocation: RuleWithLocation): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteRulesGroupAction(
|
||||||
|
namespace: CombinedRuleNamespace,
|
||||||
|
ruleGroup: CombinedRuleGroup
|
||||||
|
): ThunkResult<void> {
|
||||||
|
return async (dispatch) => {
|
||||||
|
withAppEvents(
|
||||||
|
(async () => {
|
||||||
|
const sourceName = getRulesSourceName(namespace.rulesSource);
|
||||||
|
|
||||||
|
await deleteRulerRulesGroup(sourceName, namespace.name, ruleGroup.name);
|
||||||
|
dispatch(fetchRulerRulesAction({ rulesSourceName: sourceName }));
|
||||||
|
dispatch(fetchPromRulesAction({ rulesSourceName: sourceName }));
|
||||||
|
})(),
|
||||||
|
{ successMessage: 'Group deleted' }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteRuleAction(
|
export function deleteRuleAction(
|
||||||
ruleIdentifier: RuleIdentifier,
|
ruleIdentifier: RuleIdentifier,
|
||||||
options: { navigateTo?: string } = {}
|
options: { navigateTo?: string } = {}
|
||||||
|
Loading…
Reference in New Issue
Block a user