Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added possibility to embed connectors create and edit flyouts #58514

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
*/

import React, { createContext, useContext } from 'react';
import { ActionType } from '../../types';
import { HttpSetup, ToastsApi, ApplicationStart } from 'kibana/public';
import { ActionTypeModel } from '../../types';
import { TypeRegistry } from '../type_registry';

export interface ActionsConnectorsContextValue {
addFlyoutVisible: boolean;
editFlyoutVisible: boolean;
setEditFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
actionTypesIndex: Record<string, ActionType> | undefined;
reloadConnectors: () => Promise<void>;
http: HttpSetup;
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
toastNotifications?: Pick<
YulNaumenko marked this conversation as resolved.
Show resolved Hide resolved
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
capabilities: ApplicationStart['capabilities'];
reloadConnectors?: () => Promise<void>;
}

const ActionsConnectorsContext = createContext<ActionsConnectorsContextValue>(null as any);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,11 @@ describe('connector_add_flyout', () => {
const mockes = coreMock.createSetup();
const [
{
chrome,
docLinks,
application: { capabilities },
},
] = await mockes.getStartServices();
deps = {
chrome,
docLinks,
toastNotifications: mockes.notifications.toasts,
injectedMetadata: mockes.injectedMetadata,
http: mockes.http,
uiSettings: mockes.uiSettings,
capabilities: {
...capabilities,
actions: {
Expand All @@ -39,11 +32,7 @@ describe('connector_add_flyout', () => {
show: true,
},
},
legacy: {
MANAGEMENT_BREADCRUMB: { set: () => {} } as any,
},
actionTypeRegistry: actionTypeRegistry as any,
alertTypeRegistry: {} as any,
};
});

Expand All @@ -68,27 +57,27 @@ describe('connector_add_flyout', () => {
const wrapper = mountWithIntl(
<ActionsConnectorsContextProvider
value={{
addFlyoutVisible: true,
setAddFlyoutVisibility: state => {},
editFlyoutVisible: false,
setEditFlyoutVisibility: state => {},
actionTypesIndex: {
'first-action-type': { id: 'first-action-type', name: 'first', enabled: true },
'second-action-type': { id: 'second-action-type', name: 'second', enabled: true },
},
http: deps!.http,
actionTypeRegistry: deps!.actionTypeRegistry,
capabilities: deps!.capabilities,
reloadConnectors: () => {
return new Promise<void>(() => {});
},
}}
>
<ActionTypeMenu
onActionTypeChange={onActionTypeChange}
actionTypeRegistry={deps.actionTypeRegistry}
actionTypes={[
{
id: actionType.id,
enabled: true,
name: 'Test',
},
]}
/>
</ActionsConnectorsContextProvider>
);

expect(wrapper.find('[data-test-subj="first-action-type-card"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="second-action-type-card"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="my-action-type-card"]').exists()).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,46 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { EuiFlexItem, EuiCard, EuiIcon, EuiFlexGrid } from '@elastic/eui';
import { ActionType, ActionTypeModel } from '../../../types';
import { i18n } from '@kbn/i18n';
import { ActionType, ActionTypeIndex } from '../../../types';
import { loadActionTypes } from '../../lib/action_connector_api';
import { useActionsConnectorsContext } from '../../context/actions_connectors_context';
import { TypeRegistry } from '../../type_registry';

interface Props {
onActionTypeChange: (actionType: ActionType) => void;
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
actionTypes?: ActionType[];
}

export const ActionTypeMenu = ({ onActionTypeChange, actionTypeRegistry }: Props) => {
const { actionTypesIndex } = useActionsConnectorsContext();
if (!actionTypesIndex) {
return null;
}
export const ActionTypeMenu = ({ onActionTypeChange, actionTypes }: Props) => {
const { http, toastNotifications, actionTypeRegistry } = useActionsConnectorsContext();
const [actionTypesIndex, setActionTypesIndex] = useState<ActionTypeIndex | undefined>(undefined);

const actionTypes = Object.entries(actionTypesIndex)
useEffect(() => {
(async () => {
try {
const availableActionTypes = actionTypes ?? (await loadActionTypes({ http }));
const index: ActionTypeIndex = {};
for (const actionTypeItem of availableActionTypes) {
index[actionTypeItem.id] = actionTypeItem;
}
setActionTypesIndex(index);
} catch (e) {
if (toastNotifications) {
toastNotifications.addDanger({
title: i18n.translate(
'xpack.triggersActionsUI.sections.actionsConnectorsList.unableToLoadActionTypesMessage',
{ defaultMessage: 'Unable to load action types' }
),
});
}
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const registeredActionTypes = Object.entries(actionTypesIndex ?? [])
.filter(([index]) => actionTypeRegistry.has(index))
.map(([index, actionType]) => {
const actionTypeModel = actionTypeRegistry.get(index);
Expand All @@ -33,7 +55,7 @@ export const ActionTypeMenu = ({ onActionTypeChange, actionTypeRegistry }: Props
};
});

const cardNodes = actionTypes
const cardNodes = registeredActionTypes
.sort((a, b) => a.name.localeCompare(b.name))
.map((item, index) => {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,22 @@ import { ActionsConnectorsContextProvider } from '../../context/actions_connecto
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
import { ValidationResult } from '../../../types';
import { AppContextProvider } from '../../app_context';
import { AppDeps } from '../../app';
import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks';
import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';

const actionTypeRegistry = actionTypeRegistryMock.create();

describe('connector_add_flyout', () => {
let deps: AppDeps | null;
let deps: any;
YulNaumenko marked this conversation as resolved.
Show resolved Hide resolved

beforeAll(async () => {
const mocks = coreMock.createSetup();
const [
{
chrome,
docLinks,
application: { capabilities },
},
] = await mocks.getStartServices();
deps = {
chrome,
docLinks,
dataPlugin: dataPluginMock.createStartContract(),
charts: chartPluginMock.createStartContract(),
toastNotifications: mocks.notifications.toasts,
injectedMetadata: mocks.injectedMetadata,
http: mocks.http,
uiSettings: mocks.uiSettings,
capabilities: {
...capabilities,
actions: {
Expand All @@ -46,7 +35,6 @@ describe('connector_add_flyout', () => {
show: true,
},
},
setBreadcrumbs: jest.fn(),
actionTypeRegistry: actionTypeRegistry as any,
alertTypeRegistry: {} as any,
};
Expand Down Expand Up @@ -74,19 +62,25 @@ describe('connector_add_flyout', () => {
<AppContextProvider appDeps={deps}>
<ActionsConnectorsContextProvider
value={{
addFlyoutVisible: true,
setAddFlyoutVisibility: state => {},
editFlyoutVisible: false,
setEditFlyoutVisibility: state => {},
actionTypesIndex: {
'my-action-type': { id: 'my-action-type', name: 'test', enabled: true },
},
http: deps!.http,
actionTypeRegistry: deps!.actionTypeRegistry,
capabilities: deps!.capabilities,
reloadConnectors: () => {
return new Promise<void>(() => {});
},
}}
>
<ConnectorAddFlyout />
<ConnectorAddFlyout
addFlyoutVisible={true}
setAddFlyoutVisibility={state => {}}
actionTypes={[
{
id: actionType.id,
enabled: true,
name: 'Test',
},
]}
/>
</ActionsConnectorsContextProvider>
</AppContextProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,33 @@ import {
EuiBetaBadge,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useActionsConnectorsContext } from '../../context/actions_connectors_context';
import { ActionTypeMenu } from './action_type_menu';
import { ActionConnectorForm, validateBaseProperties } from './action_connector_form';
import { ActionType, ActionConnector, IErrorObject } from '../../../types';
import { useAppDependencies } from '../../app_context';
import { connectorReducer } from './connector_reducer';
import { hasSaveActionsCapability } from '../../lib/capabilities';
import { createActionConnector } from '../../lib/action_connector_api';
import { useActionsConnectorsContext } from '../../context/actions_connectors_context';

export interface ConnectorAddFlyoutProps {
addFlyoutVisible: boolean;
setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
actionTypes?: ActionType[];
}

export const ConnectorAddFlyout = () => {
export const ConnectorAddFlyout = ({
addFlyoutVisible,
setAddFlyoutVisibility,
actionTypes,
}: ConnectorAddFlyoutProps) => {
let hasErrors = false;
const { http, toastNotifications, capabilities, actionTypeRegistry } = useAppDependencies();
const {
http,
toastNotifications,
capabilities,
actionTypeRegistry,
reloadConnectors,
} = useActionsConnectorsContext();
const [actionType, setActionType] = useState<ActionType | undefined>(undefined);

// hooks
Expand All @@ -48,11 +63,6 @@ export const ConnectorAddFlyout = () => {
dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } });
};

const {
addFlyoutVisible,
setAddFlyoutVisibility,
reloadConnectors,
} = useActionsConnectorsContext();
const [isSaving, setIsSaving] = useState<boolean>(false);

const closeFlyout = useCallback(() => {
Expand All @@ -79,10 +89,7 @@ export const ConnectorAddFlyout = () => {
let actionTypeModel;
if (!actionType) {
currentForm = (
<ActionTypeMenu
onActionTypeChange={onActionTypeChange}
actionTypeRegistry={actionTypeRegistry}
/>
<ActionTypeMenu onActionTypeChange={onActionTypeChange} actionTypes={actionTypes} />
);
} else {
actionTypeModel = actionTypeRegistry.get(actionType.id);
Expand All @@ -108,17 +115,19 @@ export const ConnectorAddFlyout = () => {
const onActionConnectorSave = async (): Promise<ActionConnector | undefined> =>
await createActionConnector({ http, connector })
.then(savedConnector => {
toastNotifications.addSuccess(
i18n.translate(
'xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText',
{
defaultMessage: "Created '{connectorName}'",
values: {
connectorName: savedConnector.name,
},
}
)
);
if (toastNotifications) {
toastNotifications.addSuccess(
i18n.translate(
'xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText',
{
defaultMessage: "Created '{connectorName}'",
values: {
connectorName: savedConnector.name,
},
}
)
);
}
return savedConnector;
})
.catch(errorRes => {
Expand Down Expand Up @@ -218,7 +227,9 @@ export const ConnectorAddFlyout = () => {
setIsSaving(false);
if (savedAction) {
closeFlyout();
reloadConnectors();
if (reloadConnectors) {
reloadConnectors();
}
}
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { coreMock } from '../../../../../../../src/core/public/mocks';
import { ConnectorAddModal } from './connector_add_modal';
import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context';
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
import { ValidationResult } from '../../../types';
import { AppDeps } from '../../app';
Expand Down Expand Up @@ -75,30 +74,15 @@ describe('connector_add_modal', () => {

const wrapper = deps
? mountWithIntl(
<ActionsConnectorsContextProvider
value={{
addFlyoutVisible: true,
setAddFlyoutVisibility: state => {},
editFlyoutVisible: false,
setEditFlyoutVisibility: state => {},
actionTypesIndex: {
'my-action-type': { id: 'my-action-type', name: 'test', enabled: true },
},
reloadConnectors: () => {
return new Promise<void>(() => {});
},
}}
>
<ConnectorAddModal
addModalVisible={true}
setAddModalVisibility={() => {}}
actionType={actionType}
http={deps.http}
actionTypeRegistry={deps.actionTypeRegistry}
alertTypeRegistry={deps.alertTypeRegistry}
toastNotifications={deps.toastNotifications}
/>
</ActionsConnectorsContextProvider>
<ConnectorAddModal
addModalVisible={true}
setAddModalVisibility={() => {}}
actionType={actionType}
http={deps.http}
actionTypeRegistry={deps.actionTypeRegistry}
alertTypeRegistry={deps.alertTypeRegistry}
toastNotifications={deps.toastNotifications}
/>
)
: undefined;
expect(wrapper?.find('EuiModalHeader')).toHaveLength(1);
Expand Down
Loading