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 { 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 { CollapseToggle } from '../CollapseToggle';
|
||||
import { RulesTable } from './RulesTable';
|
||||
import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource';
|
||||
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 { RulesTable } from './RulesTable';
|
||||
import { RuleStats } from './RuleStats';
|
||||
|
||||
interface Props {
|
||||
namespace: CombinedRuleNamespace;
|
||||
@ -22,9 +25,11 @@ interface Props {
|
||||
|
||||
export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }) => {
|
||||
const { rulesSource } = namespace;
|
||||
const dispatch = useDispatch();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const [isEditingGroup, setIsEditingGroup] = useState(false);
|
||||
const [isDeletingGroup, setIsDeletingGroup] = useState(false);
|
||||
const [isCollapsed, setIsCollapsed] = useState(!expandAll);
|
||||
|
||||
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
|
||||
const isDeleting = hasRuler(rulesSource) && !group.rules.find((rule) => !!rule.rulerRule);
|
||||
|
||||
const deleteGroup = () => {
|
||||
dispatch(deleteRulesGroupAction(namespace, group));
|
||||
setIsDeletingGroup(false);
|
||||
};
|
||||
|
||||
const actionIcons: React.ReactNode[] = [];
|
||||
|
||||
// for grafana, link to folder views
|
||||
@ -88,6 +98,17 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
|
||||
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;
|
||||
@ -129,6 +150,22 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
|
||||
{isEditingGroup && (
|
||||
<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>
|
||||
);
|
||||
});
|
||||
|
@ -12,7 +12,14 @@ import {
|
||||
TestReceiversAlert,
|
||||
} from 'app/plugins/datasource/alertmanager/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 {
|
||||
PostableRulerRuleGroupDTO,
|
||||
RulerGrafanaRuleDTO,
|
||||
@ -49,6 +56,7 @@ import {
|
||||
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
||||
import {
|
||||
getAllRulesSourceNames,
|
||||
getRulesSourceName,
|
||||
GRAFANA_RULES_SOURCE_NAME,
|
||||
isGrafanaRulesSource,
|
||||
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(
|
||||
ruleIdentifier: RuleIdentifier,
|
||||
options: { navigateTo?: string } = {}
|
||||
|
Loading…
Reference in New Issue
Block a user