From f69edd435d81b641d5437a83dba98d0a3be00ce8 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Wed, 30 Sep 2020 22:58:53 +0100 Subject: [PATCH] [Actions] Adds a "Test Connector" button on the Connectors List to make discovery of the Test tab easier (#78746) (#79008) Adds a "Test Connector" button on the Connectors List to make discovery of the Test tab easier. It also adds a "Save & test" button when creating a Connector, which navigates the user straight to the Test tab after saving. This also fixed a broken behaviour on Main where navigating to the Test tab in Case Management crashes Kibana. --- .../email/email_params.test.tsx | 4 + .../es_index/es_index_params.test.tsx | 4 + .../jira/jira_params.test.tsx | 22 ++- .../builtin_action_types/jira/jira_params.tsx | 4 +- .../pagerduty/pagerduty_params.test.tsx | 4 + .../resilient/resilient_params.test.tsx | 18 ++- .../resilient/resilient_params.tsx | 4 +- .../server_log/server_log_params.test.tsx | 7 + .../servicenow/servicenow_params.test.tsx | 4 + .../slack/slack_params.test.tsx | 4 + .../webhook/webhook_params.test.tsx | 4 + .../context/actions_connectors_context.tsx | 11 +- .../action_connector_form/action_form.tsx | 9 +- .../connector_add_flyout.tsx | 90 ++++++++---- .../connector_edit_flyout.scss | 2 +- .../connector_edit_flyout.tsx | 23 ++- .../test_connector_form.tsx | 4 +- .../components/actions_connectors_list.tsx | 137 ++++++++++++------ .../triggers_actions_ui/public/types.ts | 4 +- 19 files changed, 244 insertions(+), 115 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx index 8c37dc940a238..ed7b9a19d9541 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx @@ -6,10 +6,12 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DocLinksStart } from 'kibana/public'; +import { coreMock } from 'src/core/public/mocks'; import EmailParamsFields from './email_params'; describe('EmailParamsFields renders', () => { test('all params fields is rendered', () => { + const mocks = coreMock.createSetup(); const actionParams = { cc: [], bcc: [], @@ -25,6 +27,8 @@ describe('EmailParamsFields renders', () => { editAction={() => {}} index={0} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx index a882e3bc43f34..0e4215e503275 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx @@ -7,9 +7,11 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import ParamsFields from './es_index_params'; import { DocLinksStart } from 'kibana/public'; +import { coreMock } from 'src/core/public/mocks'; describe('IndexParamsFields renders', () => { test('all params fields is rendered', () => { + const mocks = coreMock.createSetup(); const actionParams = { documents: [{ test: 123 }], }; @@ -21,6 +23,8 @@ describe('IndexParamsFields renders', () => { editAction={() => {}} index={0} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} /> ); expect(wrapper.find('[data-test-subj="documentsJsonEditor"]').first().prop('value')).toBe(`{ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx index 26d358310741c..d96657f8ca407 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx @@ -7,20 +7,16 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import JiraParamsFields from './jira_params'; import { DocLinksStart } from 'kibana/public'; +import { coreMock } from 'src/core/public/mocks'; import { useGetIssueTypes } from './use_get_issue_types'; import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type'; -jest.mock('../../../app_context', () => { - const post = jest.fn(); - return { - useAppDependencies: jest.fn(() => ({ http: { post } })), - }; -}); - jest.mock('./use_get_issue_types'); jest.mock('./use_get_fields_by_issue_type'); +const mocks = coreMock.createSetup(); + const useGetIssueTypesMock = useGetIssueTypes as jest.Mock; const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; @@ -93,6 +89,8 @@ describe('JiraParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -118,6 +116,8 @@ describe('JiraParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -141,6 +141,8 @@ describe('JiraParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -164,6 +166,8 @@ describe('JiraParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -191,6 +195,8 @@ describe('JiraParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -218,6 +224,8 @@ describe('JiraParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx index bde3d67ffd65f..b457dcc60a43f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx @@ -13,7 +13,6 @@ import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; -import { useAppDependencies } from '../../../app_context'; import { ActionParamsProps } from '../../../../types'; import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; @@ -28,6 +27,8 @@ const JiraParamsFields: React.FunctionComponent { const { title, description, comments, issueType, priority, labels, savedObjectId } = actionParams.subActionParams || {}; @@ -35,7 +36,6 @@ const JiraParamsFields: React.FunctionComponent([]); const [firstLoad, setFirstLoad] = useState(false); const [prioritiesSelectOptions, setPrioritiesSelectOptions] = useState([]); - const { http, toastNotifications } = useAppDependencies(); useEffect(() => { setFirstLoad(true); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx index fe83054edbe07..ea947a159b893 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx @@ -8,9 +8,11 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { EventActionOptions, SeverityActionOptions } from '.././types'; import PagerDutyParamsFields from './pagerduty_params'; import { DocLinksStart } from 'kibana/public'; +import { coreMock } from 'src/core/public/mocks'; describe('PagerDutyParamsFields renders', () => { test('all params fields is rendered', () => { + const mocks = coreMock.createSetup(); const actionParams = { eventAction: EventActionOptions.TRIGGER, dedupKey: 'test', @@ -30,6 +32,8 @@ describe('PagerDutyParamsFields renders', () => { editAction={() => {}} index={0} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} /> ); expect(wrapper.find('[data-test-subj="severitySelect"]').length > 0).toBeTruthy(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx index 17020805757f9..5f03a548bf16e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx @@ -10,13 +10,9 @@ import { DocLinksStart } from 'kibana/public'; import { useGetIncidentTypes } from './use_get_incident_types'; import { useGetSeverity } from './use_get_severity'; +import { coreMock } from 'src/core/public/mocks'; -jest.mock('../../../app_context', () => { - const post = jest.fn(); - return { - useAppDependencies: jest.fn(() => ({ http: { post } })), - }; -}); +const mocks = coreMock.createSetup(); jest.mock('./use_get_incident_types'); jest.mock('./use_get_severity'); @@ -92,6 +88,8 @@ describe('ResilientParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -114,6 +112,8 @@ describe('ResilientParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -137,6 +137,8 @@ describe('ResilientParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -157,6 +159,8 @@ describe('ResilientParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); @@ -180,6 +184,8 @@ describe('ResilientParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} actionConnector={connector} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx index 4b157c6999985..b150c97506b69 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx @@ -17,7 +17,6 @@ import { import { i18n } from '@kbn/i18n'; import { ActionParamsProps } from '../../../../types'; -import { useAppDependencies } from '../../../app_context'; import { ResilientActionParams } from './types'; import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; @@ -32,9 +31,10 @@ const ResilientParamsFields: React.FunctionComponent { const [firstLoad, setFirstLoad] = useState(false); - const { http, toastNotifications } = useAppDependencies(); const { title, description, comments, incidentTypes, severityCode, savedObjectId } = actionParams.subActionParams || {}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx index 3a015cddcd335..407cd70d4ad4c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx @@ -8,8 +8,11 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ServerLogLevelOptions } from '.././types'; import ServerLogParamsFields from './server_log_params'; import { DocLinksStart } from 'kibana/public'; +import { coreMock } from 'src/core/public/mocks'; describe('ServerLogParamsFields renders', () => { + const mocks = coreMock.createSetup(); + test('all params fields is rendered', () => { const actionParams = { level: ServerLogLevelOptions.TRACE, @@ -23,6 +26,8 @@ describe('ServerLogParamsFields renders', () => { index={0} defaultMessage={'test default message'} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} /> ); expect(wrapper.find('[data-test-subj="loggingLevelSelect"]').length > 0).toBeTruthy(); @@ -44,6 +49,8 @@ describe('ServerLogParamsFields renders', () => { editAction={() => {}} index={0} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} /> ); expect(wrapper.find('[data-test-subj="loggingLevelSelect"]').length > 0).toBeTruthy(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.test.tsx index f4d831d7234e7..cc8041b38c360 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.test.tsx @@ -7,9 +7,11 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import ServiceNowParamsFields from './servicenow_params'; import { DocLinksStart } from 'kibana/public'; +import { coreMock } from 'src/core/public/mocks'; describe('ServiceNowParamsFields renders', () => { test('all params fields is rendered', () => { + const mocks = coreMock.createSetup(); const actionParams = { subAction: 'pushToService', subActionParams: { @@ -32,6 +34,8 @@ describe('ServiceNowParamsFields renders', () => { index={0} messageVariables={[]} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} /> ); expect(wrapper.find('[data-test-subj="urgencySelect"]').length > 0).toBeTruthy(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx index 45c1929ae1e22..c580424c05b95 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx @@ -7,9 +7,11 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import SlackParamsFields from './slack_params'; import { DocLinksStart } from 'kibana/public'; +import { coreMock } from 'src/core/public/mocks'; describe('SlackParamsFields renders', () => { test('all params fields is rendered', () => { + const mocks = coreMock.createSetup(); const actionParams = { message: 'test message', }; @@ -21,6 +23,8 @@ describe('SlackParamsFields renders', () => { editAction={() => {}} index={0} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} /> ); expect(wrapper.find('[data-test-subj="messageTextArea"]').length > 0).toBeTruthy(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx index 6a1c5cb2bfb53..a4cb36cab76b6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx @@ -7,9 +7,11 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import WebhookParamsFields from './webhook_params'; import { DocLinksStart } from 'kibana/public'; +import { coreMock } from 'src/core/public/mocks'; describe('WebhookParamsFields renders', () => { test('all params fields is rendered', () => { + const mocks = coreMock.createSetup(); const actionParams = { body: 'test message', }; @@ -21,6 +23,8 @@ describe('WebhookParamsFields renders', () => { editAction={() => {}} index={0} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + toastNotifications={mocks.notifications.toasts} + http={mocks.http} /> ); expect(wrapper.find('[data-test-subj="bodyJsonEditor"]').length > 0).toBeTruthy(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx index d78930344a673..786fc12380f90 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx @@ -5,19 +5,16 @@ */ import React, { createContext, useContext } from 'react'; -import { HttpSetup, ToastsApi, ApplicationStart, DocLinksStart } from 'kibana/public'; -import { ActionTypeModel } from '../../types'; +import { HttpSetup, ApplicationStart, DocLinksStart, ToastsSetup } from 'kibana/public'; +import { ActionTypeModel, ActionConnector } from '../../types'; import { TypeRegistry } from '../type_registry'; export interface ActionsConnectorsContextValue { http: HttpSetup; actionTypeRegistry: TypeRegistry; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: ToastsSetup; capabilities: ApplicationStart['capabilities']; - reloadConnectors?: () => Promise; + reloadConnectors?: () => Promise; docLinks: DocLinksStart; consumer?: string; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index ac5b2a2187c2f..1b176e0f63dbd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -29,7 +29,7 @@ import { EuiText, EuiLoadingSpinner, } from '@elastic/eui'; -import { HttpSetup, ToastsApi, ApplicationStart, DocLinksStart } from 'kibana/public'; +import { HttpSetup, ToastsSetup, ApplicationStart, DocLinksStart } from 'kibana/public'; import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/action_connector_api'; import { IErrorObject, @@ -56,10 +56,7 @@ interface ActionAccordionFormProps { setActionParamsProperty: (key: string, value: any, index: number) => void; http: HttpSetup; actionTypeRegistry: TypeRegistry; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + toastNotifications: ToastsSetup; docLinks: DocLinksStart; actionTypes?: ActionType[]; messageVariables?: ActionVariable[]; @@ -311,6 +308,8 @@ export const ActionForm = ({ messageVariables={messageVariables} defaultMessage={defaultActionMessage ?? undefined} docLinks={docLinks} + http={http} + toastNotifications={toastNotifications} actionConnector={actionConnector} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index 19ce653e465f1..9bb9d07307e13 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -37,12 +37,14 @@ export interface ConnectorAddFlyoutProps { addFlyoutVisible: boolean; setAddFlyoutVisibility: React.Dispatch>; actionTypes?: ActionType[]; + onTestConnector?: (connector: ActionConnector) => void; } export const ConnectorAddFlyout = ({ addFlyoutVisible, setAddFlyoutVisibility, actionTypes, + onTestConnector, }: ConnectorAddFlyoutProps) => { let hasErrors = false; const { @@ -153,6 +155,19 @@ export const ConnectorAddFlyout = ({ return undefined; }); + const onSaveClicked = async () => { + setIsSaving(true); + const savedAction = await onActionConnectorSave(); + setIsSaving(false); + if (savedAction) { + closeFlyout(); + if (reloadConnectors) { + await reloadConnectors(); + } + } + return savedAction; + }; + return ( @@ -245,35 +260,52 @@ export const ConnectorAddFlyout = ({ )} - {canSave && actionTypeModel && actionType ? ( - - { - setIsSaving(true); - const savedAction = await onActionConnectorSave(); - setIsSaving(false); - if (savedAction) { - closeFlyout(); - if (reloadConnectors) { - reloadConnectors(); - } - } - }} - > - - - - ) : null} + + + {canSave && actionTypeModel && actionType ? ( + + {onTestConnector && ( + + { + const savedConnector = await onSaveClicked(); + if (savedConnector) { + onTestConnector(savedConnector); + } + }} + > + + + + )} + + + + + + + ) : null} + + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss index 873a3ceb762cd..5d2997d101255 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss @@ -1,3 +1,3 @@ .connectorEditFlyoutTabs { - margin-bottom: '-25px'; + margin-bottom: -$euiSizeL; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index fc902a4fabcd8..7b985ab85cd4e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -26,7 +26,7 @@ import { i18n } from '@kbn/i18n'; import { Option, none, some } from 'fp-ts/lib/Option'; import { ActionConnectorForm, validateBaseProperties } from './action_connector_form'; import { TestConnectorForm } from './test_connector_form'; -import { ActionConnectorTableItem, ActionConnector, IErrorObject } from '../../../types'; +import { ActionConnector, IErrorObject } from '../../../types'; import { connectorReducer } from './connector_reducer'; import { updateActionConnector, executeAction } from '../../lib/action_connector_api'; import { hasSaveActionsCapability } from '../../lib/capabilities'; @@ -36,15 +36,22 @@ import { ActionTypeExecutorResult } from '../../../../../actions/common'; import './connector_edit_flyout.scss'; export interface ConnectorEditProps { - initialConnector: ActionConnectorTableItem; + initialConnector: ActionConnector; editFlyoutVisible: boolean; setEditFlyoutVisibility: React.Dispatch>; + tab?: EditConectorTabs; +} + +export enum EditConectorTabs { + Configuration = 'configuration', + Test = 'test', } export const ConnectorEditFlyout = ({ initialConnector, editFlyoutVisible, setEditFlyoutVisibility, + tab = EditConectorTabs.Configuration, }: ConnectorEditProps) => { const { http, @@ -61,7 +68,7 @@ export const ConnectorEditFlyout = ({ connector: { ...initialConnector, secrets: {} }, }); const [isSaving, setIsSaving] = useState(false); - const [selectedTab, setTab] = useState<'config' | 'test'>('config'); + const [selectedTab, setTab] = useState(tab); const [hasChanges, setHasChanges] = useState(false); const setConnector = (key: string, value: any) => { @@ -232,18 +239,18 @@ export const ConnectorEditFlyout = ({ setTab('config')} + onClick={() => setTab(EditConectorTabs.Configuration)} data-test-subj="configureConnectorTab" - isSelected={'config' === selectedTab} + isSelected={EditConectorTabs.Configuration === selectedTab} > {i18n.translate('xpack.triggersActionsUI.sections.editConnectorForm.tabText', { defaultMessage: 'Configuration', })} setTab('test')} + onClick={() => setTab(EditConectorTabs.Test)} data-test-subj="testConnectorTab" - isSelected={'test' === selectedTab} + isSelected={EditConectorTabs.Test === selectedTab} > {i18n.translate('xpack.triggersActionsUI.sections.testConnectorForm.tabText', { defaultMessage: 'Test', @@ -252,7 +259,7 @@ export const ConnectorEditFlyout = ({ - {selectedTab === 'config' ? ( + {selectedTab === EditConectorTabs.Configuration ? ( !connector.isPreconfigured ? ( { - const { actionTypeRegistry, docLinks } = useActionsConnectorsContext(); + const { actionTypeRegistry, docLinks, http, toastNotifications } = useActionsConnectorsContext(); const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId); const ParamsFieldsComponent = actionTypeModel.actionParamsFields; @@ -74,6 +74,8 @@ export const TestConnectorForm = ({ } messageVariables={[]} docLinks={docLinks} + http={http} + toastNotifications={toastNotifications} actionConnector={connector} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 6bc9fd8e7e5a8..da833c3495b4a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -24,9 +24,14 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useAppDependencies } from '../../../app_context'; import { loadAllActions, loadActionTypes, deleteActions } from '../../../lib/action_connector_api'; import ConnectorAddFlyout from '../../action_connector_form/connector_add_flyout'; -import ConnectorEditFlyout from '../../action_connector_form/connector_edit_flyout'; - -import { hasDeleteActionsCapability, hasSaveActionsCapability } from '../../../lib/capabilities'; +import ConnectorEditFlyout, { + EditConectorTabs, +} from '../../action_connector_form/connector_edit_flyout'; +import { + hasDeleteActionsCapability, + hasSaveActionsCapability, + hasExecuteActionsCapability, +} from '../../../lib/capabilities'; import { DeleteModalConfirmation } from '../../../components/delete_modal_confirmation'; import { ActionsConnectorsContextProvider } from '../../../context/actions_connectors_context'; import { checkActionTypeEnabled } from '../../../lib/check_action_type_enabled'; @@ -43,22 +48,20 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { docLinks, } = useAppDependencies(); const canDelete = hasDeleteActionsCapability(capabilities); + const canExecute = hasExecuteActionsCapability(capabilities); const canSave = hasSaveActionsCapability(capabilities); const [actionTypesIndex, setActionTypesIndex] = useState(undefined); const [actions, setActions] = useState([]); - const [data, setData] = useState([]); const [selectedItems, setSelectedItems] = useState([]); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); const [isLoadingActions, setIsLoadingActions] = useState(false); const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); const [addFlyoutVisible, setAddFlyoutVisibility] = useState(false); - const [actionTypesList, setActionTypesList] = useState>( - [] - ); - const [editedConnectorItem, setEditedConnectorItem] = useState< - ActionConnectorTableItem | undefined - >(undefined); + const [editConnectorProps, setEditConnectorProps] = useState<{ + initialConnector?: ActionConnector; + tab?: EditConectorTabs; + }>({}); const [connectorsToDelete, setConnectorsToDelete] = useState([]); useEffect(() => { @@ -90,30 +93,25 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - // Avoid flickering before action types load - if (typeof actionTypesIndex === 'undefined') { - return; - } - // Update the data for the table - const updatedData = actions.map((action) => { - return { - ...action, - actionType: actionTypesIndex[action.actionTypeId] - ? actionTypesIndex[action.actionTypeId].name - : action.actionTypeId, - }; - }); - setData(updatedData); - // Update the action types list for the filter - const actionTypes = Object.values(actionTypesIndex) - .map((actionType) => ({ - value: actionType.id, - name: `${actionType.name} (${getActionsCountByActionType(actions, actionType.id)})`, - })) - .sort((a, b) => a.name.localeCompare(b.name)); - setActionTypesList(actionTypes); - }, [actions, actionTypesIndex]); + const actionConnectorTableItems: ActionConnectorTableItem[] = actionTypesIndex + ? actions.map((action) => { + return { + ...action, + actionType: actionTypesIndex[action.actionTypeId] + ? actionTypesIndex[action.actionTypeId].name + : action.actionTypeId, + }; + }) + : []; + + const actionTypesList: Array<{ value: string; name: string }> = actionTypesIndex + ? Object.values(actionTypesIndex) + .map((actionType) => ({ + value: actionType.id, + name: `${actionType.name} (${getActionsCountByActionType(actions, actionType.id)})`, + })) + .sort((a, b) => a.name.localeCompare(b.name)) + : []; async function loadActions() { setIsLoadingActions(true); @@ -134,8 +132,8 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { } } - async function editItem(connectorTableItem: ActionConnectorTableItem) { - setEditedConnectorItem(connectorTableItem); + async function editItem(actionConnector: ActionConnector, tab: EditConectorTabs) { + setEditConnectorProps({ initialConnector: actionConnector, tab }); setEditFlyoutVisibility(true); } @@ -159,7 +157,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { const link = ( editItem(item)} + onClick={() => editItem(item, EditConectorTabs.Configuration)} key={item.id} disabled={actionTypesIndex ? !actionTypesIndex[item.actionTypeId].enabled : true} > @@ -203,6 +201,11 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { item={item} onDelete={() => setConnectorsToDelete([item.id])} /> + editItem(item, EditConectorTabs.Test)} + /> ); }, @@ -212,7 +215,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { const table = ( { )} - {data.length !== 0 && table} - {data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && ( - setAddFlyoutVisibility(true)} /> - )} - {data.length === 0 && !canSave && } + {actionConnectorTableItems.length !== 0 && table} + {actionConnectorTableItems.length === 0 && + canSave && + !isLoadingActions && + !isLoadingActionTypes && ( + setAddFlyoutVisibility(true)} /> + )} + {actionConnectorTableItems.length === 0 && !canSave && } { editItem(connector, EditConectorTabs.Test)} /> - {editedConnectorItem ? ( + {editConnectorProps.initialConnector ? ( @@ -433,6 +443,41 @@ const DeleteOperation: React.FunctionComponent<{ ); }; +const RunOperation: React.FunctionComponent<{ + item: ActionConnectorTableItem; + canExecute: boolean; + onRun: () => void; +}> = ({ item, canExecute, onRun }) => { + return ( + + + + + + ); +}; + const NoPermissionPrompt: React.FunctionComponent<{}> = () => ( { messageVariables?: ActionVariable[]; defaultMessage?: string; docLinks: DocLinksStart; + http: HttpSetup; + toastNotifications: ToastsSetup; actionConnector?: ActionConnector; }