[8.5] [RAM] Fix failing rules list bulk action jest tests (#142680) (#144881)

# Backport

This will backport the following commits from `main` to `8.5`:
- [[RAM] Fix failing rules list bulk action jest tests
(#142680)](https://github.com/elastic/kibana/pull/142680)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Jiawei
Wu","email":"74562234+JiaweiWu@users.noreply.github.com"},"sourceCommit":{"committedDate":"2022-11-09T09:04:33Z","message":"[RAM]
Fix failing rules list bulk action jest tests (#142680)\n\n##
Summary\r\nResolves:
https://github.com/elastic/kibana/issues/141052\r\n\r\nRefactors the
rules list test file to help prevent it from timing out\r\nwhen running
in CI\r\n\r\n### Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Xavier Mouligneau
<xavier.mouligneau@elastic.co>","sha":"a9f801c9196e2eee609a55547da63286ff23f0bc","branchLabelMapping":{"^v8.6.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["refactoring","release_note:skip","Team:ResponseOps","Feature:Alerting/RulesManagement","backport:prev-minor","v8.5.0","v8.6.0"],"number":142680,"url":"https://github.com/elastic/kibana/pull/142680","mergeCommit":{"message":"[RAM]
Fix failing rules list bulk action jest tests (#142680)\n\n##
Summary\r\nResolves:
https://github.com/elastic/kibana/issues/141052\r\n\r\nRefactors the
rules list test file to help prevent it from timing out\r\nwhen running
in CI\r\n\r\n### Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Xavier Mouligneau
<xavier.mouligneau@elastic.co>","sha":"a9f801c9196e2eee609a55547da63286ff23f0bc"}},"sourceBranch":"main","suggestedTargetBranches":["8.5"],"targetPullRequestStates":[{"branch":"8.5","label":"v8.5.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.6.0","labelRegex":"^v8.6.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/142680","number":142680,"mergeCommit":{"message":"[RAM]
Fix failing rules list bulk action jest tests (#142680)\n\n##
Summary\r\nResolves:
https://github.com/elastic/kibana/issues/141052\r\n\r\nRefactors the
rules list test file to help prevent it from timing out\r\nwhen running
in CI\r\n\r\n### Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Xavier Mouligneau
<xavier.mouligneau@elastic.co>","sha":"a9f801c9196e2eee609a55547da63286ff23f0bc"}}]}]
BACKPORT-->

Co-authored-by: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2022-11-09 05:24:13 -05:00 committed by GitHub
parent d938a19678
commit 87149bfd06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 599 additions and 488 deletions

View file

@ -12,13 +12,8 @@ import { ReactWrapper } from 'enzyme';
import { actionTypeRegistryMock } from '../../../action_type_registry.mock';
import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock';
import { RulesList, percentileFields } from './rules_list';
import { RuleTypeModel, ValidationResult, Percentiles } from '../../../../types';
import {
RuleExecutionStatusErrorReasons,
RuleExecutionStatusWarningReasons,
ALERTS_FEATURE_ID,
parseDuration,
} from '@kbn/alerting-plugin/common';
import { RuleTypeModel, Percentiles } from '../../../../types';
import { parseDuration } from '@kbn/alerting-plugin/common';
import { getFormattedDuration, getFormattedMilliseconds } from '../../../lib/monitoring_utils';
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
@ -26,6 +21,13 @@ import { useKibana } from '../../../../common/lib/kibana';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { IToasts } from '@kbn/core/public';
import {
mockedRulesData,
ruleTypeFromApi,
getDisabledByLicenseRuleTypeFromApi,
ruleType,
} from './test_helpers';
jest.mock('../../../../common/lib/kibana');
jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
useUiSetting: jest.fn(() => false),
@ -80,14 +82,7 @@ jest.mock('../../../../common/get_experimental_features', () => ({
const ruleTags = ['a', 'b', 'c', 'd'];
const {
loadRuleTypes,
updateAPIKey,
loadRuleTags,
bulkSnoozeRules,
bulkUnsnoozeRules,
bulkUpdateAPIKey,
} = jest.requireMock('../../../lib/rule_api');
const { loadRuleTypes, updateAPIKey, loadRuleTags } = jest.requireMock('../../../lib/rule_api');
const { loadRuleAggregationsWithKueryFilter } = jest.requireMock(
'../../../lib/rule_api/aggregate_kuery_filter'
);
@ -96,239 +91,12 @@ const { loadActionTypes, loadAllActions } = jest.requireMock('../../../lib/actio
const actionTypeRegistry = actionTypeRegistryMock.create();
const ruleTypeRegistry = ruleTypeRegistryMock.create();
const ruleType = {
id: 'test_rule_type',
description: 'test',
iconClass: 'test',
documentationUrl: null,
validate: (): ValidationResult => {
return { errors: {} };
},
ruleParamsExpression: () => null,
requiresAppContext: false,
};
const ruleTypeFromApi = {
id: 'test_rule_type',
name: 'some rule type',
actionGroups: [{ id: 'default', name: 'Default' }],
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
actionVariables: { context: [], state: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
minimumLicenseRequired: 'basic',
enabledInLicense: true,
authorizedConsumers: {
[ALERTS_FEATURE_ID]: { read: true, all: true },
},
ruleTaskTimeout: '1m',
};
ruleTypeRegistry.list.mockReturnValue([ruleType]);
actionTypeRegistry.list.mockReturnValue([]);
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
const mockedRulesData = [
{
id: '1',
name: 'test rule',
tags: ['tag1'],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '1s' },
actions: [],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'active',
lastDuration: 500,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: null,
},
monitoring: {
execution: {
history: [
{
success: true,
duration: 1000000,
},
{
success: true,
duration: 200000,
},
{
success: false,
duration: 300000,
},
],
calculated_metrics: {
success_ratio: 0.66,
p50: 200000,
p95: 300000,
p99: 300000,
},
},
},
},
{
id: '2',
name: 'test rule ok',
tags: ['tag1'],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'ok',
lastDuration: 61000,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: null,
},
monitoring: {
execution: {
history: [
{
success: true,
duration: 100000,
},
{
success: true,
duration: 500000,
},
],
calculated_metrics: {
success_ratio: 1,
p50: 0,
p95: 100000,
p99: 500000,
},
},
},
},
{
id: '3',
name: 'test rule pending',
tags: ['tag1'],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'pending',
lastDuration: 30234,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: null,
},
monitoring: {
execution: {
history: [{ success: false, duration: 100 }],
calculated_metrics: {
success_ratio: 0,
},
},
},
},
{
id: '4',
name: 'test rule error',
tags: ['tag1'],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [{ id: 'test', group: 'rule', params: { message: 'test' } }],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'error',
lastDuration: 122000,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: {
reason: RuleExecutionStatusErrorReasons.Unknown,
message: 'test',
},
},
},
{
id: '5',
name: 'test rule license error',
tags: [],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [{ id: 'test', group: 'rule', params: { message: 'test' } }],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'error',
lastDuration: 500,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: {
reason: RuleExecutionStatusErrorReasons.License,
message: 'test',
},
},
},
{
id: '6',
name: 'test rule warning',
tags: [],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [{ id: 'test', group: 'rule', params: { message: 'test' } }],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'warning',
lastDuration: 500,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
warning: {
reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS,
message: 'test',
},
},
},
];
beforeEach(() => {
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
});
@ -1813,23 +1581,7 @@ describe('rules_list with disabled items', () => {
},
]);
loadRuleTypes.mockResolvedValue([
ruleTypeFromApi,
{
id: 'test_rule_type_disabled_by_license',
name: 'some rule type that is not allowed',
actionGroups: [{ id: 'default', name: 'Default' }],
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
actionVariables: { context: [], state: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
minimumLicenseRequired: 'platinum',
enabledInLicense: false,
authorizedConsumers: {
[ALERTS_FEATURE_ID]: { read: true, all: true },
},
},
]);
loadRuleTypes.mockResolvedValue([ruleTypeFromApi, getDisabledByLicenseRuleTypeFromApi()]);
loadAllActions.mockResolvedValue([]);
ruleTypeRegistry.has.mockReturnValue(false);
@ -1881,231 +1633,3 @@ describe('rules_list with disabled items', () => {
expect(wrapper.find('[data-test-subj="snoozePanel"]').exists()).toBeTruthy();
});
});
// Failing: https://github.com/elastic/kibana/issues/141052
describe.skip('Rules list bulk actions', () => {
let wrapper: ReactWrapper<any>;
async function setup(authorized: boolean = true) {
loadRulesWithKueryFilter.mockResolvedValue({
page: 1,
perPage: 10000,
total: 6,
data: mockedRulesData,
});
loadActionTypes.mockResolvedValue([
{
id: 'test',
name: 'Test',
},
{
id: 'test2',
name: 'Test2',
},
]);
loadRuleTypes.mockResolvedValue([
ruleTypeFromApi,
{
id: 'test_rule_type_disabled_by_license',
name: 'some rule type that is not allowed',
actionGroups: [{ id: 'default', name: 'Default' }],
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
actionVariables: { context: [], state: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
minimumLicenseRequired: 'platinum',
enabledInLicense: false,
authorizedConsumers: {
[ALERTS_FEATURE_ID]: { read: true, all: authorized },
},
},
]);
loadAllActions.mockResolvedValue([]);
// eslint-disable-next-line react-hooks/rules-of-hooks
useKibanaMock().services.ruleTypeRegistry = ruleTypeRegistry;
// eslint-disable-next-line react-hooks/rules-of-hooks
useKibanaMock().services.actionTypeRegistry = actionTypeRegistry;
wrapper = mountWithIntl(<RulesList />);
await act(async () => {
await nextTick();
wrapper.update();
});
}
it('renders select all button for bulk editing', async () => {
await setup();
expect(wrapper.find('[data-test-subj="totalRulesCount"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="showBulkActionButton"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="selectAllRulesButton"]').exists()).toBeFalsy();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
expect(wrapper.find('[data-test-subj="totalRulesCount"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="showBulkActionButton"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="selectAllRulesButton"]').exists()).toBeTruthy();
});
it('does not render select all button if the user is not authorized', async () => {
await setup(false);
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
expect(wrapper.find('[data-test-subj="showBulkActionButton"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="selectAllRulesButton"]').exists()).toBeFalsy();
});
it('selects all will select all items', async () => {
await setup();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click');
mockedRulesData.forEach((rule) => {
expect(
wrapper.find(`[data-test-subj="checkboxSelectRow-${rule.id}"]`).first().prop('checked')
).toBeTruthy();
});
wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click');
expect(wrapper.find('[data-test-subj="ruleQuickEditButton"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="disableAll"]').first().prop('isDisabled')).toBeTruthy();
expect(wrapper.find('[data-test-subj="deleteAll"]').first().prop('isDisabled')).toBeTruthy();
});
it('can bulk snooze', async () => {
await setup();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click');
wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click');
// Unselect something to test filtering
wrapper.find('[data-test-subj="checkboxSelectRow-2"]').at(1).simulate('change');
wrapper.find('[data-test-subj="bulkSnooze"]').first().simulate('click');
expect(wrapper.find('[data-test-subj="snoozePanel"]').exists()).toBeTruthy();
wrapper.find('[data-test-subj="linkSnooze1h"]').first().simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
expect(bulkSnoozeRules).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
filter: 'NOT (alert.id: "alert:2")',
})
);
});
it('can bulk unsnooze', async () => {
await setup();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click');
wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click');
// Unselect something to test filtering
wrapper.find('[data-test-subj="checkboxSelectRow-2"]').at(1).simulate('change');
wrapper.find('[data-test-subj="bulkUnsnooze"]').first().simulate('click');
expect(wrapper.find('[data-test-subj="bulkUnsnoozeConfirmationModal"]').exists()).toBeTruthy();
wrapper.find('[data-test-subj="confirmModalConfirmButton"]').first().simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
expect(bulkUnsnoozeRules).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
filter: 'NOT (alert.id: "alert:2")',
})
);
});
it('can bulk add snooze schedule', async () => {
await setup();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click');
wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click');
// Unselect something to test filtering
wrapper.find('[data-test-subj="checkboxSelectRow-2"]').at(1).simulate('change');
wrapper.find('[data-test-subj="bulkSnoozeSchedule"]').first().simulate('click');
expect(wrapper.find('[data-test-subj="ruleSnoozeScheduler"]').exists()).toBeTruthy();
wrapper.find('[data-test-subj="scheduler-saveSchedule"]').first().simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
expect(bulkSnoozeRules).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
filter: 'NOT (alert.id: "alert:2")',
})
);
});
it('can bulk remove snooze schedule', async () => {
await setup();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click');
wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click');
// Unselect something to test filtering
wrapper.find('[data-test-subj="checkboxSelectRow-2"]').at(1).simulate('change');
wrapper.find('[data-test-subj="bulkRemoveSnoozeSchedule"]').first().simulate('click');
expect(
wrapper.find('[data-test-subj="bulkRemoveScheduleConfirmationModal"]').exists()
).toBeTruthy();
wrapper.find('[data-test-subj="confirmModalConfirmButton"]').first().simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
expect(bulkUnsnoozeRules).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
filter: 'NOT (alert.id: "alert:2")',
scheduleIds: [],
})
);
});
it('can bulk update API key', async () => {
await setup();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click');
wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click');
// Unselect something to test filtering
wrapper.find('[data-test-subj="checkboxSelectRow-2"]').at(1).simulate('change');
wrapper.find('[data-test-subj="updateAPIKeys"]').first().simulate('click');
expect(wrapper.find('[data-test-subj="updateApiKeyIdsConfirmation"]').exists()).toBeTruthy();
wrapper.find('[data-test-subj="confirmModalConfirmButton"]').first().simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
expect(bulkUpdateAPIKey).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
filter: 'NOT (alert.id: "alert:2")',
})
);
});
});

View file

@ -0,0 +1,329 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as React from 'react';
import { ReactWrapper } from 'enzyme';
import { act } from '@testing-library/react';
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
import { actionTypeRegistryMock } from '../../../action_type_registry.mock';
import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock';
import { RulesList } from './rules_list';
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
import { useKibana } from '../../../../common/lib/kibana';
import {
mockedRulesData,
ruleTypeFromApi,
getDisabledByLicenseRuleTypeFromApi,
ruleType,
} from './test_helpers';
jest.mock('../../../../common/lib/kibana');
jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
useUiSetting: jest.fn(() => false),
useUiSetting$: jest.fn((value: string) => ['0,0']),
}));
jest.mock('../../../lib/action_connector_api', () => ({
loadActionTypes: jest.fn(),
loadAllActions: jest.fn(),
}));
jest.mock('../../../lib/rule_api', () => ({
loadRulesWithKueryFilter: jest.fn(),
loadRuleTypes: jest.fn(),
loadRuleAggregationsWithKueryFilter: jest.fn(),
updateAPIKey: jest.fn(),
loadRuleTags: jest.fn(),
bulkSnoozeRules: jest.fn(),
bulkUnsnoozeRules: jest.fn(),
bulkUpdateAPIKey: jest.fn(),
alertingFrameworkHealth: jest.fn(() => ({
isSufficientlySecure: true,
hasPermanentEncryptionKey: true,
})),
}));
jest.mock('../../../lib/rule_api/aggregate_kuery_filter');
jest.mock('../../../lib/rule_api/rules_kuery_filter');
jest.mock('../../../../common/lib/health_api', () => ({
triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })),
}));
jest.mock('../../../../common/lib/config_api', () => ({
triggersActionsUiConfig: jest
.fn()
.mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }),
}));
jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: jest.fn(),
}),
useLocation: () => ({
pathname: '/triggersActions/rules/',
}),
}));
jest.mock('../../../lib/capabilities', () => ({
hasAllPrivilege: jest.fn(() => true),
hasSaveRulesCapability: jest.fn(() => true),
hasShowActionsCapability: jest.fn(() => true),
hasExecuteActionsCapability: jest.fn(() => true),
}));
jest.mock('../../../../common/get_experimental_features', () => ({
getIsExperimentalFeatureEnabled: jest.fn(),
}));
const { loadRuleTypes, bulkSnoozeRules, bulkUnsnoozeRules, bulkUpdateAPIKey } =
jest.requireMock('../../../lib/rule_api');
const { loadRulesWithKueryFilter } = jest.requireMock('../../../lib/rule_api/rules_kuery_filter');
const { loadActionTypes, loadAllActions } = jest.requireMock('../../../lib/action_connector_api');
const actionTypeRegistry = actionTypeRegistryMock.create();
const ruleTypeRegistry = ruleTypeRegistryMock.create();
ruleTypeRegistry.list.mockReturnValue([ruleType]);
actionTypeRegistry.list.mockReturnValue([]);
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
beforeEach(() => {
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
});
describe('Rules list bulk actions', () => {
let wrapper: ReactWrapper<any>;
async function setup(authorized: boolean = true) {
loadRulesWithKueryFilter.mockResolvedValue({
page: 1,
perPage: 10000,
total: 6,
data: mockedRulesData,
});
loadActionTypes.mockResolvedValue([
{
id: 'test',
name: 'Test',
},
{
id: 'test2',
name: 'Test2',
},
]);
loadRuleTypes.mockResolvedValue([
ruleTypeFromApi,
getDisabledByLicenseRuleTypeFromApi(authorized),
]);
loadAllActions.mockResolvedValue([]);
// eslint-disable-next-line react-hooks/rules-of-hooks
useKibanaMock().services.ruleTypeRegistry = ruleTypeRegistry;
// eslint-disable-next-line react-hooks/rules-of-hooks
useKibanaMock().services.actionTypeRegistry = actionTypeRegistry;
wrapper = mountWithIntl(<RulesList />);
await act(async () => {
await nextTick();
wrapper.update();
});
}
afterEach(() => {
jest.clearAllMocks();
});
it('renders select all button for bulk editing', async () => {
await setup();
expect(wrapper.find('[data-test-subj="totalRulesCount"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="showBulkActionButton"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="selectAllRulesButton"]').exists()).toBeFalsy();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
expect(wrapper.find('[data-test-subj="totalRulesCount"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="showBulkActionButton"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="selectAllRulesButton"]').exists()).toBeTruthy();
});
it('does not render select all button if the user is not authorized', async () => {
await setup(false);
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
expect(wrapper.find('[data-test-subj="showBulkActionButton"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="selectAllRulesButton"]').exists()).toBeFalsy();
});
it('selects all will select all items', async () => {
await setup();
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click');
mockedRulesData.forEach((rule) => {
expect(
wrapper.find(`[data-test-subj="checkboxSelectRow-${rule.id}"]`).first().prop('checked')
).toBeTruthy();
});
wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click');
expect(wrapper.find('[data-test-subj="ruleQuickEditButton"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="disableAll"]').first().prop('isDisabled')).toBeTruthy();
expect(wrapper.find('[data-test-subj="deleteAll"]').first().prop('isDisabled')).toBeTruthy();
});
describe('bulk actions', () => {
beforeAll(async () => {
await setup();
});
beforeEach(() => {
wrapper.find('[data-test-subj="checkboxSelectRow-1"]').at(1).simulate('change');
wrapper.find('[data-test-subj="selectAllRulesButton"]').at(1).simulate('click');
// Unselect something to test filtering
wrapper.find('[data-test-subj="checkboxSelectRow-2"]').at(1).simulate('change');
wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click');
});
it('can bulk snooze', async () => {
wrapper.find('[data-test-subj="bulkSnooze"]').first().simulate('click');
expect(wrapper.find('[data-test-subj="snoozePanel"]').exists()).toBeTruthy();
wrapper.find('[data-test-subj="linkSnooze1h"]').first().simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
const filter = bulkSnoozeRules.mock.calls[0][0].filter;
expect(filter.function).toEqual('not');
expect(filter.arguments[0].arguments[0].value).toEqual('alert.id');
expect(filter.arguments[0].arguments[1].value).toEqual('alert:2');
expect(bulkSnoozeRules).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
})
);
});
it('can bulk unsnooze', async () => {
wrapper.find('[data-test-subj="bulkUnsnooze"]').hostNodes().first().simulate('click');
expect(
wrapper.find('[data-test-subj="bulkUnsnoozeConfirmationModal"]').exists()
).toBeTruthy();
wrapper
.find('[data-test-subj="confirmModalConfirmButton"]')
.hostNodes()
.first()
.simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
const filter = bulkUnsnoozeRules.mock.calls[0][0].filter;
expect(filter.function).toEqual('not');
expect(filter.arguments[0].arguments[0].value).toEqual('alert.id');
expect(filter.arguments[0].arguments[1].value).toEqual('alert:2');
expect(bulkUnsnoozeRules).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
})
);
});
it('can bulk add snooze schedule', async () => {
wrapper.find('[data-test-subj="bulkSnoozeSchedule"]').hostNodes().first().simulate('click');
expect(wrapper.find('[data-test-subj="ruleSnoozeScheduler"]').exists()).toBeTruthy();
wrapper
.find('[data-test-subj="scheduler-saveSchedule"]')
.hostNodes()
.first()
.simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
const filter = bulkSnoozeRules.mock.calls[0][0].filter;
expect(filter.function).toEqual('not');
expect(filter.arguments[0].arguments[0].value).toEqual('alert.id');
expect(filter.arguments[0].arguments[1].value).toEqual('alert:2');
expect(bulkSnoozeRules).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
})
);
});
it('can bulk remove snooze schedule', async () => {
wrapper
.find('[data-test-subj="bulkRemoveSnoozeSchedule"]')
.hostNodes()
.first()
.simulate('click');
expect(
wrapper.find('[data-test-subj="bulkRemoveScheduleConfirmationModal"]').exists()
).toBeTruthy();
wrapper
.find('[data-test-subj="confirmModalConfirmButton"]')
.hostNodes()
.first()
.simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
const filter = bulkUnsnoozeRules.mock.calls[0][0].filter;
expect(filter.function).toEqual('not');
expect(filter.arguments[0].arguments[0].value).toEqual('alert.id');
expect(filter.arguments[0].arguments[1].value).toEqual('alert:2');
expect(bulkUnsnoozeRules).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
scheduleIds: [],
})
);
});
it('can bulk update API key', async () => {
wrapper.find('[data-test-subj="updateAPIKeys"]').hostNodes().first().simulate('click');
expect(wrapper.find('[data-test-subj="updateApiKeyIdsConfirmation"]').exists()).toBeTruthy();
wrapper
.find('[data-test-subj="confirmModalConfirmButton"]')
.hostNodes()
.first()
.simulate('click');
await act(async () => {
await nextTick();
wrapper.update();
});
const filter = bulkUpdateAPIKey.mock.calls[0][0].filter;
expect(filter.function).toEqual('not');
expect(filter.arguments[0].arguments[0].value).toEqual('alert.id');
expect(filter.arguments[0].arguments[1].value).toEqual('alert:2');
expect(bulkUpdateAPIKey).toHaveBeenCalledWith(
expect.objectContaining({
ids: [],
})
);
});
});
});

View file

@ -0,0 +1,258 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
ALERTS_FEATURE_ID,
RuleExecutionStatusErrorReasons,
RuleExecutionStatusWarningReasons,
} from '@kbn/alerting-plugin/common';
import { ValidationResult } from '../../../../types';
export const mockedRulesData = [
{
id: '1',
name: 'test rule',
tags: ['tag1'],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '1s' },
actions: [],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'active',
lastDuration: 500,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: null,
},
monitoring: {
execution: {
history: [
{
success: true,
duration: 1000000,
},
{
success: true,
duration: 200000,
},
{
success: false,
duration: 300000,
},
],
calculated_metrics: {
success_ratio: 0.66,
p50: 200000,
p95: 300000,
p99: 300000,
},
},
},
},
{
id: '2',
name: 'test rule ok',
tags: ['tag1'],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'ok',
lastDuration: 61000,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: null,
},
monitoring: {
execution: {
history: [
{
success: true,
duration: 100000,
},
{
success: true,
duration: 500000,
},
],
calculated_metrics: {
success_ratio: 1,
p50: 0,
p95: 100000,
p99: 500000,
},
},
},
},
{
id: '3',
name: 'test rule pending',
tags: ['tag1'],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'pending',
lastDuration: 30234,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: null,
},
monitoring: {
execution: {
history: [{ success: false, duration: 100 }],
calculated_metrics: {
success_ratio: 0,
},
},
},
},
{
id: '4',
name: 'test rule error',
tags: ['tag1'],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [{ id: 'test', group: 'rule', params: { message: 'test' } }],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'error',
lastDuration: 122000,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: {
reason: RuleExecutionStatusErrorReasons.Unknown,
message: 'test',
},
},
},
{
id: '5',
name: 'test rule license error',
tags: [],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [{ id: 'test', group: 'rule', params: { message: 'test' } }],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'error',
lastDuration: 500,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
error: {
reason: RuleExecutionStatusErrorReasons.License,
message: 'test',
},
},
},
{
id: '6',
name: 'test rule warning',
tags: [],
enabled: true,
ruleTypeId: 'test_rule_type',
schedule: { interval: '5d' },
actions: [{ id: 'test', group: 'rule', params: { message: 'test' } }],
params: { name: 'test rule type name' },
scheduledTaskId: null,
createdBy: null,
updatedBy: null,
apiKeyOwner: null,
throttle: '1m',
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'warning',
lastDuration: 500,
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
warning: {
reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS,
message: 'test',
},
},
},
];
export const ruleTypeFromApi = {
id: 'test_rule_type',
name: 'some rule type',
actionGroups: [{ id: 'default', name: 'Default' }],
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
actionVariables: { context: [], state: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
minimumLicenseRequired: 'basic',
enabledInLicense: true,
authorizedConsumers: {
[ALERTS_FEATURE_ID]: { read: true, all: true },
},
ruleTaskTimeout: '1m',
};
export const getDisabledByLicenseRuleTypeFromApi = (authorized: boolean = true) => ({
id: 'test_rule_type_disabled_by_license',
name: 'some rule type that is not allowed',
actionGroups: [{ id: 'default', name: 'Default' }],
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
actionVariables: { context: [], state: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
minimumLicenseRequired: 'platinum',
enabledInLicense: false,
authorizedConsumers: {
[ALERTS_FEATURE_ID]: { read: true, all: authorized },
},
});
export const ruleType = {
id: 'test_rule_type',
description: 'test',
iconClass: 'test',
documentationUrl: null,
validate: (): ValidationResult => {
return { errors: {} };
},
ruleParamsExpression: () => null,
requiresAppContext: false,
};