From 10a06de21d5db3f458c642b199639e63621535ca Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 10 Aug 2022 06:42:33 -0400 Subject: [PATCH] [Actionable Observability][BUG] Fix connectors doesn't appear in the Rule Details page after creating first connector (#133737) (#138484) * Move rule actions to the hook which is right call * Update naming * Fix when a connector is deleted from connector page in Stack Management * Fix tests * Update tests and mock loadAllActions API * Remove unused import * Call the connectors API only when the rule has actions (cherry picked from commit 44026a5e017635ab36b5393a1127c1ca737df163) Co-authored-by: Faisal Kanout --- .../hooks/use_fetch_rule_action_connectors.ts | 67 +++++++++++++++++++ .../public/hooks/use_fetch_rule_actions.ts | 51 -------------- .../rule_details/components/actions.test.tsx | 48 +++++++------ .../pages/rule_details/components/actions.tsx | 23 ++++--- .../public/pages/rule_details/types.ts | 3 +- 5 files changed, 109 insertions(+), 83 deletions(-) create mode 100644 x-pack/plugins/observability/public/hooks/use_fetch_rule_action_connectors.ts delete mode 100644 x-pack/plugins/observability/public/hooks/use_fetch_rule_actions.ts diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_rule_action_connectors.ts b/x-pack/plugins/observability/public/hooks/use_fetch_rule_action_connectors.ts new file mode 100644 index 00000000000000..cae7607f1d084c --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_fetch_rule_action_connectors.ts @@ -0,0 +1,67 @@ +/* + * 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 { useEffect, useState, useCallback } from 'react'; +import { ActionConnector, loadAllActions } from '@kbn/triggers-actions-ui-plugin/public'; +import { intersectionBy } from 'lodash'; +import { FetchRuleActionConnectorsProps } from '../pages/rule_details/types'; +import { ACTIONS_LOAD_ERROR } from '../pages/rule_details/translations'; + +interface FetchActionConnectors { + isLoadingActionConnectors: boolean; + actionConnectors: Array>>; + errorActionConnectors?: string; +} + +export function useFetchRuleActionConnectors({ + http, + ruleActions, +}: FetchRuleActionConnectorsProps) { + const [actionConnectors, setActionConnector] = useState({ + isLoadingActionConnectors: true, + actionConnectors: [] as Array>>, + errorActionConnectors: undefined, + }); + + const fetchRuleActionConnectors = useCallback(async () => { + try { + if (!ruleActions || ruleActions.length <= 0) { + setActionConnector((oldState: FetchActionConnectors) => ({ + ...oldState, + isLoadingActionConnectors: false, + actionConnectors: [], + })); + return; + } + const allActions = await loadAllActions({ + http, + }); + const actions = intersectionBy(allActions, ruleActions, 'actionTypeId'); + setActionConnector((oldState: FetchActionConnectors) => ({ + ...oldState, + isLoadingActionConnectors: false, + actionConnectors: actions, + })); + } catch (error) { + setActionConnector((oldState: FetchActionConnectors) => ({ + ...oldState, + isLoadingActionConnectors: false, + errorActionConnectors: ACTIONS_LOAD_ERROR( + error instanceof Error ? error.message : typeof error === 'string' ? error : '' + ), + })); + } + }, [http, ruleActions]); + useEffect(() => { + fetchRuleActionConnectors(); + }, [fetchRuleActionConnectors]); + + return { + ...actionConnectors, + reloadRuleActionConnectors: fetchRuleActionConnectors, + }; +} diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_rule_actions.ts b/x-pack/plugins/observability/public/hooks/use_fetch_rule_actions.ts deleted file mode 100644 index eaf01ed5ba59d4..00000000000000 --- a/x-pack/plugins/observability/public/hooks/use_fetch_rule_actions.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { useEffect, useState, useCallback } from 'react'; -import { ActionConnector, loadAllActions } from '@kbn/triggers-actions-ui-plugin/public'; -import { FetchRuleActionsProps } from '../pages/rule_details/types'; -import { ACTIONS_LOAD_ERROR } from '../pages/rule_details/translations'; - -interface FetchActions { - isLoadingActions: boolean; - allActions: Array>>; - errorActions: string | undefined; -} - -export function useFetchRuleActions({ http }: FetchRuleActionsProps) { - const [ruleActions, setRuleActions] = useState({ - isLoadingActions: true, - allActions: [] as Array>>, - errorActions: undefined, - }); - - const fetchRuleActions = useCallback(async () => { - try { - const response = await loadAllActions({ - http, - }); - setRuleActions((oldState: FetchActions) => ({ - ...oldState, - isLoadingActions: false, - allActions: response, - })); - } catch (error) { - setRuleActions((oldState: FetchActions) => ({ - ...oldState, - isLoadingActions: false, - errorActions: ACTIONS_LOAD_ERROR( - error instanceof Error ? error.message : typeof error === 'string' ? error : '' - ), - })); - } - }, [http]); - useEffect(() => { - fetchRuleActions(); - }, [fetchRuleActions]); - - return { ...ruleActions, reloadRuleActions: fetchRuleActions }; -} diff --git a/x-pack/plugins/observability/public/pages/rule_details/components/actions.test.tsx b/x-pack/plugins/observability/public/pages/rule_details/components/actions.test.tsx index 9000d9dbf5f99e..707c1204d1564f 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/components/actions.test.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/components/actions.test.tsx @@ -5,7 +5,9 @@ * 2.0. */ import React from 'react'; -import { ReactWrapper, mount } from 'enzyme'; +import { mount } from 'enzyme'; +import { nextTick } from '@kbn/test-jest-helpers'; +import { act } from 'react-dom/test-utils'; import { Actions } from './actions'; import { observabilityPublicPluginsStartMock } from '../../../observability_public_plugins_start.mock'; import { kibanaStartMock } from '../../../utils/kibana_react.mock'; @@ -17,14 +19,11 @@ jest.mock('../../../utils/kibana_react', () => ({ useKibana: jest.fn(() => mockUseKibanaReturnValue), })); -jest.mock('../../../hooks/use_fetch_rule_actions', () => ({ - useFetchRuleActions: jest.fn(), +jest.mock('@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api', () => ({ + loadAllActions: jest.fn(), })); -const { useFetchRuleActions } = jest.requireMock('../../../hooks/use_fetch_rule_actions'); - describe('Actions', () => { - let wrapper: ReactWrapper; async function setup() { const ruleActions = [ { @@ -38,26 +37,28 @@ describe('Actions', () => { actionTypeId: '.slack', }, ]; - const allActions = [ + const { loadAllActions } = jest.requireMock( + '@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api' + ); + loadAllActions.mockResolvedValueOnce([ { - id: 1, - name: 'Server log', + id: 'a0d2f6c0-e682-11ec-843b-213c67313f8c', + name: 'Email', + config: {}, + actionTypeId: '.email', + }, + { + id: 'f57cabc0-e660-11ec-8241-7deb55b17f15', + name: 'logs', + config: {}, actionTypeId: '.server-log', }, { - id: 2, + id: '05b7ab30-e683-11ec-843b-213c67313f8c', name: 'Slack', actionTypeId: '.slack', }, - { - id: 3, - name: 'Email', - actionTypeId: '.email', - }, - ]; - useFetchRuleActions.mockReturnValue({ - allActions, - }); + ]); const actionTypeRegistryMock = observabilityPublicPluginsStartMock.createStart().triggersActionsUi.actionTypeRegistry; @@ -67,13 +68,18 @@ describe('Actions', () => { { id: '.email', iconClass: 'email' }, { id: '.index', iconClass: 'indexOpen' }, ]); - wrapper = mount( + const wrapper = mount( ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + return wrapper; } it("renders action connector icons for user's selected rule actions", async () => { - await setup(); + const wrapper = await setup(); wrapper.debug(); expect(wrapper.find('[data-euiicon-type]').length).toBe(2); expect(wrapper.find('[data-euiicon-type="logsApp"]').length).toBe(1); diff --git a/x-pack/plugins/observability/public/pages/rule_details/components/actions.tsx b/x-pack/plugins/observability/public/pages/rule_details/components/actions.tsx index d3dbe3cf4bdef6..7feb8c8d271863 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/components/actions.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/components/actions.tsx @@ -14,11 +14,10 @@ import { IconType, EuiLoadingSpinner, } from '@elastic/eui'; -import { intersectionBy } from 'lodash'; import { suspendedComponentWithProps } from '@kbn/triggers-actions-ui-plugin/public'; import { i18n } from '@kbn/i18n'; import { ActionsProps } from '../types'; -import { useFetchRuleActions } from '../../../hooks/use_fetch_rule_actions'; +import { useFetchRuleActionConnectors } from '../../../hooks/use_fetch_rule_action_connectors'; import { useKibana } from '../../../utils/kibana_react'; export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) { @@ -26,13 +25,18 @@ export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) { http, notifications: { toasts }, } = useKibana().services; - const { isLoadingActions, allActions, errorActions } = useFetchRuleActions({ http }); + const { isLoadingActionConnectors, actionConnectors, errorActionConnectors } = + useFetchRuleActionConnectors({ + http, + ruleActions, + }); useEffect(() => { - if (errorActions) { - toasts.addDanger({ title: errorActions }); + if (errorActionConnectors) { + toasts.addDanger({ title: errorActionConnectors }); } - }, [errorActions, toasts]); - if (ruleActions && ruleActions.length <= 0) + }, [errorActionConnectors, toasts]); + + if (!actionConnectors || actionConnectors.length <= 0) return ( @@ -49,11 +53,10 @@ export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) { ? actionGroup?.iconClass : suspendedComponentWithProps(actionGroup?.iconClass as React.ComponentType); } - const actions = intersectionBy(allActions, ruleActions, 'actionTypeId'); - if (isLoadingActions) return ; + if (isLoadingActionConnectors) return ; return ( - {actions.map(({ actionTypeId, name }) => ( + {actionConnectors.map(({ actionTypeId, name }) => ( diff --git a/x-pack/plugins/observability/public/pages/rule_details/types.ts b/x-pack/plugins/observability/public/pages/rule_details/types.ts index 4b1c62f7dbb9a3..7906e6c8af29ae 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/types.ts +++ b/x-pack/plugins/observability/public/pages/rule_details/types.ts @@ -36,8 +36,9 @@ export interface FetchRuleSummaryProps { ruleId: string; http: HttpSetup; } -export interface FetchRuleActionsProps { +export interface FetchRuleActionConnectorsProps { http: HttpSetup; + ruleActions: any[]; } export interface FetchRuleExecutionLogProps {