From 9d6aa997edd73af507c0221e36385b67fa253abd Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 20 Apr 2022 13:10:06 +0200 Subject: [PATCH] Unit Tests for new o11y rules page (#128576) * tests for rules page * mock useKibana and useBreadcrubms * mock useFetchRules * rules page scenarios * temp * mock usePluginContext and useLoadRuleTypes * fix typescript errors * fix Jest did not exit one second after the test run has completed warning * add more tests to the empty rules page scenario * fix typescript error * render documentation link * empty RulesPage with show only capability * rules page with items * RulesPage with items and show only capability * remove disabled items (I need to implement it later on) * refactoring * update documentation link * add types to the ruleState Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/hooks/use_fetch_rules.ts | 11 +- ...observability_public_plugins_start.mock.ts | 16 +- .../components/prompts/no_data_prompt.tsx | 2 +- .../prompts/no_permission_prompt.tsx | 1 + .../public/pages/rules/index.test.tsx | 559 ++++++++++++++++++ .../observability/public/pages/rules/types.ts | 7 + 6 files changed, 585 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/rules/index.test.tsx diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts b/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts index b241cb3529f89..00cb58e504bdc 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts +++ b/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts @@ -7,19 +7,12 @@ import { useEffect, useState, useCallback } from 'react'; import { isEmpty } from 'lodash'; -import { loadRules, Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import { loadRules } from '@kbn/triggers-actions-ui-plugin/public'; import { RULES_LOAD_ERROR } from '../pages/rules/translations'; -import { FetchRulesProps } from '../pages/rules/types'; +import { FetchRulesProps, RuleState } from '../pages/rules/types'; import { OBSERVABILITY_RULE_TYPES } from '../pages/rules/config'; import { useKibana } from '../utils/kibana_react'; -interface RuleState { - isLoading: boolean; - data: Rule[]; - error: string | null; - totalItemCount: number; -} - export function useFetchRules({ searchText, ruleLastResponseFilter, diff --git a/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts b/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts index 4d6d312bcf199..62edefc1b737d 100644 --- a/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts +++ b/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts @@ -32,12 +32,26 @@ const embeddableStartMock = { }, }; +const triggersActionsUiStartMock = { + createStart() { + return { + getAddAlertFlyout: jest.fn(), + ruleTypeRegistry: { + has: jest.fn(), + register: jest.fn(), + get: jest.fn(), + list: jest.fn(), + }, + }; + }, +}; + export const observabilityPublicPluginsStartMock = { createStart() { return { cases: casesUiStartMock.createStart(), embeddable: embeddableStartMock.createStart(), - triggersActionsUi: null, + triggersActionsUi: triggersActionsUiStartMock.createStart(), data: null, lens: null, discover: null, diff --git a/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx b/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx index b9c0e24160004..bacc311357fd7 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx @@ -58,7 +58,7 @@ export function NoDataPrompt({ /> , - + Documentation , diff --git a/x-pack/plugins/observability/public/pages/rules/components/prompts/no_permission_prompt.tsx b/x-pack/plugins/observability/public/pages/rules/components/prompts/no_permission_prompt.tsx index edfe1c6840d8b..7201e0cc45d16 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/prompts/no_permission_prompt.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/prompts/no_permission_prompt.tsx @@ -19,6 +19,7 @@ export function NoPermissionPrompt() { }} > ({ + __esModule: true, + useKibana: jest.fn(() => mockUseKibanaReturnValue), +})); + +jest.mock('../../hooks/use_breadcrumbs', () => ({ + useBreadcrumbs: jest.fn(), +})); + +jest.mock('../../hooks/use_fetch_rules', () => ({ + useFetchRules: jest.fn(), +})); + +jest.mock('@kbn/triggers-actions-ui-plugin/public', () => ({ + useLoadRuleTypes: jest.fn(), +})); + +jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ + appMountParameters: {} as AppMountParameters, + config: { + unsafe: { + alertingExperience: { enabled: true }, + cases: { enabled: true }, + overviewNext: { enabled: false }, + rules: { enabled: true }, + }, + }, + observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), + ObservabilityPageTemplate: KibanaPageTemplate, + kibanaFeatures: [], +})); + +const { useFetchRules } = jest.requireMock('../../hooks/use_fetch_rules'); +const { useLoadRuleTypes } = jest.requireMock('@kbn/triggers-actions-ui-plugin/public'); + +describe('empty RulesPage', () => { + let wrapper: ReactWrapper; + async function setup() { + const rulesState: RuleState = { + isLoading: false, + data: [], + error: null, + totalItemCount: 0, + }; + + useLoadRuleTypes.mockReturnValue({ + ruleTypes: [ + { + 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', + }, + ], + }); + useFetchRules.mockReturnValue({ rulesState, noData: true }); + wrapper = mountWithIntl(); + } + it('renders empty screen', async () => { + await setup(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find(RulesTable).exists()).toBe(false); + expect(wrapper.find('[data-test-subj="createFirstRuleEmptyPrompt"]').exists()).toBeTruthy(); + }); + it('renders Create rule button', async () => { + await setup(); + expect(wrapper.find('EuiButton[data-test-subj="createFirstRuleButton"]')).toHaveLength(1); + }); + it('renders Documentation link', async () => { + await setup(); + expect(wrapper.find('EuiLink[data-test-subj="documentationLink"]')).toHaveLength(1); + expect( + wrapper.find('EuiLink[data-test-subj="documentationLink"]').getElement().props.href + ).toContain('create-alerts.html'); + }); +}); + +describe('empty RulesPage with show only capability', () => { + let wrapper: ReactWrapper; + async function setup() { + const rulesState: RuleState = { + isLoading: false, + data: [], + error: null, + totalItemCount: 0, + }; + const ruleTypes = [ + { + 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: false }, + }, + ruleTaskTimeout: '1m', + }, + ]; + useFetchRules.mockReturnValue({ rulesState, noData: true }); + useLoadRuleTypes.mockReturnValue({ ruleTypes }); + + wrapper = mountWithIntl(); + } + + it('renders no permission screen', async () => { + await setup(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="noPermissionPrompt"]').exists()).toBeTruthy(); + }); + + it('does not render no data screen', async () => { + await setup(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('[data-test-subj="createFirstRuleEmptyPrompt"]').exists()).toBeFalsy(); + }); +}); + +describe('RulesPage with items', () => { + let wrapper: ReactWrapper; + async function setup() { + const mockedRulesData: Rule[] = [ + { + id: '1', + name: 'test rule', + tags: ['tag1'], + enabled: true, + ruleTypeId: 'test_rule_type', + schedule: { interval: '1s' }, + actions: [], + params: { name: 'test rule type name' }, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + createdAt: new Date(), + updatedAt: new Date(), + consumer: 'alerts', + notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'active', + lastDuration: 500, + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + monitoring: { + execution: { + history: [ + { + success: true, + duration: 1000000, + timestamp: 1234567, + }, + { + success: true, + duration: 200000, + timestamp: 1234567, + }, + { + success: false, + duration: 300000, + timestamp: 1234567, + }, + ], + 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' }, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + createdAt: new Date(), + updatedAt: new Date(), + consumer: 'alerts', + notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'ok', + lastDuration: 61000, + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + error: undefined, + }, + monitoring: { + execution: { + history: [ + { + success: true, + duration: 100000, + timestamp: 1234567, + }, + { + success: true, + duration: 500000, + timestamp: 1234567, + }, + ], + 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' }, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + createdAt: new Date(), + updatedAt: new Date(), + consumer: 'alerts', + notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'pending', + lastDuration: 30234, + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + monitoring: { + execution: { + history: [{ success: false, duration: 100, timestamp: 1234567 }], + calculated_metrics: { + success_ratio: 0, + }, + }, + }, + }, + ]; + + const rulesState: RuleState = { + isLoading: false, + data: mockedRulesData, + error: null, + totalItemCount: 3, + }; + + const mockedRuleTypeIndex = new Map( + Object.entries({ + '1': { + enabledInLicense: true, + id: '1', + name: 'test rule', + }, + '2': { + enabledInLicense: true, + id: '2', + name: 'test rule ok', + }, + '3': { + enabledInLicense: true, + id: '3', + name: 'test rule pending', + }, + }) + ); + const ruleTypes = [ + { + 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: false }, + }, + ruleTaskTimeout: '1m', + }, + ]; + useLoadRuleTypes.mockReturnValue({ + ruleTypes, + ruleTypeIndex: mockedRuleTypeIndex, + }); + useFetchRules.mockReturnValue({ rulesState }); + wrapper = mountWithIntl(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + } + + it('renders table of rules', async () => { + await setup(); + expect(wrapper.find(RulesTable).exists()).toBe(true); + }); +}); + +describe('RulesPage with items and show only capability', () => { + let wrapper: ReactWrapper; + async function setup() { + const mockedRulesData: Rule[] = [ + { + id: '1', + name: 'test rule', + tags: ['tag1'], + enabled: true, + ruleTypeId: 'test_rule_type', + schedule: { interval: '1s' }, + actions: [], + params: { name: 'test rule type name' }, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + createdAt: new Date(), + updatedAt: new Date(), + consumer: 'alerts', + notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'active', + lastDuration: 500, + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + monitoring: { + execution: { + history: [ + { + success: true, + duration: 1000000, + timestamp: 1234567, + }, + { + success: true, + duration: 200000, + timestamp: 1234567, + }, + { + success: false, + duration: 300000, + timestamp: 1234567, + }, + ], + 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' }, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + createdAt: new Date(), + updatedAt: new Date(), + consumer: 'alerts', + notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'ok', + lastDuration: 61000, + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + monitoring: { + execution: { + history: [ + { + success: true, + duration: 100000, + timestamp: 1234567, + }, + { + success: true, + duration: 500000, + timestamp: 1234567, + }, + ], + 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' }, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + createdAt: new Date(), + updatedAt: new Date(), + consumer: 'alerts', + notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'pending', + lastDuration: 30234, + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + monitoring: { + execution: { + history: [{ success: false, duration: 100, timestamp: 1234567 }], + calculated_metrics: { + success_ratio: 0, + }, + }, + }, + }, + ]; + const rulesState: RuleState = { + isLoading: false, + data: mockedRulesData, + error: null, + totalItemCount: 3, + }; + useFetchRules.mockReturnValue({ rulesState }); + + const mockedRuleTypeIndex = new Map( + Object.entries({ + '1': { + enabledInLicense: true, + id: '1', + name: 'test rule', + }, + '2': { + enabledInLicense: true, + id: '2', + name: 'test rule ok', + }, + '3': { + enabledInLicense: true, + id: '3', + name: 'test rule pending', + }, + }) + ); + const ruleTypes = [ + { + 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: false }, + }, + ruleTaskTimeout: '1m', + }, + ]; + useLoadRuleTypes.mockReturnValue({ ruleTypes, ruleTypeIndex: mockedRuleTypeIndex }); + + wrapper = mountWithIntl(); + } + + it('does not render create rule button', async () => { + await setup(); + expect(wrapper.find('[data-test-subj="createRuleButton"]')).toHaveLength(0); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/rules/types.ts b/x-pack/plugins/observability/public/pages/rules/types.ts index c3e2d8248e2fe..cbcd97919cddc 100644 --- a/x-pack/plugins/observability/public/pages/rules/types.ts +++ b/x-pack/plugins/observability/public/pages/rules/types.ts @@ -85,3 +85,10 @@ export interface RulesTableProps { onSortChange: (changedSort: EuiTableSortingType['sort']) => void; isLoading: boolean; } + +export interface RuleState { + isLoading: boolean; + data: Rule[]; + error: string | null; + totalItemCount: number; +}