mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Alerting] adds an Run When field in the alert flyout to assign the action to an Action Group (#82472)
Adds a `RunsWhen` field to actions in the Alerts Flyout when creating / editing an Alert which allows the user to assign specific actions to a certain Action Groups
This commit is contained in:
parent
858befef44
commit
3c525d7341
13 changed files with 846 additions and 554 deletions
|
@ -127,9 +127,9 @@ export const PeopleinSpaceExpression: React.FunctionComponent<PeopleinSpaceParam
|
|||
});
|
||||
|
||||
const errorsCallout = flatten(
|
||||
Object.entries(errors).map(([field, errs]: [string, string[]]) =>
|
||||
errs.map((e) => (
|
||||
<p>
|
||||
Object.entries(errors).map(([field, errs]: [string, string[]], fieldIndex) =>
|
||||
errs.map((e, index) => (
|
||||
<p key={`astros-error-${fieldIndex}-${index}`}>
|
||||
<EuiTextColor color="accent">{field}:</EuiTextColor>`: ${errs}`
|
||||
</p>
|
||||
))
|
||||
|
|
|
@ -5,25 +5,31 @@
|
|||
*/
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { range } from 'lodash';
|
||||
import { range, random } from 'lodash';
|
||||
import { AlertType } from '../../../../plugins/alerts/server';
|
||||
import { DEFAULT_INSTANCES_TO_GENERATE, ALERTING_EXAMPLE_APP_ID } from '../../common/constants';
|
||||
|
||||
const ACTION_GROUPS = [
|
||||
{ id: 'small', name: 'small' },
|
||||
{ id: 'medium', name: 'medium' },
|
||||
{ id: 'large', name: 'large' },
|
||||
];
|
||||
|
||||
export const alertType: AlertType = {
|
||||
id: 'example.always-firing',
|
||||
name: 'Always firing',
|
||||
actionGroups: [{ id: 'default', name: 'default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
actionGroups: ACTION_GROUPS,
|
||||
defaultActionGroupId: 'small',
|
||||
async executor({ services, params: { instances = DEFAULT_INSTANCES_TO_GENERATE }, state }) {
|
||||
const count = (state.count ?? 0) + 1;
|
||||
|
||||
range(instances)
|
||||
.map(() => ({ id: uuid.v4() }))
|
||||
.forEach((instance: { id: string }) => {
|
||||
.map(() => ({ id: uuid.v4(), tshirtSize: ACTION_GROUPS[random(0, 2)].id! }))
|
||||
.forEach((instance: { id: string; tshirtSize: string }) => {
|
||||
services
|
||||
.alertInstanceFactory(instance.id)
|
||||
.replaceState({ triggerdOnCycle: count })
|
||||
.scheduleActions('default');
|
||||
.scheduleActions(instance.tshirtSize);
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -1319,19 +1319,19 @@ ActionForm Props definition:
|
|||
interface ActionAccordionFormProps {
|
||||
actions: AlertAction[];
|
||||
defaultActionGroupId: string;
|
||||
actionGroups?: ActionGroup[];
|
||||
setActionIdByIndex: (id: string, index: number) => void;
|
||||
setActionGroupIdByIndex?: (group: string, index: number) => void;
|
||||
setAlertProperty: (actions: AlertAction[]) => void;
|
||||
setActionParamsProperty: (key: string, value: any, index: number) => void;
|
||||
http: HttpSetup;
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
toastNotifications: Pick<
|
||||
ToastsApi,
|
||||
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
|
||||
>;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
toastNotifications: ToastsSetup;
|
||||
docLinks: DocLinksStart;
|
||||
actionTypes?: ActionType[];
|
||||
messageVariables?: ActionVariable[];
|
||||
defaultActionMessage?: string;
|
||||
consumer: string;
|
||||
capabilities: ApplicationStart['capabilities'];
|
||||
}
|
||||
|
||||
```
|
||||
|
@ -1339,17 +1339,20 @@ interface ActionAccordionFormProps {
|
|||
|Property|Description|
|
||||
|---|---|
|
||||
|actions|List of actions comes from alert.actions property.|
|
||||
|defaultActionGroupId|Default action group id to which each new action will belong to.|
|
||||
|defaultActionGroupId|Default action group id to which each new action will belong by default.|
|
||||
|actionGroups|Optional. List of action groups to which new action can be assigned. The RunWhen field is only displayed when these action groups are specified|
|
||||
|setActionIdByIndex|Function for changing action 'id' by the proper index in alert.actions array.|
|
||||
|setActionGroupIdByIndex|Function for changing action 'group' by the proper index in alert.actions array.|
|
||||
|setAlertProperty|Function for changing alert property 'actions'. Used when deleting action from the array to reset it.|
|
||||
|setActionParamsProperty|Function for changing action key/value property by index in alert.actions array.|
|
||||
|http|HttpSetup needed for executing API calls.|
|
||||
|actionTypeRegistry|Registry for action types.|
|
||||
|toastNotifications|Toast messages.|
|
||||
|toastNotifications|Toast messages Plugin Setup Contract.|
|
||||
|docLinks|Documentation links Plugin Start Contract.|
|
||||
|actionTypes|Optional property, which allowes to define a list of available actions specific for a current plugin.|
|
||||
|actionTypes|Optional property, which allowes to define a list of variables for action 'message' property.|
|
||||
|defaultActionMessage|Optional property, which allowes to define a message value for action with 'message' property.|
|
||||
|consumer|Name of the plugin that creates an action.|
|
||||
|capabilities|Kibana core's Capabilities ApplicationStart['capabilities'].|
|
||||
|
||||
|
||||
AlertsContextProvider value options:
|
||||
|
|
|
@ -3,9 +3,15 @@
|
|||
}
|
||||
|
||||
.actAccordionActionForm {
|
||||
.euiCard {
|
||||
box-shadow: none;
|
||||
}
|
||||
background-color: $euiColorLightestShade;
|
||||
}
|
||||
|
||||
.actAccordionActionForm .euiCard {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.actAccordionActionForm__button {
|
||||
padding: $euiSizeM;
|
||||
}
|
||||
|
||||
.actConnectorsListGrid {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import React, { Fragment, lazy } from 'react';
|
||||
import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult, Alert, AlertAction } from '../../../types';
|
||||
|
@ -112,8 +111,6 @@ describe('action_form', () => {
|
|||
};
|
||||
|
||||
describe('action_form in alert', () => {
|
||||
let wrapper: ReactWrapper<any>;
|
||||
|
||||
async function setup(customActions?: AlertAction[]) {
|
||||
const { loadAllActions } = jest.requireMock('../../lib/action_connector_api');
|
||||
loadAllActions.mockResolvedValueOnce([
|
||||
|
@ -217,7 +214,7 @@ describe('action_form', () => {
|
|||
mutedInstanceIds: [],
|
||||
} as unknown) as Alert;
|
||||
|
||||
wrapper = mountWithIntl(
|
||||
const wrapper = mountWithIntl(
|
||||
<ActionForm
|
||||
actions={initialAlert.actions}
|
||||
messageVariables={[
|
||||
|
@ -228,6 +225,10 @@ describe('action_form', () => {
|
|||
setActionIdByIndex={(id: string, index: number) => {
|
||||
initialAlert.actions[index].id = id;
|
||||
}}
|
||||
actionGroups={[{ id: 'default', name: 'Default' }]}
|
||||
setActionGroupIdByIndex={(group: string, index: number) => {
|
||||
initialAlert.actions[index].group = group;
|
||||
}}
|
||||
setAlertProperty={(_updatedActions: AlertAction[]) => {}}
|
||||
setActionParamsProperty={(key: string, value: any, index: number) =>
|
||||
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
|
||||
|
@ -297,10 +298,12 @@ describe('action_form', () => {
|
|||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('renders available action cards', async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
`[data-test-subj="${actionType.id}-ActionTypeSelectOption"]`
|
||||
);
|
||||
|
@ -314,7 +317,7 @@ describe('action_form', () => {
|
|||
});
|
||||
|
||||
it('does not render action types disabled by config', async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
'[data-test-subj="disabled-by-config-ActionTypeSelectOption"]'
|
||||
);
|
||||
|
@ -322,52 +325,72 @@ describe('action_form', () => {
|
|||
});
|
||||
|
||||
it('render action types which is preconfigured only (disabled by config and with preconfigured connectors)', async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]');
|
||||
expect(actionOption.exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders available action groups for the selected action type', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
`[data-test-subj="${actionType.id}-ActionTypeSelectOption"]`
|
||||
);
|
||||
actionOption.first().simulate('click');
|
||||
const actionGroupsSelect = wrapper.find(
|
||||
`[data-test-subj="addNewActionConnectorActionGroup-0"]`
|
||||
);
|
||||
expect((actionGroupsSelect.first().props() as any).options).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "addNewActionConnectorActionGroup-0-option-default",
|
||||
"inputDisplay": "Default",
|
||||
"value": "default",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders available connectors for the selected action type', async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
`[data-test-subj="${actionType.id}-ActionTypeSelectOption"]`
|
||||
);
|
||||
actionOption.first().simulate('click');
|
||||
const combobox = wrapper.find(`[data-test-subj="selectActionConnector-${actionType.id}"]`);
|
||||
expect((combobox.first().props() as any).options).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "test",
|
||||
"key": "test",
|
||||
"label": "Test connector ",
|
||||
},
|
||||
Object {
|
||||
"id": "test2",
|
||||
"key": "test2",
|
||||
"label": "Test connector 2 (preconfigured)",
|
||||
},
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Object {
|
||||
"id": "test",
|
||||
"key": "test",
|
||||
"label": "Test connector ",
|
||||
},
|
||||
Object {
|
||||
"id": "test2",
|
||||
"key": "test2",
|
||||
"label": "Test connector 2 (preconfigured)",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders only preconfigured connectors for the selected preconfigured action type', async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]');
|
||||
actionOption.first().simulate('click');
|
||||
const combobox = wrapper.find('[data-test-subj="selectActionConnector-preconfigured"]');
|
||||
expect((combobox.first().props() as any).options).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "test3",
|
||||
"key": "test3",
|
||||
"label": "Preconfigured Only (preconfigured)",
|
||||
},
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Object {
|
||||
"id": "test3",
|
||||
"key": "test3",
|
||||
"label": "Preconfigured Only (preconfigured)",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not render "Add connector" button for preconfigured only action type', async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]');
|
||||
actionOption.first().simulate('click');
|
||||
const preconfigPannel = wrapper.find('[data-test-subj="alertActionAccordion-default"]');
|
||||
|
@ -378,7 +401,7 @@ describe('action_form', () => {
|
|||
});
|
||||
|
||||
it('renders action types disabled by license', async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
'[data-test-subj="disabled-by-license-ActionTypeSelectOption"]'
|
||||
);
|
||||
|
@ -391,7 +414,7 @@ describe('action_form', () => {
|
|||
});
|
||||
|
||||
it(`shouldn't render action types without params component`, async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
`[data-test-subj="${actionTypeWithoutParams.id}-ActionTypeSelectOption"]`
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, Suspense, useState, useEffect } from 'react';
|
||||
import React, { Fragment, useState, useEffect, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
|
@ -14,25 +14,13 @@ import {
|
|||
EuiIcon,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
EuiFormRow,
|
||||
EuiComboBox,
|
||||
EuiKeyPadMenuItem,
|
||||
EuiAccordion,
|
||||
EuiButtonIcon,
|
||||
EuiEmptyPrompt,
|
||||
EuiButtonEmpty,
|
||||
EuiToolTip,
|
||||
EuiIconTip,
|
||||
EuiLink,
|
||||
EuiCallOut,
|
||||
EuiHorizontalRule,
|
||||
EuiText,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import { HttpSetup, ToastsSetup, ApplicationStart, DocLinksStart } from 'kibana/public';
|
||||
import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/action_connector_api';
|
||||
import {
|
||||
IErrorObject,
|
||||
ActionTypeModel,
|
||||
ActionTypeRegistryContract,
|
||||
AlertAction,
|
||||
|
@ -43,15 +31,19 @@ import {
|
|||
} from '../../../types';
|
||||
import { SectionLoading } from '../../components/section_loading';
|
||||
import { ConnectorAddModal } from './connector_add_modal';
|
||||
import { ActionTypeForm, ActionTypeFormProps } from './action_type_form';
|
||||
import { AddConnectorInline } from './connector_add_inline';
|
||||
import { actionTypeCompare } from '../../lib/action_type_compare';
|
||||
import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled';
|
||||
import { VIEW_LICENSE_OPTIONS_LINK, DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { ActionGroup } from '../../../../../alerts/common';
|
||||
|
||||
interface ActionAccordionFormProps {
|
||||
export interface ActionAccordionFormProps {
|
||||
actions: AlertAction[];
|
||||
defaultActionGroupId: string;
|
||||
actionGroups?: ActionGroup[];
|
||||
setActionIdByIndex: (id: string, index: number) => void;
|
||||
setActionGroupIdByIndex?: (group: string, index: number) => void;
|
||||
setAlertProperty: (actions: AlertAction[]) => void;
|
||||
setActionParamsProperty: (key: string, value: any, index: number) => void;
|
||||
http: HttpSetup;
|
||||
|
@ -74,7 +66,9 @@ interface ActiveActionConnectorState {
|
|||
export const ActionForm = ({
|
||||
actions,
|
||||
defaultActionGroupId,
|
||||
actionGroups,
|
||||
setActionIdByIndex,
|
||||
setActionGroupIdByIndex,
|
||||
setAlertProperty,
|
||||
setActionParamsProperty,
|
||||
http,
|
||||
|
@ -88,8 +82,6 @@ export const ActionForm = ({
|
|||
capabilities,
|
||||
docLinks,
|
||||
}: ActionAccordionFormProps) => {
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
const [addModalVisible, setAddModalVisibility] = useState<boolean>(false);
|
||||
const [activeActionItem, setActiveActionItem] = useState<ActiveActionConnectorState | undefined>(
|
||||
undefined
|
||||
|
@ -101,6 +93,10 @@ export const ActionForm = ({
|
|||
const [actionTypesIndex, setActionTypesIndex] = useState<ActionTypeIndex | undefined>(undefined);
|
||||
const [emptyActionsIds, setEmptyActionsIds] = useState<string[]>([]);
|
||||
|
||||
const closeAddConnectorModal = useCallback(() => setAddModalVisibility(false), [
|
||||
setAddModalVisibility,
|
||||
]);
|
||||
|
||||
// load action types
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
@ -183,359 +179,6 @@ export const ActionForm = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [actions, connectors]);
|
||||
|
||||
const preconfiguredMessage = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage',
|
||||
{
|
||||
defaultMessage: '(preconfigured)',
|
||||
}
|
||||
);
|
||||
|
||||
const getSelectedOptions = (actionItemId: string) => {
|
||||
const selectedConnector = connectors.find((connector) => connector.id === actionItemId);
|
||||
if (
|
||||
!selectedConnector ||
|
||||
// if selected connector is not preconfigured and action type is for preconfiguration only,
|
||||
// do not show regular connectors of this type
|
||||
(actionTypesIndex &&
|
||||
!actionTypesIndex[selectedConnector.actionTypeId].enabledInConfig &&
|
||||
!selectedConnector.isPreconfigured)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const optionTitle = `${selectedConnector.name} ${
|
||||
selectedConnector.isPreconfigured ? preconfiguredMessage : ''
|
||||
}`;
|
||||
return [
|
||||
{
|
||||
label: optionTitle,
|
||||
value: optionTitle,
|
||||
id: actionItemId,
|
||||
'data-test-subj': 'itemActionConnector',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getActionTypeForm = (
|
||||
actionItem: AlertAction,
|
||||
actionConnector: ActionConnector,
|
||||
actionParamsErrors: {
|
||||
errors: IErrorObject;
|
||||
},
|
||||
index: number
|
||||
) => {
|
||||
if (!actionTypesIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const actionType = actionTypesIndex[actionItem.actionTypeId];
|
||||
|
||||
const optionsList = connectors
|
||||
.filter(
|
||||
(connectorItem) =>
|
||||
connectorItem.actionTypeId === actionItem.actionTypeId &&
|
||||
// include only enabled by config connectors or preconfigured
|
||||
(actionType.enabledInConfig || connectorItem.isPreconfigured)
|
||||
)
|
||||
.map(({ name, id, isPreconfigured }) => ({
|
||||
label: `${name} ${isPreconfigured ? preconfiguredMessage : ''}`,
|
||||
key: id,
|
||||
id,
|
||||
}));
|
||||
const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId);
|
||||
if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null;
|
||||
const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields;
|
||||
const checkEnabledResult = checkActionFormActionTypeEnabled(
|
||||
actionTypesIndex[actionConnector.actionTypeId],
|
||||
connectors.filter((connector) => connector.isPreconfigured)
|
||||
);
|
||||
|
||||
const accordionContent = checkEnabledResult.isEnabled ? (
|
||||
<Fragment>
|
||||
<EuiFlexGroup component="div">
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.actionIdLabel"
|
||||
defaultMessage="{connectorInstance} connector"
|
||||
values={{
|
||||
connectorInstance: actionTypesIndex
|
||||
? actionTypesIndex[actionConnector.actionTypeId].name
|
||||
: actionConnector.actionTypeId,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
labelAppend={
|
||||
canSave &&
|
||||
actionTypesIndex &&
|
||||
actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj={`addNewActionConnectorButton-${actionItem.actionTypeId}`}
|
||||
onClick={() => {
|
||||
setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index });
|
||||
setAddModalVisibility(true);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add connector"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={optionsList}
|
||||
id={`selectActionConnector-${actionItem.id}`}
|
||||
data-test-subj={`selectActionConnector-${actionItem.actionTypeId}`}
|
||||
selectedOptions={getSelectedOptions(actionItem.id)}
|
||||
onChange={(selectedOptions) => {
|
||||
setActionIdByIndex(selectedOptions[0].id ?? '', index);
|
||||
}}
|
||||
isClearable={false}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xl" />
|
||||
{ParamsFieldsComponent ? (
|
||||
<Suspense
|
||||
fallback={
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<ParamsFieldsComponent
|
||||
actionParams={actionItem.params as any}
|
||||
index={index}
|
||||
errors={actionParamsErrors.errors}
|
||||
editAction={setActionParamsProperty}
|
||||
messageVariables={messageVariables}
|
||||
defaultMessage={defaultActionMessage ?? undefined}
|
||||
docLinks={docLinks}
|
||||
http={http}
|
||||
toastNotifications={toastNotifications}
|
||||
actionConnector={actionConnector}
|
||||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
</Fragment>
|
||||
) : (
|
||||
checkEnabledResult.messageCard
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={true}
|
||||
id={index.toString()}
|
||||
className="actAccordionActionForm"
|
||||
buttonContentClassName="actAccordionActionForm__button"
|
||||
data-test-subj={`alertActionAccordion-${defaultActionGroupId}`}
|
||||
buttonContent={
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={actionTypeRegistered.iconClass} size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<div>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
defaultMessage="{actionConnectorName}"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.existingAlertActionTypeEditTitle"
|
||||
values={{
|
||||
actionConnectorName: `${actionConnector.name} ${
|
||||
actionConnector.isPreconfigured ? preconfiguredMessage : ''
|
||||
}`,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{checkEnabledResult.isEnabled === false && (
|
||||
<Fragment>
|
||||
<EuiIconTip
|
||||
type="alert"
|
||||
color="danger"
|
||||
content={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.actionDisabledTitle',
|
||||
{
|
||||
defaultMessage: 'This action is disabled',
|
||||
}
|
||||
)}
|
||||
position="right"
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
extraAction={
|
||||
<EuiButtonIcon
|
||||
iconType="cross"
|
||||
color="danger"
|
||||
className="actAccordionActionForm__extraAction"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
const updatedActions = actions.filter(
|
||||
(_item: AlertAction, i: number) => i !== index
|
||||
);
|
||||
setAlertProperty(updatedActions);
|
||||
setIsAddActionPanelOpen(
|
||||
updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length ===
|
||||
0
|
||||
);
|
||||
setActiveActionItem(undefined);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
paddingSize="l"
|
||||
>
|
||||
{accordionContent}
|
||||
</EuiAccordion>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getAddConnectorsForm = (actionItem: AlertAction, index: number) => {
|
||||
const actionTypeName = actionTypesIndex
|
||||
? actionTypesIndex[actionItem.actionTypeId].name
|
||||
: actionItem.actionTypeId;
|
||||
const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId);
|
||||
if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null;
|
||||
|
||||
const noConnectorsLabel = (
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel"
|
||||
defaultMessage="No {actionTypeName} connectors"
|
||||
values={{
|
||||
actionTypeName,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={true}
|
||||
id={index.toString()}
|
||||
className="actAccordionActionForm"
|
||||
buttonContentClassName="actAccordionActionForm__button"
|
||||
data-test-subj={`alertActionAccordion-${defaultActionGroupId}`}
|
||||
buttonContent={
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={actionTypeRegistered.iconClass} size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="{actionConnectorName}"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.newAlertActionTypeEditTitle"
|
||||
values={{
|
||||
actionConnectorName: actionTypeRegistered.actionTypeTitle,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
extraAction={
|
||||
<EuiButtonIcon
|
||||
iconType="cross"
|
||||
color="danger"
|
||||
className="actAccordionActionForm__extraAction"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
const updatedActions = actions.filter(
|
||||
(_item: AlertAction, i: number) => i !== index
|
||||
);
|
||||
setAlertProperty(updatedActions);
|
||||
setIsAddActionPanelOpen(
|
||||
updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length ===
|
||||
0
|
||||
);
|
||||
setActiveActionItem(undefined);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
paddingSize="l"
|
||||
>
|
||||
{canSave ? (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
emptyActionsIds.find((emptyId: string) => actionItem.id === emptyId) ? (
|
||||
noConnectorsLabel
|
||||
) : (
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTitle',
|
||||
{
|
||||
defaultMessage: 'Unable to load connector.',
|
||||
}
|
||||
)}
|
||||
color="warning"
|
||||
/>
|
||||
)
|
||||
}
|
||||
actions={[
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
size="s"
|
||||
data-test-subj="createActionConnectorButton"
|
||||
onClick={() => {
|
||||
setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index });
|
||||
setAddModalVisibility(true);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel"
|
||||
defaultMessage="Create a connector"
|
||||
/>
|
||||
</EuiButton>,
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<EuiCallOut title={noConnectorsLabel}>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.unauthorizedToCreateForEmptyConnectors"
|
||||
defaultMessage="Only authorized users can configure a connector. Contact your administrator."
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
</EuiAccordion>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
function addActionType(actionTypeModel: ActionTypeModel) {
|
||||
if (!defaultActionGroupId) {
|
||||
toastNotifications!.addDanger({
|
||||
|
@ -628,116 +271,172 @@ export const ActionForm = ({
|
|||
});
|
||||
}
|
||||
|
||||
const alertActionsList = actions.map((actionItem: AlertAction, index: number) => {
|
||||
const actionConnector = connectors.find((field) => field.id === actionItem.id);
|
||||
// connectors doesn't exists
|
||||
if (!actionConnector) {
|
||||
return getAddConnectorsForm(actionItem, index);
|
||||
}
|
||||
|
||||
const actionErrors: { errors: IErrorObject } = actionTypeRegistry
|
||||
.get(actionItem.actionTypeId)
|
||||
?.validateParams(actionItem.params);
|
||||
|
||||
return getActionTypeForm(actionItem, actionConnector, actionErrors, index);
|
||||
});
|
||||
|
||||
return (
|
||||
return isLoadingConnectors ? (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.loadingConnectorsDescription"
|
||||
defaultMessage="Loading connectors…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
) : (
|
||||
<Fragment>
|
||||
{isLoadingConnectors ? (
|
||||
<SectionLoading>
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.loadingConnectorsDescription"
|
||||
defaultMessage="Loading connectors…"
|
||||
defaultMessage="Actions"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.actionSectionsTitle"
|
||||
/>
|
||||
</SectionLoading>
|
||||
) : (
|
||||
<Fragment>
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
defaultMessage="Actions"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.actionSectionsTitle"
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
{actionTypesIndex &&
|
||||
actions.map((actionItem: AlertAction, index: number) => {
|
||||
const actionConnector = connectors.find((field) => field.id === actionItem.id);
|
||||
// connectors doesn't exists
|
||||
if (!actionConnector) {
|
||||
return (
|
||||
<AddConnectorInline
|
||||
actionTypesIndex={actionTypesIndex}
|
||||
actionItem={actionItem}
|
||||
index={index}
|
||||
key={`action-form-action-at-${index}`}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
capabilities={capabilities}
|
||||
emptyActionsIds={emptyActionsIds}
|
||||
onDeleteConnector={() => {
|
||||
const updatedActions = actions.filter(
|
||||
(_item: AlertAction, i: number) => i !== index
|
||||
);
|
||||
setAlertProperty(updatedActions);
|
||||
setIsAddActionPanelOpen(
|
||||
updatedActions.filter((item: AlertAction) => item.id !== actionItem.id)
|
||||
.length === 0
|
||||
);
|
||||
setActiveActionItem(undefined);
|
||||
}}
|
||||
onAddConnector={() => {
|
||||
setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index });
|
||||
setAddModalVisibility(true);
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
{alertActionsList}
|
||||
{isAddActionPanelOpen === false ? (
|
||||
<div>
|
||||
<EuiHorizontalRule />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
data-test-subj="addAlertActionButton"
|
||||
onClick={() => setIsAddActionPanelOpen(true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.addActionButtonLabel"
|
||||
defaultMessage="Add action"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
) : null}
|
||||
{isAddActionPanelOpen ? (
|
||||
<Fragment>
|
||||
<EuiFlexGroup id="alertActionTypeTitle" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
);
|
||||
}
|
||||
|
||||
const actionParamsErrors: ActionTypeFormProps['actionParamsErrors'] = actionTypeRegistry
|
||||
.get(actionItem.actionTypeId)
|
||||
?.validateParams(actionItem.params);
|
||||
|
||||
return (
|
||||
<ActionTypeForm
|
||||
actionItem={actionItem}
|
||||
actionConnector={actionConnector}
|
||||
actionParamsErrors={actionParamsErrors}
|
||||
index={index}
|
||||
key={`action-form-action-at-${index}`}
|
||||
setActionParamsProperty={setActionParamsProperty}
|
||||
actionTypesIndex={actionTypesIndex}
|
||||
connectors={connectors}
|
||||
http={http}
|
||||
toastNotifications={toastNotifications}
|
||||
docLinks={docLinks}
|
||||
capabilities={capabilities}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
defaultActionMessage={defaultActionMessage}
|
||||
messageVariables={messageVariables}
|
||||
actionGroups={actionGroups}
|
||||
setActionGroupIdByIndex={setActionGroupIdByIndex}
|
||||
onAddConnector={() => {
|
||||
setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index });
|
||||
setAddModalVisibility(true);
|
||||
}}
|
||||
onConnectorSelected={(id: string) => {
|
||||
setActionIdByIndex(id, index);
|
||||
}}
|
||||
onDeleteAction={() => {
|
||||
const updatedActions = actions.filter(
|
||||
(_item: AlertAction, i: number) => i !== index
|
||||
);
|
||||
setAlertProperty(updatedActions);
|
||||
setIsAddActionPanelOpen(
|
||||
updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length ===
|
||||
0
|
||||
);
|
||||
setActiveActionItem(undefined);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<EuiSpacer size="m" />
|
||||
{isAddActionPanelOpen ? (
|
||||
<Fragment>
|
||||
<EuiFlexGroup id="alertActionTypeTitle" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
defaultMessage="Select an action type"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{hasDisabledByLicenseActionTypes && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<EuiLink
|
||||
href={VIEW_LICENSE_OPTIONS_LINK}
|
||||
target="_blank"
|
||||
external
|
||||
className="actActionForm__getMoreActionsLink"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Select an action type"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle"
|
||||
defaultMessage="Get more actions"
|
||||
id="xpack.triggersActionsUI.sections.actionForm.getMoreActionsTitle"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{hasDisabledByLicenseActionTypes && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<EuiLink
|
||||
href={VIEW_LICENSE_OPTIONS_LINK}
|
||||
target="_blank"
|
||||
external
|
||||
className="actActionForm__getMoreActionsLink"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Get more actions"
|
||||
id="xpack.triggersActionsUI.sections.actionForm.getMoreActionsTitle"
|
||||
/>
|
||||
</EuiLink>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup gutterSize="m" wrap>
|
||||
{isLoadingActionTypes ? (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.loadingActionTypesDescription"
|
||||
defaultMessage="Loading action types…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
) : (
|
||||
actionTypeNodes
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiLink>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup gutterSize="m" wrap>
|
||||
{isLoadingActionTypes ? (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.loadingActionTypesDescription"
|
||||
defaultMessage="Loading action types…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
) : (
|
||||
actionTypeNodes
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
) : (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
data-test-subj="addAlertActionButton"
|
||||
onClick={() => setIsAddActionPanelOpen(true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.addActionButtonLabel"
|
||||
defaultMessage="Add action"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{actionTypesIndex && activeActionItem ? (
|
||||
{actionTypesIndex && activeActionItem && addModalVisible ? (
|
||||
<ConnectorAddModal
|
||||
key={activeActionItem.index}
|
||||
actionType={actionTypesIndex[activeActionItem.actionTypeId]}
|
||||
addModalVisible={addModalVisible}
|
||||
setAddModalVisibility={setAddModalVisibility}
|
||||
onClose={closeAddConnectorModal}
|
||||
postSaveEventHandler={(savedAction: ActionConnector) => {
|
||||
connectors.push(savedAction);
|
||||
setActionIdByIndex(savedAction.id, activeActionItem.index);
|
||||
|
|
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* 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, { Fragment, Suspense, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
EuiFormRow,
|
||||
EuiComboBox,
|
||||
EuiAccordion,
|
||||
EuiButtonIcon,
|
||||
EuiButtonEmpty,
|
||||
EuiIconTip,
|
||||
EuiText,
|
||||
EuiFormLabel,
|
||||
EuiFormControlLayout,
|
||||
EuiSuperSelect,
|
||||
EuiLoadingSpinner,
|
||||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
import { IErrorObject, AlertAction, ActionTypeIndex, ActionConnector } from '../../../types';
|
||||
import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { ActionAccordionFormProps } from './action_form';
|
||||
|
||||
export type ActionTypeFormProps = {
|
||||
actionItem: AlertAction;
|
||||
actionConnector: ActionConnector;
|
||||
actionParamsErrors: {
|
||||
errors: IErrorObject;
|
||||
};
|
||||
index: number;
|
||||
onAddConnector: () => void;
|
||||
onConnectorSelected: (id: string) => void;
|
||||
onDeleteAction: () => void;
|
||||
setActionParamsProperty: (key: string, value: any, index: number) => void;
|
||||
actionTypesIndex: ActionTypeIndex;
|
||||
connectors: ActionConnector[];
|
||||
} & Pick<
|
||||
ActionAccordionFormProps,
|
||||
| 'defaultActionGroupId'
|
||||
| 'actionGroups'
|
||||
| 'setActionGroupIdByIndex'
|
||||
| 'setActionParamsProperty'
|
||||
| 'http'
|
||||
| 'actionTypeRegistry'
|
||||
| 'toastNotifications'
|
||||
| 'docLinks'
|
||||
| 'messageVariables'
|
||||
| 'defaultActionMessage'
|
||||
| 'capabilities'
|
||||
>;
|
||||
|
||||
const preconfiguredMessage = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage',
|
||||
{
|
||||
defaultMessage: '(preconfigured)',
|
||||
}
|
||||
);
|
||||
|
||||
export const ActionTypeForm = ({
|
||||
actionItem,
|
||||
actionConnector,
|
||||
actionParamsErrors,
|
||||
index,
|
||||
onAddConnector,
|
||||
onConnectorSelected,
|
||||
onDeleteAction,
|
||||
setActionParamsProperty,
|
||||
actionTypesIndex,
|
||||
connectors,
|
||||
http,
|
||||
toastNotifications,
|
||||
docLinks,
|
||||
capabilities,
|
||||
actionTypeRegistry,
|
||||
defaultActionGroupId,
|
||||
defaultActionMessage,
|
||||
messageVariables,
|
||||
actionGroups,
|
||||
setActionGroupIdByIndex,
|
||||
}: ActionTypeFormProps) => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
const getSelectedOptions = (actionItemId: string) => {
|
||||
const selectedConnector = connectors.find((connector) => connector.id === actionItemId);
|
||||
if (
|
||||
!selectedConnector ||
|
||||
// if selected connector is not preconfigured and action type is for preconfiguration only,
|
||||
// do not show regular connectors of this type
|
||||
(actionTypesIndex &&
|
||||
!actionTypesIndex[selectedConnector.actionTypeId].enabledInConfig &&
|
||||
!selectedConnector.isPreconfigured)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const optionTitle = `${selectedConnector.name} ${
|
||||
selectedConnector.isPreconfigured ? preconfiguredMessage : ''
|
||||
}`;
|
||||
return [
|
||||
{
|
||||
label: optionTitle,
|
||||
value: optionTitle,
|
||||
id: actionItemId,
|
||||
'data-test-subj': 'itemActionConnector',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const actionType = actionTypesIndex[actionItem.actionTypeId];
|
||||
|
||||
const optionsList = connectors
|
||||
.filter(
|
||||
(connectorItem) =>
|
||||
connectorItem.actionTypeId === actionItem.actionTypeId &&
|
||||
// include only enabled by config connectors or preconfigured
|
||||
(actionType.enabledInConfig || connectorItem.isPreconfigured)
|
||||
)
|
||||
.map(({ name, id, isPreconfigured }) => ({
|
||||
label: `${name} ${isPreconfigured ? preconfiguredMessage : ''}`,
|
||||
key: id,
|
||||
id,
|
||||
}));
|
||||
const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId);
|
||||
if (!actionTypeRegistered) return null;
|
||||
|
||||
const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields;
|
||||
const checkEnabledResult = checkActionFormActionTypeEnabled(
|
||||
actionTypesIndex[actionConnector.actionTypeId],
|
||||
connectors.filter((connector) => connector.isPreconfigured)
|
||||
);
|
||||
|
||||
const defaultActionGroup = actionGroups?.find(({ id }) => id === defaultActionGroupId);
|
||||
const selectedActionGroup =
|
||||
actionGroups?.find(({ id }) => id === actionItem.group) ?? defaultActionGroup;
|
||||
|
||||
const accordionContent = checkEnabledResult.isEnabled ? (
|
||||
<Fragment>
|
||||
{actionGroups && selectedActionGroup && setActionGroupIdByIndex && (
|
||||
<Fragment>
|
||||
<EuiFlexGroup component="div">
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiFormControlLayout
|
||||
fullWidth
|
||||
prepend={
|
||||
<EuiFormLabel
|
||||
htmlFor={`addNewActionConnectorActionGroup-${actionItem.actionTypeId}`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.actionRunWhenInActionGroup"
|
||||
defaultMessage="Run When"
|
||||
/>
|
||||
</EuiFormLabel>
|
||||
}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
fullWidth
|
||||
id={`addNewActionConnectorActionGroup-${actionItem.actionTypeId}`}
|
||||
data-test-subj={`addNewActionConnectorActionGroup-${index}`}
|
||||
options={actionGroups.map(({ id: value, name }) => ({
|
||||
value,
|
||||
inputDisplay: name,
|
||||
'data-test-subj': `addNewActionConnectorActionGroup-${index}-option-${value}`,
|
||||
}))}
|
||||
valueOfSelected={selectedActionGroup.id}
|
||||
onChange={(group) => {
|
||||
setActionGroupIdByIndex(group, index);
|
||||
}}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
</Fragment>
|
||||
)}
|
||||
<EuiFlexGroup component="div">
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.actionIdLabel"
|
||||
defaultMessage="{connectorInstance} connector"
|
||||
values={{
|
||||
connectorInstance: actionTypesIndex
|
||||
? actionTypesIndex[actionConnector.actionTypeId].name
|
||||
: actionConnector.actionTypeId,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
labelAppend={
|
||||
canSave &&
|
||||
actionTypesIndex &&
|
||||
actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj={`addNewActionConnectorButton-${actionItem.actionTypeId}`}
|
||||
onClick={onAddConnector}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add connector"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={optionsList}
|
||||
id={`selectActionConnector-${actionItem.id}`}
|
||||
data-test-subj={`selectActionConnector-${actionItem.actionTypeId}`}
|
||||
selectedOptions={getSelectedOptions(actionItem.id)}
|
||||
onChange={(selectedOptions) => {
|
||||
onConnectorSelected(selectedOptions[0].id ?? '');
|
||||
}}
|
||||
isClearable={false}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xl" />
|
||||
{ParamsFieldsComponent ? (
|
||||
<Suspense
|
||||
fallback={
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<ParamsFieldsComponent
|
||||
actionParams={actionItem.params as any}
|
||||
index={index}
|
||||
errors={actionParamsErrors.errors}
|
||||
editAction={setActionParamsProperty}
|
||||
messageVariables={messageVariables}
|
||||
defaultMessage={defaultActionMessage ?? undefined}
|
||||
docLinks={docLinks}
|
||||
http={http}
|
||||
toastNotifications={toastNotifications}
|
||||
actionConnector={actionConnector}
|
||||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
</Fragment>
|
||||
) : (
|
||||
checkEnabledResult.messageCard
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={true}
|
||||
id={index.toString()}
|
||||
onToggle={setIsOpen}
|
||||
paddingSize="l"
|
||||
className="actAccordionActionForm"
|
||||
buttonContentClassName="actAccordionActionForm__button"
|
||||
data-test-subj={`alertActionAccordion-${index}`}
|
||||
buttonContent={
|
||||
<EuiFlexGroup gutterSize="l" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={actionTypeRegistered.iconClass} size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<div>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
defaultMessage="{actionConnectorName}"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.existingAlertActionTypeEditTitle"
|
||||
values={{
|
||||
actionConnectorName: `${actionConnector.name} ${
|
||||
actionConnector.isPreconfigured ? preconfiguredMessage : ''
|
||||
}`,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{selectedActionGroup && !isOpen && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge>{selectedActionGroup.name}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
{checkEnabledResult.isEnabled === false && (
|
||||
<Fragment>
|
||||
<EuiIconTip
|
||||
type="alert"
|
||||
color="danger"
|
||||
content={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.actionDisabledTitle',
|
||||
{
|
||||
defaultMessage: 'This action is disabled',
|
||||
}
|
||||
)}
|
||||
position="right"
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
extraAction={
|
||||
<EuiButtonIcon
|
||||
iconType="minusInCircle"
|
||||
color="danger"
|
||||
className="actAccordionActionForm__extraAction"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
)}
|
||||
onClick={onDeleteAction}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{accordionContent}
|
||||
</EuiAccordion>
|
||||
<EuiSpacer size="m" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* 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, { Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
EuiAccordion,
|
||||
EuiButtonIcon,
|
||||
EuiEmptyPrompt,
|
||||
EuiCallOut,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { AlertAction, ActionTypeIndex } from '../../../types';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { ActionAccordionFormProps } from './action_form';
|
||||
|
||||
type AddConnectorInFormProps = {
|
||||
actionTypesIndex: ActionTypeIndex;
|
||||
actionItem: AlertAction;
|
||||
index: number;
|
||||
onAddConnector: () => void;
|
||||
onDeleteConnector: () => void;
|
||||
emptyActionsIds: string[];
|
||||
} & Pick<ActionAccordionFormProps, 'actionTypeRegistry' | 'defaultActionGroupId' | 'capabilities'>;
|
||||
|
||||
export const AddConnectorInline = ({
|
||||
actionTypesIndex,
|
||||
actionItem,
|
||||
index,
|
||||
onAddConnector,
|
||||
onDeleteConnector,
|
||||
actionTypeRegistry,
|
||||
emptyActionsIds,
|
||||
defaultActionGroupId,
|
||||
capabilities,
|
||||
}: AddConnectorInFormProps) => {
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
const actionTypeName = actionTypesIndex
|
||||
? actionTypesIndex[actionItem.actionTypeId].name
|
||||
: actionItem.actionTypeId;
|
||||
const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId);
|
||||
if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null;
|
||||
|
||||
const noConnectorsLabel = (
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel"
|
||||
defaultMessage="No {actionTypeName} connectors"
|
||||
values={{
|
||||
actionTypeName,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={true}
|
||||
id={index.toString()}
|
||||
className="actAccordionActionForm"
|
||||
buttonContentClassName="actAccordionActionForm__button"
|
||||
data-test-subj={`alertActionAccordion-${index}`}
|
||||
buttonContent={
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={actionTypeRegistered.iconClass} size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="{actionConnectorName}"
|
||||
id="xpack.triggersActionsUI.sections.alertForm.newAlertActionTypeEditTitle"
|
||||
values={{
|
||||
actionConnectorName: actionTypeRegistered.actionTypeTitle,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
extraAction={
|
||||
<EuiButtonIcon
|
||||
iconType="minusInCircle"
|
||||
color="danger"
|
||||
className="actAccordionActionForm__extraAction"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
)}
|
||||
onClick={onDeleteConnector}
|
||||
/>
|
||||
}
|
||||
paddingSize="l"
|
||||
>
|
||||
{canSave ? (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
emptyActionsIds.find((emptyId: string) => actionItem.id === emptyId) ? (
|
||||
noConnectorsLabel
|
||||
) : (
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTitle',
|
||||
{
|
||||
defaultMessage: 'Unable to load connector.',
|
||||
}
|
||||
)}
|
||||
color="warning"
|
||||
/>
|
||||
)
|
||||
}
|
||||
actions={[
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
size="s"
|
||||
data-test-subj="createActionConnectorButton"
|
||||
onClick={onAddConnector}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel"
|
||||
defaultMessage="Create a connector"
|
||||
/>
|
||||
</EuiButton>,
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<EuiCallOut title={noConnectorsLabel}>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertForm.unauthorizedToCreateForEmptyConnectors"
|
||||
defaultMessage="Only authorized users can configure a connector. Contact your administrator."
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
</EuiAccordion>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
|
@ -65,8 +65,7 @@ describe('connector_add_modal', () => {
|
|||
|
||||
const wrapper = mountWithIntl(
|
||||
<ConnectorAddModal
|
||||
addModalVisible={true}
|
||||
setAddModalVisibility={() => {}}
|
||||
onClose={() => {}}
|
||||
actionType={actionType}
|
||||
http={deps!.http}
|
||||
actionTypeRegistry={deps!.actionTypeRegistry}
|
||||
|
|
|
@ -32,8 +32,7 @@ import {
|
|||
|
||||
interface ConnectorAddModalProps {
|
||||
actionType: ActionType;
|
||||
addModalVisible: boolean;
|
||||
setAddModalVisibility: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onClose: () => void;
|
||||
postSaveEventHandler?: (savedAction: ActionConnector) => void;
|
||||
http: HttpSetup;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
|
@ -48,8 +47,7 @@ interface ConnectorAddModalProps {
|
|||
|
||||
export const ConnectorAddModal = ({
|
||||
actionType,
|
||||
addModalVisible,
|
||||
setAddModalVisibility,
|
||||
onClose,
|
||||
postSaveEventHandler,
|
||||
http,
|
||||
toastNotifications,
|
||||
|
@ -79,14 +77,11 @@ export const ConnectorAddModal = ({
|
|||
>(undefined);
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
setAddModalVisibility(false);
|
||||
setConnector(initialConnector);
|
||||
setServerError(undefined);
|
||||
}, [initialConnector, setAddModalVisibility]);
|
||||
onClose();
|
||||
}, [initialConnector, onClose]);
|
||||
|
||||
if (!addModalVisible) {
|
||||
return null;
|
||||
}
|
||||
const actionTypeModel = actionTypeRegistry.get(actionType.id);
|
||||
const errors = {
|
||||
...actionTypeModel?.validateConnector(connector).errors,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* 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, { Fragment, useState, useEffect, Suspense } from 'react';
|
||||
import React, { Fragment, useState, useEffect, Suspense, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
|
@ -153,9 +153,17 @@ export const AlertForm = ({
|
|||
setAlertTypeModel(alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null);
|
||||
}, [alert, alertTypeRegistry]);
|
||||
|
||||
const setAlertProperty = (key: string, value: any) => {
|
||||
dispatch({ command: { type: 'setProperty' }, payload: { key, value } });
|
||||
};
|
||||
const setAlertProperty = useCallback(
|
||||
(key: string, value: any) => {
|
||||
dispatch({ command: { type: 'setProperty' }, payload: { key, value } });
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setActions = useCallback(
|
||||
(updatedActions: AlertAction[]) => setAlertProperty('actions', updatedActions),
|
||||
[setAlertProperty]
|
||||
);
|
||||
|
||||
const setAlertParams = (key: string, value: any) => {
|
||||
dispatch({ command: { type: 'setAlertParams' }, payload: { key, value } });
|
||||
|
@ -169,9 +177,12 @@ export const AlertForm = ({
|
|||
dispatch({ command: { type: 'setAlertActionProperty' }, payload: { key, value, index } });
|
||||
};
|
||||
|
||||
const setActionParamsProperty = (key: string, value: any, index: number) => {
|
||||
dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } });
|
||||
};
|
||||
const setActionParamsProperty = useCallback(
|
||||
(key: string, value: any, index: number) => {
|
||||
dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } });
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : [];
|
||||
|
||||
|
@ -202,6 +213,7 @@ export const AlertForm = ({
|
|||
label={item.name}
|
||||
onClick={() => {
|
||||
setAlertProperty('alertTypeId', item.id);
|
||||
setActions([]);
|
||||
setAlertTypeModel(item);
|
||||
setAlertProperty('params', {});
|
||||
if (alertTypesIndex && alertTypesIndex.has(item.id)) {
|
||||
|
@ -289,26 +301,25 @@ export const AlertForm = ({
|
|||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
{canShowActions && defaultActionGroupId ? (
|
||||
{canShowActions &&
|
||||
defaultActionGroupId &&
|
||||
alertTypeModel &&
|
||||
alertTypesIndex?.has(alert.alertTypeId) ? (
|
||||
<ActionForm
|
||||
actions={alert.actions}
|
||||
setHasActionsDisabled={setHasActionsDisabled}
|
||||
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
|
||||
messageVariables={
|
||||
alertTypesIndex && alertTypesIndex.has(alert.alertTypeId)
|
||||
? actionVariablesFromAlertType(alertTypesIndex.get(alert.alertTypeId)!).sort((a, b) =>
|
||||
a.name.toUpperCase().localeCompare(b.name.toUpperCase())
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
messageVariables={actionVariablesFromAlertType(
|
||||
alertTypesIndex.get(alert.alertTypeId)!
|
||||
).sort((a, b) => a.name.toUpperCase().localeCompare(b.name.toUpperCase()))}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
actionGroups={alertTypesIndex.get(alert.alertTypeId)!.actionGroups}
|
||||
setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)}
|
||||
setAlertProperty={(updatedActions: AlertAction[]) =>
|
||||
setAlertProperty('actions', updatedActions)
|
||||
}
|
||||
setActionParamsProperty={(key: string, value: any, index: number) =>
|
||||
setActionParamsProperty(key, value, index)
|
||||
setActionGroupIdByIndex={(group: string, index: number) =>
|
||||
setActionProperty('group', group, index)
|
||||
}
|
||||
setAlertProperty={setActions}
|
||||
setActionParamsProperty={setActionParamsProperty}
|
||||
http={http}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
defaultActionMessage={alertTypeModel?.defaultActionMessage}
|
||||
|
|
|
@ -55,6 +55,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await nameInput.click();
|
||||
}
|
||||
|
||||
async function defineAlwaysFiringAlert(alertName: string) {
|
||||
await pageObjects.triggersActionsUI.clickCreateAlertButton();
|
||||
await testSubjects.setValue('alertNameInput', alertName);
|
||||
await testSubjects.click('test.always-firing-SelectOption');
|
||||
}
|
||||
|
||||
describe('create alert', function () {
|
||||
before(async () => {
|
||||
await pageObjects.common.navigateToApp('triggersActions');
|
||||
|
@ -106,6 +112,57 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id));
|
||||
});
|
||||
|
||||
it('should create an alert with actions in multiple groups', async () => {
|
||||
const alertName = generateUniqueKey();
|
||||
await defineAlwaysFiringAlert(alertName);
|
||||
|
||||
// create Slack connector and attach an action using it
|
||||
await testSubjects.click('.slack-ActionTypeSelectOption');
|
||||
await testSubjects.click('addNewActionConnectorButton-.slack');
|
||||
const slackConnectorName = generateUniqueKey();
|
||||
await testSubjects.setValue('nameInput', slackConnectorName);
|
||||
await testSubjects.setValue('slackWebhookUrlInput', 'https://test');
|
||||
await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)');
|
||||
const createdConnectorToastTitle = await pageObjects.common.closeToast();
|
||||
expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`);
|
||||
await testSubjects.setValue('messageTextArea', 'test message ');
|
||||
await (
|
||||
await find.byCssSelector(
|
||||
'[data-test-subj="alertActionAccordion-0"] [data-test-subj="messageTextArea"]'
|
||||
)
|
||||
).type('some text ');
|
||||
|
||||
await testSubjects.click('addAlertActionButton');
|
||||
await testSubjects.click('.slack-ActionTypeSelectOption');
|
||||
await testSubjects.setValue('messageTextArea', 'test message ');
|
||||
await (
|
||||
await find.byCssSelector(
|
||||
'[data-test-subj="alertActionAccordion-1"] [data-test-subj="messageTextArea"]'
|
||||
)
|
||||
).type('some text ');
|
||||
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-1');
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-1-option-other');
|
||||
|
||||
await testSubjects.click('saveAlertButton');
|
||||
const toastTitle = await pageObjects.common.closeToast();
|
||||
expect(toastTitle).to.eql(`Created alert "${alertName}"`);
|
||||
await pageObjects.triggersActionsUI.searchAlerts(alertName);
|
||||
const searchResultsAfterSave = await pageObjects.triggersActionsUI.getAlertsList();
|
||||
expect(searchResultsAfterSave).to.eql([
|
||||
{
|
||||
name: alertName,
|
||||
tagsText: '',
|
||||
alertType: 'Always Firing',
|
||||
interval: '1m',
|
||||
},
|
||||
]);
|
||||
|
||||
// clean up created alert
|
||||
const alertsToDelete = await getAlertsByName(alertName);
|
||||
await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id));
|
||||
});
|
||||
|
||||
it('should show save confirmation before creating alert with no actions', async () => {
|
||||
const alertName = generateUniqueKey();
|
||||
await defineAlert(alertName);
|
||||
|
|
|
@ -78,6 +78,7 @@ function createAlwaysFiringAlertType(alerts: AlertingSetup) {
|
|||
{ id: 'default', name: 'Default' },
|
||||
{ id: 'other', name: 'Other' },
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerts',
|
||||
async executor(alertExecutorOptions: any) {
|
||||
const { services, state, params } = alertExecutorOptions;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue