Added possibility to embed connectors create and edit flyouts (#58514)
* Added possibility to embed connectors flyout * Fixed type checks and removed example from siem start page * Fixed jest tests * Fixed failing tests * fixed type check * Added config for siem tests * Fixed failing tests * Fixed due to comments * Added missing documentation
This commit is contained in:
parent
c3f8647c3e
commit
e869695d73
16 changed files with 539 additions and 335 deletions
|
@ -43,6 +43,8 @@ Table of Contents
|
|||
- [Action type model definition](#action-type-model-definition)
|
||||
- [Register action type model](#register-action-type-model)
|
||||
- [Create and register new action type UI example](#reate-and-register-new-action-type-ui-example)
|
||||
- [Embed the Create Connector flyout within any Kibana plugin](#embed-the-create-connector-flyout-within-any-kibana-plugin)
|
||||
- [Embed the Edit Connector flyout within any Kibana plugin](#embed-the-edit-connector-flyout-within-any-kibana-plugin)
|
||||
|
||||
## Built-in Alert Types
|
||||
|
||||
|
@ -667,6 +669,7 @@ const [alertFlyoutVisible, setAlertFlyoutVisibility] = useState<boolean>(false);
|
|||
uiSettings,
|
||||
charts,
|
||||
dataFieldsFormats,
|
||||
metadata: { test: 'some value', fields: ['test'] },
|
||||
}}
|
||||
>
|
||||
<AlertAdd consumer={'watcher'} />
|
||||
|
@ -690,7 +693,7 @@ interface AlertAddProps {
|
|||
|
||||
AlertsContextProvider value options:
|
||||
```
|
||||
export interface AlertsContextValue {
|
||||
export interface AlertsContextValue<MetaData = Record<string, any>> {
|
||||
addFlyoutVisible: boolean;
|
||||
setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
reloadAlerts?: () => Promise<void>;
|
||||
|
@ -704,6 +707,7 @@ export interface AlertsContextValue {
|
|||
>;
|
||||
charts?: ChartsPluginSetup;
|
||||
dataFieldsFormats?: Pick<FieldFormatsRegistry, 'register'>;
|
||||
metadata?: MetaData;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -719,6 +723,7 @@ export interface AlertsContextValue {
|
|||
|toastNotifications|Optional toast messages.|
|
||||
|charts|Optional property, which is needed to display visualization of alert type expression. Will be changed after visualization refactoring.|
|
||||
|dataFieldsFormats|Optional property, which is needed to display visualization of alert type expression. Will be changed after visualization refactoring.|
|
||||
|metadata|Optional generic property, which allows to define component specific metadata. This metadata can be used for passing down preloaded data for Alert type expression component.|
|
||||
|
||||
## Build and register Action Types
|
||||
|
||||
|
@ -1198,3 +1203,213 @@ Clicking on the select card for `Example Action Type` will open the action type
|
|||
|
||||
or create a new connector:
|
||||
![Example Action Type with empty connectors list](https://i.imgur.com/EamA9Xv.png)
|
||||
|
||||
## Embed the Create Connector flyout within any Kibana plugin
|
||||
|
||||
Follow the instructions bellow to embed the Create Connector flyout within any Kibana plugin:
|
||||
1. Add TriggersAndActionsUIPublicPluginSetup and TriggersAndActionsUIPublicPluginStart to Kibana plugin setup dependencies:
|
||||
|
||||
```
|
||||
import {
|
||||
TriggersAndActionsUIPublicPluginSetup,
|
||||
TriggersAndActionsUIPublicPluginStart,
|
||||
} from '../../../../../x-pack/plugins/triggers_actions_ui/public';
|
||||
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
|
||||
...
|
||||
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginStart;
|
||||
```
|
||||
Then this dependency will be used to embed Create Connector flyout or register new action type.
|
||||
|
||||
2. Add Create Connector flyout to React component:
|
||||
```
|
||||
// import section
|
||||
import { ActionsConnectorsContextProvider, ConnectorAddFlyout } from '../../../../../../../triggers_actions_ui/public';
|
||||
|
||||
// in the component state definition section
|
||||
const [addFlyoutVisible, setAddFlyoutVisibility] = useState<boolean>(false);
|
||||
|
||||
// load required dependancied
|
||||
const { http, triggers_actions_ui, toastNotifications, capabilities } = useKibana().services;
|
||||
|
||||
const connector = {
|
||||
secrets: {},
|
||||
id: 'test',
|
||||
actionTypeId: '.index',
|
||||
actionType: 'Index',
|
||||
name: 'action-connector',
|
||||
referencedByCount: 0,
|
||||
config: {},
|
||||
};
|
||||
|
||||
// UI control item for open flyout
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
iconSide="left"
|
||||
onClick={() => setAddFlyoutVisibility(true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="emptyButton"
|
||||
defaultMessage="Create connector"
|
||||
/>
|
||||
</EuiButton>
|
||||
|
||||
// in render section of component
|
||||
<ActionsConnectorsContextProvider
|
||||
value={{
|
||||
http: http,
|
||||
toastNotifications: toastNotifications,
|
||||
actionTypeRegistry: triggers_actions_ui.actionTypeRegistry,
|
||||
capabilities: capabilities,
|
||||
}}
|
||||
>
|
||||
<ConnectorAddFlyout
|
||||
addFlyoutVisible={addFlyoutVisible}
|
||||
setAddFlyoutVisibility={setAddFlyoutVisibility}
|
||||
actionTypes={[
|
||||
{
|
||||
id: '.index',
|
||||
enabled: true,
|
||||
name: 'Index',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ActionsConnectorsContextProvider>
|
||||
```
|
||||
|
||||
ConnectorAddFlyout Props definition:
|
||||
```
|
||||
export interface ConnectorAddFlyoutProps {
|
||||
addFlyoutVisible: boolean;
|
||||
setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
actionTypes?: ActionType[];
|
||||
}
|
||||
```
|
||||
|
||||
|Property|Description|
|
||||
|---|---|
|
||||
|addFlyoutVisible|Visibility state of the Create Connector flyout.|
|
||||
|setAddFlyoutVisibility|Function for changing visibility state of the Create Connector flyout.|
|
||||
|actionTypes|Optional property, that allows to define only specific action types list which is available for a current plugin.|
|
||||
|
||||
ActionsConnectorsContextValue options:
|
||||
```
|
||||
export interface ActionsConnectorsContextValue {
|
||||
http: HttpSetup;
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
toastNotifications: Pick<
|
||||
ToastsApi,
|
||||
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
|
||||
>;
|
||||
capabilities: ApplicationStart['capabilities'];
|
||||
reloadConnectors?: () => Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|Property|Description|
|
||||
|---|---|
|
||||
|http|HttpSetup needed for executing API calls.|
|
||||
|actionTypeRegistry|Registry for action types.|
|
||||
|capabilities|Property, which is defining action current user usage capabilities like canSave or canDelete.|
|
||||
|toastNotifications|Toast messages.|
|
||||
|reloadConnectors|Optional function, which will be executed if connector was saved sucsessfuly, like reload list of connecotrs.|
|
||||
|
||||
|
||||
## Embed the Edit Connector flyout within any Kibana plugin
|
||||
|
||||
Follow the instructions bellow to embed the Edit Connector flyout within any Kibana plugin:
|
||||
1. Add TriggersAndActionsUIPublicPluginSetup and TriggersAndActionsUIPublicPluginStart to Kibana plugin setup dependencies:
|
||||
|
||||
```
|
||||
import {
|
||||
TriggersAndActionsUIPublicPluginSetup,
|
||||
TriggersAndActionsUIPublicPluginStart,
|
||||
} from '../../../../../x-pack/plugins/triggers_actions_ui/public';
|
||||
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
|
||||
...
|
||||
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginStart;
|
||||
```
|
||||
Then this dependency will be used to embed Edit Connector flyout.
|
||||
|
||||
2. Add Create Connector flyout to React component:
|
||||
```
|
||||
// import section
|
||||
import { ActionsConnectorsContextProvider, ConnectorEditFlyout } from '../../../../../../../triggers_actions_ui/public';
|
||||
|
||||
// in the component state definition section
|
||||
const [editFlyoutVisible, setEditFlyoutVisibility] = useState<boolean>(false);
|
||||
|
||||
// load required dependancied
|
||||
const { http, triggers_actions_ui, toastNotifications, capabilities } = useKibana().services;
|
||||
|
||||
// UI control item for open flyout
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
iconSide="left"
|
||||
onClick={() => setEditFlyoutVisibility(true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="emptyButton"
|
||||
defaultMessage="Edit connector"
|
||||
/>
|
||||
</EuiButton>
|
||||
|
||||
// in render section of component
|
||||
<ActionsConnectorsContextProvider
|
||||
value={{
|
||||
http: http,
|
||||
toastNotifications: toastNotifications,
|
||||
actionTypeRegistry: triggers_actions_ui.actionTypeRegistry,
|
||||
capabilities: capabilities,
|
||||
}}
|
||||
>
|
||||
<ConnectorEditFlyout
|
||||
initialConnector={connector}
|
||||
editFlyoutVisible={editFlyoutVisible}
|
||||
setEditFlyoutVisibility={setEditFlyoutVisibility}
|
||||
/>
|
||||
</ActionsConnectorsContextProvider>
|
||||
|
||||
```
|
||||
|
||||
ConnectorEditFlyout Props definition:
|
||||
```
|
||||
export interface ConnectorEditProps {
|
||||
initialConnector: ActionConnectorTableItem;
|
||||
editFlyoutVisible: boolean;
|
||||
setEditFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
```
|
||||
|
||||
|Property|Description|
|
||||
|---|---|
|
||||
|initialConnector|Property, that allows to define the initial state of edited connector.|
|
||||
|editFlyoutVisible|Visibility state of the Edit Connector flyout.|
|
||||
|setEditFlyoutVisibility|Function for changing visibility state of the Edit Connector flyout.|
|
||||
|
||||
ActionsConnectorsContextValue options:
|
||||
```
|
||||
export interface ActionsConnectorsContextValue {
|
||||
http: HttpSetup;
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
toastNotifications: Pick<
|
||||
ToastsApi,
|
||||
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
|
||||
>;
|
||||
capabilities: ApplicationStart['capabilities'];
|
||||
reloadConnectors?: () => Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
|Property|Description|
|
||||
|---|---|
|
||||
|http|HttpSetup needed for executing API calls.|
|
||||
|actionTypeRegistry|Registry for action types.|
|
||||
|capabilities|Property, which is defining action current user usage capabilities like canSave or canDelete.|
|
||||
|toastNotifications|Toast messages.|
|
||||
|reloadConnectors|Optional function, which will be executed if connector was saved sucsessfuly, like reload list of connecotrs.|
|
||||
|
|
|
@ -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<
|
||||
ToastsApi,
|
||||
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
|
||||
>;
|
||||
capabilities: ApplicationStart['capabilities'];
|
||||
reloadConnectors?: () => Promise<void>;
|
||||
}
|
||||
|
||||
const ActionsConnectorsContext = createContext<ActionsConnectorsContextValue>(null as any);
|
||||
|
|
|
@ -9,26 +9,21 @@ import { coreMock } from '../../../../../../../src/core/public/mocks';
|
|||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult, ActionConnector } from '../../../types';
|
||||
import { ActionConnectorForm } from './action_connector_form';
|
||||
import { ActionsConnectorsContextValue } from '../../context/actions_connectors_context';
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
|
||||
describe('action_connector_form', () => {
|
||||
let deps: any;
|
||||
let deps: ActionsConnectorsContextValue;
|
||||
beforeAll(async () => {
|
||||
const mocks = coreMock.createSetup();
|
||||
const [
|
||||
{
|
||||
chrome,
|
||||
docLinks,
|
||||
application: { capabilities },
|
||||
},
|
||||
] = await mocks.getStartServices();
|
||||
deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
toastNotifications: mocks.notifications.toasts,
|
||||
injectedMetadata: mocks.injectedMetadata,
|
||||
http: mocks.http,
|
||||
uiSettings: mocks.uiSettings,
|
||||
capabilities: {
|
||||
...capabilities,
|
||||
actions: {
|
||||
|
@ -37,11 +32,7 @@ describe('action_connector_form', () => {
|
|||
show: true,
|
||||
},
|
||||
},
|
||||
legacy: {
|
||||
MANAGEMENT_BREADCRUMB: { set: () => {} } as any,
|
||||
},
|
||||
actionTypeRegistry: actionTypeRegistry as any,
|
||||
alertTypeRegistry: {} as any,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -6,31 +6,28 @@
|
|||
import * as React from 'react';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context';
|
||||
import {
|
||||
ActionsConnectorsContextProvider,
|
||||
ActionsConnectorsContextValue,
|
||||
} from '../../context/actions_connectors_context';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ActionTypeMenu } from './action_type_menu';
|
||||
import { ValidationResult } from '../../../types';
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
|
||||
describe('connector_add_flyout', () => {
|
||||
let deps: any;
|
||||
let deps: ActionsConnectorsContextValue;
|
||||
|
||||
beforeAll(async () => {
|
||||
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,
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
capabilities: {
|
||||
...capabilities,
|
||||
actions: {
|
||||
|
@ -39,11 +36,7 @@ describe('connector_add_flyout', () => {
|
|||
show: true,
|
||||
},
|
||||
},
|
||||
legacy: {
|
||||
MANAGEMENT_BREADCRUMB: { set: () => {} } as any,
|
||||
},
|
||||
actionTypeRegistry: actionTypeRegistry as any,
|
||||
alertTypeRegistry: {} as any,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -68,14 +61,10 @@ 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,
|
||||
toastNotifications: deps!.toastNotifications,
|
||||
reloadConnectors: () => {
|
||||
return new Promise<void>(() => {});
|
||||
},
|
||||
|
@ -83,12 +72,17 @@ describe('connector_add_flyout', () => {
|
|||
>
|
||||
<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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
@ -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 (
|
||||
|
|
|
@ -7,37 +7,28 @@ import * as React from 'react';
|
|||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { ConnectorAddFlyout } from './connector_add_flyout';
|
||||
import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context';
|
||||
import {
|
||||
ActionsConnectorsContextProvider,
|
||||
ActionsConnectorsContextValue,
|
||||
} from '../../context/actions_connectors_context';
|
||||
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: ActionsConnectorsContextValue;
|
||||
|
||||
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: {
|
||||
|
@ -46,9 +37,7 @@ describe('connector_add_flyout', () => {
|
|||
show: true,
|
||||
},
|
||||
},
|
||||
setBreadcrumbs: jest.fn(),
|
||||
actionTypeRegistry: actionTypeRegistry as any,
|
||||
alertTypeRegistry: {} as any,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -71,24 +60,29 @@ describe('connector_add_flyout', () => {
|
|||
actionTypeRegistry.has.mockReturnValue(true);
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<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 },
|
||||
<ActionsConnectorsContextProvider
|
||||
value={{
|
||||
http: deps!.http,
|
||||
toastNotifications: deps!.toastNotifications,
|
||||
actionTypeRegistry: deps!.actionTypeRegistry,
|
||||
capabilities: deps!.capabilities,
|
||||
reloadConnectors: () => {
|
||||
return new Promise<void>(() => {});
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ConnectorAddFlyout
|
||||
addFlyoutVisible={true}
|
||||
setAddFlyoutVisibility={() => {}}
|
||||
actionTypes={[
|
||||
{
|
||||
id: actionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
},
|
||||
reloadConnectors: () => {
|
||||
return new Promise<void>(() => {});
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ConnectorAddFlyout />
|
||||
</ActionsConnectorsContextProvider>
|
||||
</AppContextProvider>
|
||||
]}
|
||||
/>
|
||||
</ActionsConnectorsContextProvider>
|
||||
);
|
||||
expect(wrapper.find('ActionTypeMenu')).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="my-action-type-card"]').exists()).toBeTruthy();
|
||||
|
|
|
@ -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 const ConnectorAddFlyout = () => {
|
||||
export interface ConnectorAddFlyoutProps {
|
||||
addFlyoutVisible: boolean;
|
||||
setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
actionTypes?: ActionType[];
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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(() => {
|
||||
|
@ -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);
|
||||
|
@ -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 => {
|
||||
|
@ -218,7 +227,9 @@ export const ConnectorAddFlyout = () => {
|
|||
setIsSaving(false);
|
||||
if (savedAction) {
|
||||
closeFlyout();
|
||||
reloadConnectors();
|
||||
if (reloadConnectors) {
|
||||
reloadConnectors();
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -7,35 +7,24 @@ 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';
|
||||
import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
|
||||
import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks';
|
||||
import { ActionsConnectorsContextValue } from '../../context/actions_connectors_context';
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
|
||||
describe('connector_add_modal', () => {
|
||||
let deps: AppDeps | null;
|
||||
let deps: ActionsConnectorsContextValue;
|
||||
|
||||
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: {
|
||||
|
@ -44,9 +33,7 @@ describe('connector_add_modal', () => {
|
|||
show: true,
|
||||
},
|
||||
},
|
||||
setBreadcrumbs: jest.fn(),
|
||||
actionTypeRegistry: actionTypeRegistry as any,
|
||||
alertTypeRegistry: {} as any,
|
||||
};
|
||||
});
|
||||
it('renders connector modal form if addModalVisible is true', () => {
|
||||
|
@ -75,30 +62,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={{} as any}
|
||||
toastNotifications={deps.toastNotifications}
|
||||
/>
|
||||
)
|
||||
: undefined;
|
||||
expect(wrapper?.find('EuiModalHeader')).toHaveLength(1);
|
||||
|
|
|
@ -11,8 +11,6 @@ import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
|||
import { ValidationResult } from '../../../types';
|
||||
import { ConnectorEditFlyout } from './connector_edit_flyout';
|
||||
import { AppContextProvider } from '../../app_context';
|
||||
import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks';
|
||||
import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
|
||||
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
let deps: any;
|
||||
|
@ -22,18 +20,11 @@ describe('connector_edit_flyout', () => {
|
|||
const mockes = coreMock.createSetup();
|
||||
const [
|
||||
{
|
||||
chrome,
|
||||
docLinks,
|
||||
application: { capabilities },
|
||||
},
|
||||
] = await mockes.getStartServices();
|
||||
deps = {
|
||||
chrome,
|
||||
docLinks,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
injectedMetadata: mockes.injectedMetadata,
|
||||
http: mockes.http,
|
||||
uiSettings: mockes.uiSettings,
|
||||
capabilities: {
|
||||
|
@ -44,7 +35,6 @@ describe('connector_edit_flyout', () => {
|
|||
show: true,
|
||||
},
|
||||
},
|
||||
setBreadcrumbs: jest.fn(),
|
||||
actionTypeRegistry: actionTypeRegistry as any,
|
||||
alertTypeRegistry: {} as any,
|
||||
};
|
||||
|
@ -82,19 +72,20 @@ describe('connector_edit_flyout', () => {
|
|||
<AppContextProvider appDeps={deps}>
|
||||
<ActionsConnectorsContextProvider
|
||||
value={{
|
||||
addFlyoutVisible: false,
|
||||
setAddFlyoutVisibility: state => {},
|
||||
editFlyoutVisible: true,
|
||||
setEditFlyoutVisibility: state => {},
|
||||
actionTypesIndex: {
|
||||
'test-action-type-id': { id: 'test-action-type-id', name: 'test', enabled: true },
|
||||
},
|
||||
http: deps.http,
|
||||
toastNotifications: deps.toastNotifications,
|
||||
capabilities: deps.capabilities,
|
||||
actionTypeRegistry: deps.actionTypeRegistry,
|
||||
reloadConnectors: () => {
|
||||
return new Promise<void>(() => {});
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ConnectorEditFlyout initialConnector={connector} />
|
||||
<ConnectorEditFlyout
|
||||
initialConnector={connector}
|
||||
editFlyoutVisible={true}
|
||||
setEditFlyoutVisibility={state => {}}
|
||||
/>
|
||||
</ActionsConnectorsContextProvider>
|
||||
</AppContextProvider>
|
||||
);
|
||||
|
|
|
@ -19,27 +19,33 @@ import {
|
|||
EuiBetaBadge,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useActionsConnectorsContext } from '../../context/actions_connectors_context';
|
||||
import { ActionConnectorForm, validateBaseProperties } from './action_connector_form';
|
||||
import { useAppDependencies } from '../../app_context';
|
||||
import { ActionConnectorTableItem, ActionConnector, IErrorObject } from '../../../types';
|
||||
import { connectorReducer } from './connector_reducer';
|
||||
import { updateActionConnector } from '../../lib/action_connector_api';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { useActionsConnectorsContext } from '../../context/actions_connectors_context';
|
||||
|
||||
export interface ConnectorEditProps {
|
||||
initialConnector: ActionConnectorTableItem;
|
||||
editFlyoutVisible: boolean;
|
||||
setEditFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const ConnectorEditFlyout = ({ initialConnector }: ConnectorEditProps) => {
|
||||
export const ConnectorEditFlyout = ({
|
||||
initialConnector,
|
||||
editFlyoutVisible,
|
||||
setEditFlyoutVisibility,
|
||||
}: ConnectorEditProps) => {
|
||||
let hasErrors = false;
|
||||
const { http, toastNotifications, capabilities, actionTypeRegistry } = useAppDependencies();
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
const {
|
||||
editFlyoutVisible,
|
||||
setEditFlyoutVisibility,
|
||||
http,
|
||||
toastNotifications,
|
||||
capabilities,
|
||||
actionTypeRegistry,
|
||||
reloadConnectors,
|
||||
} = useActionsConnectorsContext();
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
const closeFlyout = useCallback(() => setEditFlyoutVisibility(false), [setEditFlyoutVisibility]);
|
||||
const [{ connector }, dispatch] = useReducer(connectorReducer, {
|
||||
connector: { ...initialConnector, secrets: {} },
|
||||
|
@ -63,17 +69,19 @@ export const ConnectorEditFlyout = ({ initialConnector }: ConnectorEditProps) =>
|
|||
const onActionConnectorSave = async (): Promise<ActionConnector | undefined> =>
|
||||
await updateActionConnector({ http, connector, id: connector.id })
|
||||
.then(savedConnector => {
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText',
|
||||
{
|
||||
defaultMessage: "Updated '{connectorName}'",
|
||||
values: {
|
||||
connectorName: savedConnector.name,
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
if (toastNotifications) {
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText',
|
||||
{
|
||||
defaultMessage: "Updated '{connectorName}'",
|
||||
values: {
|
||||
connectorName: savedConnector.name,
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
return savedConnector;
|
||||
})
|
||||
.catch(errorRes => {
|
||||
|
@ -151,7 +159,9 @@ export const ConnectorEditFlyout = ({ initialConnector }: ConnectorEditProps) =>
|
|||
setIsSaving(false);
|
||||
if (savedAction) {
|
||||
closeFlyout();
|
||||
reloadConnectors();
|
||||
if (reloadConnectors) {
|
||||
reloadConnectors();
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -18,16 +18,16 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ActionsConnectorsContextProvider } from '../../../context/actions_connectors_context';
|
||||
import { useAppDependencies } from '../../../app_context';
|
||||
import { loadAllActions, loadActionTypes } from '../../../lib/action_connector_api';
|
||||
import { ActionConnector, ActionConnectorTableItem, ActionTypeIndex } from '../../../../types';
|
||||
import { ConnectorAddFlyout, ConnectorEditFlyout } from '../../action_connector_form';
|
||||
import { hasDeleteActionsCapability, hasSaveActionsCapability } from '../../../lib/capabilities';
|
||||
import { DeleteConnectorsModal } from '../../../components/delete_connectors_modal';
|
||||
import { ActionsConnectorsContextProvider } from '../../../context/actions_connectors_context';
|
||||
|
||||
export const ActionsConnectorsList: React.FunctionComponent = () => {
|
||||
const { http, toastNotifications, capabilities } = useAppDependencies();
|
||||
const { http, toastNotifications, capabilities, actionTypeRegistry } = useAppDependencies();
|
||||
const canDelete = hasDeleteActionsCapability(capabilities);
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
|
@ -377,19 +377,23 @@ export const ActionsConnectorsList: React.FunctionComponent = () => {
|
|||
{data.length === 0 && !canSave && noPermissionPrompt}
|
||||
<ActionsConnectorsContextProvider
|
||||
value={{
|
||||
addFlyoutVisible,
|
||||
setAddFlyoutVisibility,
|
||||
editFlyoutVisible,
|
||||
setEditFlyoutVisibility,
|
||||
actionTypesIndex,
|
||||
actionTypeRegistry,
|
||||
http,
|
||||
capabilities,
|
||||
toastNotifications,
|
||||
reloadConnectors: loadActions,
|
||||
}}
|
||||
>
|
||||
<ConnectorAddFlyout />
|
||||
<ConnectorAddFlyout
|
||||
addFlyoutVisible={addFlyoutVisible}
|
||||
setAddFlyoutVisibility={setAddFlyoutVisibility}
|
||||
/>
|
||||
{editedConnectorItem ? (
|
||||
<ConnectorEditFlyout
|
||||
key={editedConnectorItem.id}
|
||||
initialConnector={editedConnectorItem}
|
||||
editFlyoutVisible={editFlyoutVisible}
|
||||
setEditFlyoutVisibility={setEditFlyoutVisibility}
|
||||
/>
|
||||
) : null}
|
||||
</ActionsConnectorsContextProvider>
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('alert_edit', () => {
|
|||
let deps: any;
|
||||
let wrapper: ReactWrapper<any>;
|
||||
|
||||
beforeAll(async () => {
|
||||
async function setup() {
|
||||
const mockes = coreMock.createSetup();
|
||||
deps = {
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
|
@ -122,9 +122,10 @@ describe('alert_edit', () => {
|
|||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('renders alert add flyout', () => {
|
||||
it('renders alert add flyout', async () => {
|
||||
await setup();
|
||||
expect(wrapper.find('[data-test-subj="editAlertFlyoutTitle"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="saveEditedAlertButton"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -4,22 +4,19 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { Fragment } from 'react';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
|
||||
import { ValidationResult, Alert } from '../../../types';
|
||||
import { AlertForm } from './alert_form';
|
||||
import { AppDeps } from '../../app';
|
||||
import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks';
|
||||
import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
|
||||
import { AlertsContextProvider } from '../../context/alerts_context';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const alertTypeRegistry = alertTypeRegistryMock.create();
|
||||
describe('alert_form', () => {
|
||||
let deps: AppDeps | null;
|
||||
let deps: any;
|
||||
const alertType = {
|
||||
id: 'my-alert-type',
|
||||
iconClass: 'test',
|
||||
|
@ -44,42 +41,19 @@ describe('alert_form', () => {
|
|||
actionConnectorFields: null,
|
||||
actionParamsFields: null,
|
||||
};
|
||||
beforeAll(async () => {
|
||||
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,
|
||||
dataPlugin: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
capabilities: {
|
||||
...capabilities,
|
||||
siem: {
|
||||
'alerting:show': true,
|
||||
'alerting:save': true,
|
||||
'alerting:delete': false,
|
||||
},
|
||||
},
|
||||
setBreadcrumbs: jest.fn(),
|
||||
actionTypeRegistry: actionTypeRegistry as any,
|
||||
alertTypeRegistry: alertTypeRegistry as any,
|
||||
};
|
||||
});
|
||||
|
||||
describe('alert_form create alert', () => {
|
||||
let wrapper: ReactWrapper<any>;
|
||||
|
||||
beforeAll(async () => {
|
||||
async function setup() {
|
||||
const mockes = coreMock.createSetup();
|
||||
deps = {
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
http: mockes.http,
|
||||
uiSettings: mockes.uiSettings,
|
||||
actionTypeRegistry: actionTypeRegistry as any,
|
||||
alertTypeRegistry: alertTypeRegistry as any,
|
||||
};
|
||||
alertTypeRegistry.list.mockReturnValue([alertType]);
|
||||
alertTypeRegistry.has.mockReturnValue(true);
|
||||
actionTypeRegistry.list.mockReturnValue([actionType]);
|
||||
|
@ -99,47 +73,49 @@ describe('alert_form', () => {
|
|||
mutedInstanceIds: [],
|
||||
} as unknown) as Alert;
|
||||
|
||||
wrapper = mountWithIntl(
|
||||
<AlertsContextProvider
|
||||
value={{
|
||||
reloadAlerts: () => {
|
||||
return new Promise<void>(() => {});
|
||||
},
|
||||
http: deps!.http,
|
||||
actionTypeRegistry: deps!.actionTypeRegistry,
|
||||
alertTypeRegistry: deps!.alertTypeRegistry,
|
||||
toastNotifications: deps!.toastNotifications,
|
||||
uiSettings: deps!.uiSettings,
|
||||
}}
|
||||
>
|
||||
<AlertForm
|
||||
alert={initialAlert}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [] }}
|
||||
serverError={null}
|
||||
/>
|
||||
</AlertsContextProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
if (deps) {
|
||||
wrapper = mountWithIntl(
|
||||
<AlertsContextProvider
|
||||
value={{
|
||||
reloadAlerts: () => {
|
||||
return new Promise<void>(() => {});
|
||||
},
|
||||
http: deps.http,
|
||||
actionTypeRegistry: deps.actionTypeRegistry,
|
||||
alertTypeRegistry: deps.alertTypeRegistry,
|
||||
toastNotifications: deps.toastNotifications,
|
||||
uiSettings: deps.uiSettings,
|
||||
}}
|
||||
>
|
||||
<AlertForm
|
||||
alert={initialAlert}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [] }}
|
||||
serverError={null}
|
||||
/>
|
||||
</AlertsContextProvider>
|
||||
);
|
||||
}
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
}
|
||||
|
||||
await waitForRender(wrapper);
|
||||
});
|
||||
|
||||
it('renders alert name', () => {
|
||||
it('renders alert name', async () => {
|
||||
await setup();
|
||||
const alertNameField = wrapper.find('[data-test-subj="alertNameInput"]');
|
||||
expect(alertNameField.exists()).toBeTruthy();
|
||||
expect(alertNameField.first().prop('value')).toBe('test');
|
||||
});
|
||||
|
||||
it('renders registered selected alert type', () => {
|
||||
it('renders registered selected alert type', async () => {
|
||||
await setup();
|
||||
const alertTypeSelectOptions = wrapper.find('[data-test-subj="my-alert-type-SelectOption"]');
|
||||
expect(alertTypeSelectOptions.exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders registered action types', () => {
|
||||
it('renders registered action types', async () => {
|
||||
await setup();
|
||||
const alertTypeSelectOptions = wrapper.find(
|
||||
'[data-test-subj=".server-log-ActionTypeSelectOption"]'
|
||||
);
|
||||
|
@ -150,7 +126,15 @@ describe('alert_form', () => {
|
|||
describe('alert_form edit alert', () => {
|
||||
let wrapper: ReactWrapper<any>;
|
||||
|
||||
beforeAll(async () => {
|
||||
async function setup() {
|
||||
const mockes = coreMock.createSetup();
|
||||
deps = {
|
||||
toastNotifications: mockes.notifications.toasts,
|
||||
http: mockes.http,
|
||||
uiSettings: mockes.uiSettings,
|
||||
actionTypeRegistry: actionTypeRegistry as any,
|
||||
alertTypeRegistry: alertTypeRegistry as any,
|
||||
};
|
||||
alertTypeRegistry.list.mockReturnValue([alertType]);
|
||||
alertTypeRegistry.get.mockReturnValue(alertType);
|
||||
alertTypeRegistry.has.mockReturnValue(true);
|
||||
|
@ -173,57 +157,53 @@ describe('alert_form', () => {
|
|||
mutedInstanceIds: [],
|
||||
} as unknown) as Alert;
|
||||
|
||||
wrapper = mountWithIntl(
|
||||
<AlertsContextProvider
|
||||
value={{
|
||||
reloadAlerts: () => {
|
||||
return new Promise<void>(() => {});
|
||||
},
|
||||
http: deps!.http,
|
||||
actionTypeRegistry: deps!.actionTypeRegistry,
|
||||
alertTypeRegistry: deps!.alertTypeRegistry,
|
||||
toastNotifications: deps!.toastNotifications,
|
||||
uiSettings: deps!.uiSettings,
|
||||
}}
|
||||
>
|
||||
<AlertForm
|
||||
alert={initialAlert}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [] }}
|
||||
serverError={null}
|
||||
/>
|
||||
</AlertsContextProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
if (deps) {
|
||||
wrapper = mountWithIntl(
|
||||
<AlertsContextProvider
|
||||
value={{
|
||||
reloadAlerts: () => {
|
||||
return new Promise<void>(() => {});
|
||||
},
|
||||
http: deps.http,
|
||||
actionTypeRegistry: deps.actionTypeRegistry,
|
||||
alertTypeRegistry: deps.alertTypeRegistry,
|
||||
toastNotifications: deps.toastNotifications,
|
||||
uiSettings: deps.uiSettings,
|
||||
}}
|
||||
>
|
||||
<AlertForm
|
||||
alert={initialAlert}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [] }}
|
||||
serverError={null}
|
||||
/>
|
||||
</AlertsContextProvider>
|
||||
);
|
||||
}
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
}
|
||||
|
||||
await waitForRender(wrapper);
|
||||
});
|
||||
|
||||
it('renders alert name', () => {
|
||||
it('renders alert name', async () => {
|
||||
await setup();
|
||||
const alertNameField = wrapper.find('[data-test-subj="alertNameInput"]');
|
||||
expect(alertNameField.exists()).toBeTruthy();
|
||||
expect(alertNameField.first().prop('value')).toBe('test');
|
||||
});
|
||||
|
||||
it('renders registered selected alert type', () => {
|
||||
it('renders registered selected alert type', async () => {
|
||||
await setup();
|
||||
const alertTypeSelectOptions = wrapper.find('[data-test-subj="selectedAlertTypeTitle"]');
|
||||
expect(alertTypeSelectOptions.exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders registered action types', () => {
|
||||
it('renders registered action types', async () => {
|
||||
await setup();
|
||||
const actionTypeSelectOptions = wrapper.find(
|
||||
'[data-test-subj="my-action-type-ActionTypeSelectOption"]'
|
||||
);
|
||||
expect(actionTypeSelectOptions.exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
async function waitForRender(wrapper: ReactWrapper<any, any>) {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
wrapper.update();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,7 +8,12 @@ import { PluginInitializerContext } from 'src/core/public';
|
|||
import { Plugin } from './plugin';
|
||||
|
||||
export { AlertsContextProvider } from './application/context/alerts_context';
|
||||
export { ActionsConnectorsContextProvider } from './application/context/actions_connectors_context';
|
||||
export { AlertAdd } from './application/sections/alert_form';
|
||||
export {
|
||||
ConnectorAddFlyout,
|
||||
ConnectorEditFlyout,
|
||||
} from './application/sections/action_connector_form';
|
||||
|
||||
export function plugin(ctx: PluginInitializerContext) {
|
||||
return new Plugin(ctx);
|
||||
|
|
|
@ -22,7 +22,10 @@ export interface TriggersAndActionsUIPublicPluginSetup {
|
|||
alertTypeRegistry: TypeRegistry<AlertTypeModel>;
|
||||
}
|
||||
|
||||
export type Start = void;
|
||||
export interface TriggersAndActionsUIPublicPluginStart {
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
alertTypeRegistry: TypeRegistry<AlertTypeModel>;
|
||||
}
|
||||
|
||||
interface PluginsStart {
|
||||
data: DataPublicPluginStart;
|
||||
|
@ -30,7 +33,9 @@ interface PluginsStart {
|
|||
management: ManagementStart;
|
||||
}
|
||||
|
||||
export class Plugin implements CorePlugin<TriggersAndActionsUIPublicPluginSetup, Start> {
|
||||
export class Plugin
|
||||
implements
|
||||
CorePlugin<TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart> {
|
||||
private actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
private alertTypeRegistry: TypeRegistry<AlertTypeModel>;
|
||||
|
||||
|
@ -57,44 +62,46 @@ export class Plugin implements CorePlugin<TriggersAndActionsUIPublicPluginSetup,
|
|||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: PluginsStart) {
|
||||
public start(core: CoreStart, plugins: PluginsStart): TriggersAndActionsUIPublicPluginStart {
|
||||
const { capabilities } = core.application;
|
||||
|
||||
const canShowActions = hasShowActionsCapability(capabilities);
|
||||
const canShowAlerts = hasShowAlertsCapability(capabilities);
|
||||
|
||||
// Don't register routes when user doesn't have access to the application
|
||||
if (!canShowActions && !canShowAlerts) {
|
||||
return;
|
||||
if (canShowActions || canShowAlerts) {
|
||||
plugins.management.sections.getSection('kibana')!.registerApp({
|
||||
id: 'triggersActions',
|
||||
title: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', {
|
||||
defaultMessage: 'Alerts and Actions',
|
||||
}),
|
||||
order: 7,
|
||||
mount: params => {
|
||||
boot({
|
||||
dataPlugin: plugins.data,
|
||||
charts: plugins.charts,
|
||||
element: params.element,
|
||||
toastNotifications: core.notifications.toasts,
|
||||
injectedMetadata: core.injectedMetadata,
|
||||
http: core.http,
|
||||
uiSettings: core.uiSettings,
|
||||
docLinks: core.docLinks,
|
||||
chrome: core.chrome,
|
||||
savedObjects: core.savedObjects.client,
|
||||
I18nContext: core.i18n.Context,
|
||||
capabilities: core.application.capabilities,
|
||||
setBreadcrumbs: params.setBreadcrumbs,
|
||||
actionTypeRegistry: this.actionTypeRegistry,
|
||||
alertTypeRegistry: this.alertTypeRegistry,
|
||||
});
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
plugins.management.sections.getSection('kibana')!.registerApp({
|
||||
id: 'triggersActions',
|
||||
title: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', {
|
||||
defaultMessage: 'Alerts and Actions',
|
||||
}),
|
||||
order: 7,
|
||||
mount: params => {
|
||||
boot({
|
||||
dataPlugin: plugins.data,
|
||||
charts: plugins.charts,
|
||||
element: params.element,
|
||||
toastNotifications: core.notifications.toasts,
|
||||
injectedMetadata: core.injectedMetadata,
|
||||
http: core.http,
|
||||
uiSettings: core.uiSettings,
|
||||
docLinks: core.docLinks,
|
||||
chrome: core.chrome,
|
||||
savedObjects: core.savedObjects.client,
|
||||
I18nContext: core.i18n.Context,
|
||||
capabilities: core.application.capabilities,
|
||||
setBreadcrumbs: params.setBreadcrumbs,
|
||||
actionTypeRegistry: this.actionTypeRegistry,
|
||||
alertTypeRegistry: this.alertTypeRegistry,
|
||||
});
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
return {
|
||||
actionTypeRegistry: this.actionTypeRegistry,
|
||||
alertTypeRegistry: this.alertTypeRegistry,
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
|
|
|
@ -60,7 +60,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await testSubjects.click('thresholdAlertTimeFieldSelect');
|
||||
const fieldOptions = await find.allByCssSelector('#thresholdTimeField option');
|
||||
await fieldOptions[1].click();
|
||||
// need this two out of popup clicks to close them
|
||||
await nameInput.click();
|
||||
await testSubjects.click('intervalInput');
|
||||
|
||||
await testSubjects.click('.slack-ActionTypeSelectOption');
|
||||
await testSubjects.click('createActionConnectorButton');
|
||||
const connectorNameInput = await testSubjects.find('nameInput');
|
||||
|
|
Loading…
Reference in a new issue