From 01c7c7d2aa982f2371c7333d28be2041fd4d561d Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Tue, 3 Nov 2020 18:47:28 +0000 Subject: [PATCH 01/45] plugged Task Manager lifecycle into status reactively --- .../lib/check_action_type_enabled.scss | 12 +- .../action_connector_form/action_form.tsx | 117 +++++++++++++----- .../connector_add_modal.tsx | 13 +- .../sections/alert_form/alert_form.tsx | 51 +++++--- .../alert_create_flyout.ts | 57 +++++++++ .../fixtures/plugins/alerts/server/plugin.ts | 1 + 6 files changed, 185 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss index 24dbb865742d8d..bb622829e997ab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss @@ -3,9 +3,15 @@ } .actAccordionActionForm { - .euiCard { - box-shadow: none; - } + background-color: $euiColorLightestShade; +} + +.actAccordionActionForm .euiCard { + box-shadow: none; +} + +.actAccordionActionForm__button { + padding: $euiSizeM; } .actConnectorsListGrid { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 51d3b0074ca54c..d8dd2ac06ef36e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -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, Suspense, useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -25,9 +25,12 @@ import { EuiIconTip, EuiLink, EuiCallOut, - EuiHorizontalRule, EuiText, + EuiFormLabel, + EuiFormControlLayout, + EuiSuperSelect, EuiLoadingSpinner, + EuiBadge, } from '@elastic/eui'; import { HttpSetup, ToastsSetup, ApplicationStart, DocLinksStart } from 'kibana/public'; import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/action_connector_api'; @@ -47,11 +50,14 @@ import { actionTypeCompare } from '../../lib/action_type_compare'; import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { ActionGroup } from '../../../../../alerts/common'; 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 +80,9 @@ interface ActiveActionConnectorState { export const ActionForm = ({ actions, defaultActionGroupId, + actionGroups, setActionIdByIndex, + setActionGroupIdByIndex, setAlertProperty, setActionParamsProperty, http, @@ -94,13 +102,17 @@ export const ActionForm = ({ const [activeActionItem, setActiveActionItem] = useState( undefined ); - const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(true); + const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(actions.length === 0); const [connectors, setConnectors] = useState([]); const [isLoadingConnectors, setIsLoadingConnectors] = useState(false); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); const [actionTypesIndex, setActionTypesIndex] = useState(undefined); const [emptyActionsIds, setEmptyActionsIds] = useState([]); + const closeAddConnectorModal = useCallback(() => setAddModalVisibility(false), [ + setAddModalVisibility, + ]); + // load action types useEffect(() => { (async () => { @@ -242,15 +254,53 @@ export const ActionForm = ({ id, })); const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId); - if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; + 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 ? ( + + + + + + } + > + ({ + value, + inputDisplay: name, + 'data-test-subj': `addNewActionConnectorActionGroup-${index}-option-${value}`, + }))} + valueOfSelected={selectedActionGroup.id} + onChange={(group) => { + setActionGroupIdByIndex(group, index); + }} + /> + + + + { setActionIdByIndex(selectedOptions[0].id ?? '', index); @@ -336,11 +386,12 @@ export const ActionForm = ({ + @@ -359,6 +410,9 @@ export const ActionForm = ({ }} /> + + {selectedActionGroup.name} + {checkEnabledResult.isEnabled === false && ( @@ -384,7 +438,7 @@ export const ActionForm = ({ } extraAction={ } - paddingSize="l" > {accordionContent} - + ); }; @@ -438,7 +491,7 @@ export const ActionForm = ({ id={index.toString()} className="actAccordionActionForm" buttonContentClassName="actAccordionActionForm__button" - data-test-subj={`alertActionAccordion-${defaultActionGroupId}`} + data-test-subj={`alertActionAccordion-${index}`} buttonContent={ @@ -658,25 +711,7 @@ export const ActionForm = ({ {alertActionsList} - {isAddActionPanelOpen === false ? ( -
- - - - setIsAddActionPanelOpen(true)} - > - - - - -
- ) : null} + {isAddActionPanelOpen ? ( @@ -724,15 +759,29 @@ export const ActionForm = ({ )} - ) : null} + ) : ( + + + setIsAddActionPanelOpen(true)} + > + + + + + )}
)} - {actionTypesIndex && activeActionItem ? ( + {actionTypesIndex && activeActionItem && addModalVisible ? ( { connectors.push(savedAction); setActionIdByIndex(savedAction.id, activeActionItem.index); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index 13ec8395aa5578..de27256bf566cb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -32,8 +32,7 @@ import { interface ConnectorAddModalProps { actionType: ActionType; - addModalVisible: boolean; - setAddModalVisibility: React.Dispatch>; + 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, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index bdc11fd543ee14..aa0c97c804d77c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -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 { @@ -151,9 +151,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 } }); @@ -167,9 +175,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 })) : []; @@ -200,6 +211,7 @@ export const AlertForm = ({ label={item.name} onClick={() => { setAlertProperty('alertTypeId', item.id); + setActions([]); setAlertTypeModel(item); setAlertProperty('params', {}); if (alertTypesIndex && alertTypesIndex.has(item.id)) { @@ -260,26 +272,25 @@ export const AlertForm = ({ /> ) : null} - {canShowActions && defaultActionGroupId ? ( + {canShowActions && + defaultActionGroupId && + alertTypeModel && + alertTypesIndex?.has(alert.alertTypeId) ? ( - 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} diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 7d99d3635106dd..ee0de582a9bffa 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -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); diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts index e3927f6bfffb95..6f9d0103786244 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts @@ -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; From e9f2cd05bd93cf231ae0bb6e886221c95b43100c Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 11:21:30 +0000 Subject: [PATCH 02/45] fixed tests --- .../server/alert_types/always_firing.ts | 20 +++-- .../signals/siem_rule_action_groups.ts | 0 .../common/shared_exports.ts | 1 + .../rules/rule_actions_field/index.tsx | 24 +++-- .../rules_notification_alert_type.ts | 2 +- .../signals/signal_rule_alert_type.ts | 2 +- .../action_form.test.tsx | 89 ++++++++++++------- .../action_connector_form/action_form.tsx | 4 +- 8 files changed, 93 insertions(+), 49 deletions(-) rename x-pack/plugins/security_solution/{server/lib => common}/detection_engine/signals/siem_rule_action_groups.ts (100%) diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index bb1cb0d97689bf..bf90faa2fcf529 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -5,25 +5,33 @@ */ import uuid from 'uuid'; -import { range } from 'lodash'; +import { range, random, pick } 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', tshirtSize: 1 }, + { id: 'medium', name: 'medium', tshirtSize: 2 }, + { id: 'large', name: 'large', tshirtSize: 3 }, +]; + export const alertType: AlertType = { id: 'example.always-firing', name: 'Always firing', - actionGroups: [{ id: 'default', name: 'default' }], - defaultActionGroupId: 'default', + actionGroups: ACTION_GROUPS.map((actionGroup) => pick(actionGroup, ['id', 'name'])), + 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: random(1, 3) })) + .forEach((instance: { id: string; tshirtSize: number }) => { services .alertInstanceFactory(instance.id) .replaceState({ triggerdOnCycle: count }) - .scheduleActions('default'); + .scheduleActions( + ACTION_GROUPS.find((actionGroup) => actionGroup.tshirtSize === instance.tshirtSize)!.id + ); }); return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts b/x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts rename to x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts diff --git a/x-pack/plugins/security_solution/common/shared_exports.ts b/x-pack/plugins/security_solution/common/shared_exports.ts index bee2e54d0e3eab..b5945e3138c96d 100644 --- a/x-pack/plugins/security_solution/common/shared_exports.ts +++ b/x-pack/plugins/security_solution/common/shared_exports.ts @@ -7,6 +7,7 @@ export { NonEmptyString } from './detection_engine/schemas/types/non_empty_string'; export { DefaultArray } from './detection_engine/schemas/types/default_array'; export { DefaultUuid } from './detection_engine/schemas/types/default_uuid'; +export { siemRuleActionGroups } from './detection_engine/signals/siem_rule_action_groups'; export { DefaultStringArray } from './detection_engine/schemas/types/default_string_array'; export { DefaultVersionNumber, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index 4ff1b4e4f20f35..42b5b9402047cd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -21,10 +21,11 @@ import { import { AlertAction } from '../../../../../../alerts/common'; import { useKibana } from '../../../../common/lib/kibana'; import { FORM_ERRORS_TITLE } from './translations'; +import { siemRuleActionGroups } from '../../../../../common'; type ThrottleSelectField = typeof SelectField; -const DEFAULT_ACTION_GROUP_ID = 'default'; +const DEFAULT_ACTION_GROUP = siemRuleActionGroups[0]; const DEFAULT_ACTION_MESSAGE = 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts'; @@ -52,12 +53,19 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables [field.value] ); + const setActionPropByIndex = (prop: 'id' | 'group', value: string, index: number) => { + const updatedActions = [...(actions as Array>)]; + updatedActions[index] = deepMerge(updatedActions[index], { [prop]: value }); + field.setValue(updatedActions); + }; const setActionIdByIndex = useCallback( - (id: string, index: number) => { - const updatedActions = [...(actions as Array>)]; - updatedActions[index] = deepMerge(updatedActions[index], { id }); - field.setValue(updatedActions); - }, + (id: string, index: number) => setActionPropByIndex('id', id, index), + // eslint-disable-next-line react-hooks/exhaustive-deps + [field.setValue, actions] + ); + + const setActionGroupIdByIndex = useCallback( + (group: string, index: number) => setActionPropByIndex('group', group, index), // eslint-disable-next-line react-hooks/exhaustive-deps [field.setValue, actions] ); @@ -118,7 +126,9 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables docLinks={docLinks} capabilities={capabilities} messageVariables={messageVariables} - defaultActionGroupId={DEFAULT_ACTION_GROUP_ID} + defaultActionGroupId={DEFAULT_ACTION_GROUP.id} + actionGroups={siemRuleActionGroups} + setActionGroupIdByIndex={setActionGroupIdByIndex} setActionIdByIndex={setActionIdByIndex} setAlertProperty={setAlertProperty} setActionParamsProperty={setActionParamsProperty} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index 802b9472a44870..7f2c0369ee3f73 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -7,11 +7,11 @@ import { Logger } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { NOTIFICATIONS_ID, SERVER_APP_ID } from '../../../../common/constants'; +import { siemRuleActionGroups } from '../../../../common'; import { NotificationAlertTypeDefinition } from './types'; import { getSignalsCount } from './get_signals_count'; import { RuleAlertAttributes } from '../signals/types'; -import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; import { getNotificationResultsLink } from './utils'; import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index bb3a0b4fa6f08c..7e66a17f7f2aa7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -43,7 +43,7 @@ import { createSearchAfterReturnTypeFromResponse, } from './utils'; import { signalParamsSchema } from './signal_params_schema'; -import { siemRuleActionGroups } from './siem_rule_action_groups'; +import { siemRuleActionGroups } from '../../../../common'; import { findMlSignals } from './find_ml_signals'; import { findThresholdSignals } from './find_threshold_signals'; import { bulkCreateMlSignals } from './bulk_create_ml_signals'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 7c718e8248e41c..83ed2c6ed8cac9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -112,8 +112,6 @@ describe('action_form', () => { }; describe('action_form in alert', () => { - let wrapper: ReactWrapper; - async function setup(customActions?: AlertAction[]) { const { loadAllActions } = jest.requireMock('../../lib/action_connector_api'); loadAllActions.mockResolvedValueOnce([ @@ -217,7 +215,7 @@ describe('action_form', () => { mutedInstanceIds: [], } as unknown) as Alert; - wrapper = mountWithIntl( + const wrapper = mountWithIntl( { 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,13 +299,16 @@ 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"]` ); + wrapper.debug(); expect(actionOption.exists()).toBeTruthy(); expect( wrapper @@ -314,7 +319,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 +327,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 +403,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 +416,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"]` ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index d8dd2ac06ef36e..0ea1b710a08269 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -102,7 +102,7 @@ export const ActionForm = ({ const [activeActionItem, setActiveActionItem] = useState( undefined ); - const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(actions.length === 0); + const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(true); const [connectors, setConnectors] = useState([]); const [isLoadingConnectors, setIsLoadingConnectors] = useState(false); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); @@ -341,7 +341,7 @@ export const ActionForm = ({ singleSelection={{ asPlainText: true }} options={optionsList} id={`selectActionConnector-${actionItem.id}`} - data-test-subj={`selectActionConnector-${index}`} + data-test-subj={`selectActionConnector-${actionItem.actionTypeId}`} selectedOptions={getSelectedOptions(actionItem.id)} onChange={(selectedOptions) => { setActionIdByIndex(selectedOptions[0].id ?? '', index); From 42395e90898d4a29a6a74c13638e2fd927b8b597 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 11:26:55 +0000 Subject: [PATCH 03/45] Revert "fixed tests" This reverts commit e9f2cd05bd93cf231ae0bb6e886221c95b43100c. --- .../server/alert_types/always_firing.ts | 20 ++--- .../common/shared_exports.ts | 1 - .../rules/rule_actions_field/index.tsx | 24 ++--- .../rules_notification_alert_type.ts | 2 +- .../signals/siem_rule_action_groups.ts | 0 .../signals/signal_rule_alert_type.ts | 2 +- .../action_form.test.tsx | 89 +++++++------------ .../action_connector_form/action_form.tsx | 4 +- 8 files changed, 49 insertions(+), 93 deletions(-) rename x-pack/plugins/security_solution/{common => server/lib}/detection_engine/signals/siem_rule_action_groups.ts (100%) diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index bf90faa2fcf529..bb1cb0d97689bf 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -5,33 +5,25 @@ */ import uuid from 'uuid'; -import { range, random, pick } from 'lodash'; +import { range } 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', tshirtSize: 1 }, - { id: 'medium', name: 'medium', tshirtSize: 2 }, - { id: 'large', name: 'large', tshirtSize: 3 }, -]; - export const alertType: AlertType = { id: 'example.always-firing', name: 'Always firing', - actionGroups: ACTION_GROUPS.map((actionGroup) => pick(actionGroup, ['id', 'name'])), - defaultActionGroupId: 'small', + actionGroups: [{ id: 'default', name: 'default' }], + defaultActionGroupId: 'default', async executor({ services, params: { instances = DEFAULT_INSTANCES_TO_GENERATE }, state }) { const count = (state.count ?? 0) + 1; range(instances) - .map(() => ({ id: uuid.v4(), tshirtSize: random(1, 3) })) - .forEach((instance: { id: string; tshirtSize: number }) => { + .map(() => ({ id: uuid.v4() })) + .forEach((instance: { id: string }) => { services .alertInstanceFactory(instance.id) .replaceState({ triggerdOnCycle: count }) - .scheduleActions( - ACTION_GROUPS.find((actionGroup) => actionGroup.tshirtSize === instance.tshirtSize)!.id - ); + .scheduleActions('default'); }); return { diff --git a/x-pack/plugins/security_solution/common/shared_exports.ts b/x-pack/plugins/security_solution/common/shared_exports.ts index b5945e3138c96d..bee2e54d0e3eab 100644 --- a/x-pack/plugins/security_solution/common/shared_exports.ts +++ b/x-pack/plugins/security_solution/common/shared_exports.ts @@ -7,7 +7,6 @@ export { NonEmptyString } from './detection_engine/schemas/types/non_empty_string'; export { DefaultArray } from './detection_engine/schemas/types/default_array'; export { DefaultUuid } from './detection_engine/schemas/types/default_uuid'; -export { siemRuleActionGroups } from './detection_engine/signals/siem_rule_action_groups'; export { DefaultStringArray } from './detection_engine/schemas/types/default_string_array'; export { DefaultVersionNumber, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index 42b5b9402047cd..4ff1b4e4f20f35 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -21,11 +21,10 @@ import { import { AlertAction } from '../../../../../../alerts/common'; import { useKibana } from '../../../../common/lib/kibana'; import { FORM_ERRORS_TITLE } from './translations'; -import { siemRuleActionGroups } from '../../../../../common'; type ThrottleSelectField = typeof SelectField; -const DEFAULT_ACTION_GROUP = siemRuleActionGroups[0]; +const DEFAULT_ACTION_GROUP_ID = 'default'; const DEFAULT_ACTION_MESSAGE = 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts'; @@ -53,19 +52,12 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables [field.value] ); - const setActionPropByIndex = (prop: 'id' | 'group', value: string, index: number) => { - const updatedActions = [...(actions as Array>)]; - updatedActions[index] = deepMerge(updatedActions[index], { [prop]: value }); - field.setValue(updatedActions); - }; const setActionIdByIndex = useCallback( - (id: string, index: number) => setActionPropByIndex('id', id, index), - // eslint-disable-next-line react-hooks/exhaustive-deps - [field.setValue, actions] - ); - - const setActionGroupIdByIndex = useCallback( - (group: string, index: number) => setActionPropByIndex('group', group, index), + (id: string, index: number) => { + const updatedActions = [...(actions as Array>)]; + updatedActions[index] = deepMerge(updatedActions[index], { id }); + field.setValue(updatedActions); + }, // eslint-disable-next-line react-hooks/exhaustive-deps [field.setValue, actions] ); @@ -126,9 +118,7 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables docLinks={docLinks} capabilities={capabilities} messageVariables={messageVariables} - defaultActionGroupId={DEFAULT_ACTION_GROUP.id} - actionGroups={siemRuleActionGroups} - setActionGroupIdByIndex={setActionGroupIdByIndex} + defaultActionGroupId={DEFAULT_ACTION_GROUP_ID} setActionIdByIndex={setActionIdByIndex} setAlertProperty={setAlertProperty} setActionParamsProperty={setActionParamsProperty} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index 7f2c0369ee3f73..802b9472a44870 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -7,11 +7,11 @@ import { Logger } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { NOTIFICATIONS_ID, SERVER_APP_ID } from '../../../../common/constants'; -import { siemRuleActionGroups } from '../../../../common'; import { NotificationAlertTypeDefinition } from './types'; import { getSignalsCount } from './get_signals_count'; import { RuleAlertAttributes } from '../signals/types'; +import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; import { getNotificationResultsLink } from './utils'; import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts similarity index 100% rename from x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 7e66a17f7f2aa7..bb3a0b4fa6f08c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -43,7 +43,7 @@ import { createSearchAfterReturnTypeFromResponse, } from './utils'; import { signalParamsSchema } from './signal_params_schema'; -import { siemRuleActionGroups } from '../../../../common'; +import { siemRuleActionGroups } from './siem_rule_action_groups'; import { findMlSignals } from './find_ml_signals'; import { findThresholdSignals } from './find_threshold_signals'; import { bulkCreateMlSignals } from './bulk_create_ml_signals'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 83ed2c6ed8cac9..7c718e8248e41c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -112,6 +112,8 @@ describe('action_form', () => { }; describe('action_form in alert', () => { + let wrapper: ReactWrapper; + async function setup(customActions?: AlertAction[]) { const { loadAllActions } = jest.requireMock('../../lib/action_connector_api'); loadAllActions.mockResolvedValueOnce([ @@ -215,7 +217,7 @@ describe('action_form', () => { mutedInstanceIds: [], } as unknown) as Alert; - const wrapper = mountWithIntl( + wrapper = mountWithIntl( { 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 }) @@ -299,16 +297,13 @@ describe('action_form', () => { await nextTick(); wrapper.update(); }); - - return wrapper; } it('renders available action cards', async () => { - const wrapper = await setup(); + await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` ); - wrapper.debug(); expect(actionOption.exists()).toBeTruthy(); expect( wrapper @@ -319,7 +314,7 @@ describe('action_form', () => { }); it('does not render action types disabled by config', async () => { - const wrapper = await setup(); + await setup(); const actionOption = wrapper.find( '[data-test-subj="disabled-by-config-ActionTypeSelectOption"]' ); @@ -327,72 +322,52 @@ describe('action_form', () => { }); it('render action types which is preconfigured only (disabled by config and with preconfigured connectors)', async () => { - const wrapper = await setup(); + 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 () => { - const wrapper = await setup(); + 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 () => { - const wrapper = await setup(); + 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 () => { - const wrapper = await setup(); + await setup(); const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); actionOption.first().simulate('click'); const preconfigPannel = wrapper.find('[data-test-subj="alertActionAccordion-default"]'); @@ -403,7 +378,7 @@ describe('action_form', () => { }); it('renders action types disabled by license', async () => { - const wrapper = await setup(); + await setup(); const actionOption = wrapper.find( '[data-test-subj="disabled-by-license-ActionTypeSelectOption"]' ); @@ -416,7 +391,7 @@ describe('action_form', () => { }); it(`shouldn't render action types without params component`, async () => { - const wrapper = await setup(); + await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionTypeWithoutParams.id}-ActionTypeSelectOption"]` ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 0ea1b710a08269..d8dd2ac06ef36e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -102,7 +102,7 @@ export const ActionForm = ({ const [activeActionItem, setActiveActionItem] = useState( undefined ); - const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(true); + const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(actions.length === 0); const [connectors, setConnectors] = useState([]); const [isLoadingConnectors, setIsLoadingConnectors] = useState(false); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); @@ -341,7 +341,7 @@ export const ActionForm = ({ singleSelection={{ asPlainText: true }} options={optionsList} id={`selectActionConnector-${actionItem.id}`} - data-test-subj={`selectActionConnector-${actionItem.actionTypeId}`} + data-test-subj={`selectActionConnector-${index}`} selectedOptions={getSelectedOptions(actionItem.id)} onChange={(selectedOptions) => { setActionIdByIndex(selectedOptions[0].id ?? '', index); From 6d00ce8459a3924c7a202aba674cb95390074d5e Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 11:34:14 +0000 Subject: [PATCH 04/45] made action group fields optional --- .../signals/siem_rule_action_groups.ts | 19 ---- .../action_form.test.tsx | 90 ++++++++++++------- .../action_connector_form/action_form.tsx | 86 +++++++++--------- .../connector_add_modal.test.tsx | 3 +- 4 files changed, 104 insertions(+), 94 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts deleted file mode 100644 index 46fdb739bf143a..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const siemRuleActionGroups = [ - { - id: 'default', - name: i18n.translate( - 'xpack.securitySolution.detectionEngine.signalRuleAlert.actionGroups.default', - { - defaultMessage: 'Default', - } - ), - }, -]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 7c718e8248e41c..5c8d706da83ecf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -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; - 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( { 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,13 +298,16 @@ 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"]` ); + wrapper.debug(); expect(actionOption.exists()).toBeTruthy(); expect( wrapper @@ -314,7 +318,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 +326,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 +402,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 +415,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"]` ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index d8dd2ac06ef36e..b46bbd4cd2b174 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -55,9 +55,9 @@ import { ActionGroup } from '../../../../../alerts/common'; interface ActionAccordionFormProps { actions: AlertAction[]; defaultActionGroupId: string; - actionGroups: ActionGroup[]; + actionGroups?: ActionGroup[]; setActionIdByIndex: (id: string, index: number) => void; - setActionGroupIdByIndex: (group: string, index: number) => void; + setActionGroupIdByIndex?: (group: string, index: number) => void; setAlertProperty: (actions: AlertAction[]) => void; setActionParamsProperty: (key: string, value: any, index: number) => void; http: HttpSetup; @@ -102,7 +102,7 @@ export const ActionForm = ({ const [activeActionItem, setActiveActionItem] = useState( undefined ); - const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(actions.length === 0); + const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(true); const [connectors, setConnectors] = useState([]); const [isLoadingConnectors, setIsLoadingConnectors] = useState(false); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); @@ -262,45 +262,49 @@ export const ActionForm = ({ connectors.filter((connector) => connector.isPreconfigured) ); - const defaultActionGroup = actionGroups.find(({ id }) => id === defaultActionGroupId)!; + const defaultActionGroup = actionGroups?.find(({ id }) => id === defaultActionGroupId); const selectedActionGroup = - actionGroups.find(({ id }) => id === actionItem.group) ?? defaultActionGroup; + actionGroups?.find(({ id }) => id === actionItem.group) ?? defaultActionGroup; const accordionContent = checkEnabledResult.isEnabled ? ( - - - + + + + + + } > - ({ + value, + inputDisplay: name, + 'data-test-subj': `addNewActionConnectorActionGroup-${index}-option-${value}`, + }))} + valueOfSelected={selectedActionGroup.id} + onChange={(group) => { + setActionGroupIdByIndex(group, index); + }} /> - - } - > - ({ - value, - inputDisplay: name, - 'data-test-subj': `addNewActionConnectorActionGroup-${index}-option-${value}`, - }))} - valueOfSelected={selectedActionGroup.id} - onChange={(group) => { - setActionGroupIdByIndex(group, index); - }} - /> - - - - + + + + + + )} { setActionIdByIndex(selectedOptions[0].id ?? '', index); @@ -410,9 +414,11 @@ export const ActionForm = ({ }} /> - - {selectedActionGroup.name} - + {selectedActionGroup && ( + + {selectedActionGroup.name} + + )} {checkEnabledResult.isEnabled === false && ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx index cba9eea3cf3f7f..71a3936ed50559 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx @@ -65,8 +65,7 @@ describe('connector_add_modal', () => { const wrapper = mountWithIntl( {}} + onClose={() => {}} actionType={actionType} http={deps!.http} actionTypeRegistry={deps!.actionTypeRegistry} From 89deeca164599de70e8bb7131585da924ad0de65 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 11:37:54 +0000 Subject: [PATCH 05/45] revert deletion --- .../signals/siem_rule_action_groups.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts b/x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts new file mode 100644 index 00000000000000..46fdb739bf143a --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts @@ -0,0 +1,19 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const siemRuleActionGroups = [ + { + id: 'default', + name: i18n.translate( + 'xpack.securitySolution.detectionEngine.signalRuleAlert.actionGroups.default', + { + defaultMessage: 'Default', + } + ), + }, +]; From 73fe0a8c78a24727c6ef25421636f37c01ff4b4b Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 11:38:55 +0000 Subject: [PATCH 06/45] again --- .../lib}/detection_engine/signals/siem_rule_action_groups.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename x-pack/plugins/security_solution/{common => server/lib}/detection_engine/signals/siem_rule_action_groups.ts (100%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts similarity index 100% rename from x-pack/plugins/security_solution/common/detection_engine/signals/siem_rule_action_groups.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/signals/siem_rule_action_groups.ts From 78873a167f6ed40a2c7e9077d4fac7c8f1ad293f Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 13:51:55 +0000 Subject: [PATCH 07/45] extracted action type for mto its own component --- .../action_connector_form/action_form.tsx | 353 +++--------------- .../action_type_form.tsx | 342 +++++++++++++++++ 2 files changed, 393 insertions(+), 302 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 16405e789944b3..413ea16967f5e9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, Suspense, useState, useEffect, useCallback } from 'react'; +import React, { Fragment, useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -14,28 +14,18 @@ import { EuiIcon, EuiTitle, EuiSpacer, - EuiFormRow, - EuiComboBox, EuiKeyPadMenuItem, EuiAccordion, EuiButtonIcon, EuiEmptyPrompt, - EuiButtonEmpty, EuiToolTip, - EuiIconTip, EuiLink, EuiCallOut, EuiText, - EuiFormLabel, - EuiFormControlLayout, - EuiSuperSelect, - EuiLoadingSpinner, - EuiBadge, } 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, @@ -46,13 +36,14 @@ import { } from '../../../types'; import { SectionLoading } from '../../components/section_loading'; import { ConnectorAddModal } from './connector_add_modal'; +import { ActionTypeForm, ActionTypeFormProps } from './action_type_form'; 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[]; @@ -195,285 +186,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) 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 ? ( - - {actionGroups && selectedActionGroup && setActionGroupIdByIndex && ( - - - - - - - } - > - ({ - value, - inputDisplay: name, - 'data-test-subj': `addNewActionConnectorActionGroup-${index}-option-${value}`, - }))} - valueOfSelected={selectedActionGroup.id} - onChange={(group) => { - setActionGroupIdByIndex(group, index); - }} - /> - - - - - - )} - - - - } - labelAppend={ - canSave && - actionTypesIndex && - actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? ( - { - setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); - setAddModalVisibility(true); - }} - > - - - ) : null - } - > - { - setActionIdByIndex(selectedOptions[0].id ?? '', index); - }} - isClearable={false} - /> - - - - - {ParamsFieldsComponent ? ( - - - - - - } - > - - - ) : null} - - ) : ( - checkEnabledResult.messageCard - ); - - return ( - - - - - - - -
- - - - - {selectedActionGroup && ( - - {selectedActionGroup.name} - - )} - - {checkEnabledResult.isEnabled === false && ( - - - - )} - - -
-
-
- - } - extraAction={ - { - 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); - }} - /> - } - > - {accordionContent} -
- -
- ); - }; - const getAddConnectorsForm = (actionItem: AlertAction, index: number) => { const actionTypeName = actionTypesIndex ? actionTypesIndex[actionItem.actionTypeId].name @@ -687,19 +399,56 @@ 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 alertActionsList = + actionTypesIndex && + 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); + const actionParamsErrors: ActionTypeFormProps['actionParamsErrors'] = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); - return getActionTypeForm(actionItem, actionConnector, actionErrors, index); - }); + return ( + { + 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); + }} + /> + ); + }); return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx new file mode 100644 index 00000000000000..8d33001ed3d093 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -0,0 +1,342 @@ +/* + * 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 ? ( + + {actionGroups && selectedActionGroup && setActionGroupIdByIndex && ( + + + + + + + } + > + ({ + value, + inputDisplay: name, + 'data-test-subj': `addNewActionConnectorActionGroup-${index}-option-${value}`, + }))} + valueOfSelected={selectedActionGroup.id} + onChange={(group) => { + setActionGroupIdByIndex(group, index); + }} + /> + + + + + + )} + + + + } + labelAppend={ + canSave && + actionTypesIndex && + actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? ( + + + + ) : null + } + > + { + onConnectorSelected(selectedOptions[0].id ?? ''); + }} + isClearable={false} + /> + + + + + {ParamsFieldsComponent ? ( + + + + + + } + > + + + ) : null} + + ) : ( + checkEnabledResult.messageCard + ); + + return ( + + + + + + + +
+ + + + + {selectedActionGroup && !isOpen && ( + + {selectedActionGroup.name} + + )} + + {checkEnabledResult.isEnabled === false && ( + + + + )} + + +
+
+
+ + } + extraAction={ + + } + > + {accordionContent} +
+ +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export { ActionTypeForm as default }; From 85a106851c1b53563536b2a83ecda92d784832c5 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 16:36:18 +0000 Subject: [PATCH 08/45] extracted more sections of the action form to their own components --- .../server/alert_types/always_firing.ts | 18 +- .../action_connector_form/action_form.tsx | 154 +++-------------- .../connector_add_inline.tsx | 156 ++++++++++++++++++ 3 files changed, 192 insertions(+), 136 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index bb1cb0d97689bf..97cebe38300b08 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -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)] })) + .forEach((instance: { id: string; tshirtSize: string }) => { services .alertInstanceFactory(instance.id) .replaceState({ triggerdOnCycle: count }) - .scheduleActions('default'); + .scheduleActions(tshirtSize); }); return { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 413ea16967f5e9..5379a7a99f4292 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -15,13 +15,8 @@ import { EuiTitle, EuiSpacer, EuiKeyPadMenuItem, - EuiAccordion, - EuiButtonIcon, - EuiEmptyPrompt, EuiToolTip, EuiLink, - EuiCallOut, - EuiText, } from '@elastic/eui'; import { HttpSetup, ToastsSetup, ApplicationStart, DocLinksStart } from 'kibana/public'; import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/action_connector_api'; @@ -37,10 +32,10 @@ import { 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'; export interface ActionAccordionFormProps { @@ -87,8 +82,6 @@ export const ActionForm = ({ capabilities, docLinks, }: ActionAccordionFormProps) => { - const canSave = hasSaveActionsCapability(capabilities); - const [addModalVisible, setAddModalVisibility] = useState(false); const [activeActionItem, setActiveActionItem] = useState( undefined @@ -186,127 +179,6 @@ export const ActionForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [actions, connectors]); - 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 = ( - - ); - return ( - - - - - - - -
- -
-
-
- - } - extraAction={ - { - 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 ? ( - actionItem.id === emptyId) ? ( - noConnectorsLabel - ) : ( - - ) - } - actions={[ - { - setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); - setAddModalVisibility(true); - }} - > - - , - ]} - /> - ) : ( - -

- -

-
- )} -
- -
- ); - }; - function addActionType(actionTypeModel: ActionTypeModel) { if (!defaultActionGroupId) { toastNotifications!.addDanger({ @@ -405,7 +277,29 @@ export const ActionForm = ({ const actionConnector = connectors.find((field) => field.id === actionItem.id); // connectors doesn't exists if (!actionConnector) { - return getAddConnectorsForm(actionItem, index); + return ( + { + 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); + }} + /> + ); } const actionParamsErrors: ActionTypeFormProps['actionParamsErrors'] = actionTypeRegistry diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx new file mode 100644 index 00000000000000..3f46b9e96df772 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx @@ -0,0 +1,156 @@ +/* + * 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; + +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 = ( + + ); + return ( + + + + + + + +
+ +
+
+
+ + } + extraAction={ + + } + paddingSize="l" + > + {canSave ? ( + actionItem.id === emptyId) ? ( + noConnectorsLabel + ) : ( + + ) + } + actions={[ + + + , + ]} + /> + ) : ( + +

+ +

+
+ )} +
+ +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export { AddConnectorInline as default }; From 44eb6704546a99cfdcadfb51cb862a22408f6418 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 16:51:44 +0000 Subject: [PATCH 09/45] updated icon --- .../sections/action_connector_form/connector_add_inline.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx index 3f46b9e96df772..45ef4ceab461e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx @@ -90,7 +90,7 @@ export const AddConnectorInline = ({ } extraAction={ Date: Thu, 5 Nov 2020 16:59:35 +0000 Subject: [PATCH 10/45] added docs --- x-pack/plugins/triggers_actions_ui/README.md | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index aabb9899cb3434..32e157255c0cc2 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -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; - 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: From afc6e81e22c96654a7f2c61caba40e8772c9c515 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 17:47:10 +0000 Subject: [PATCH 11/45] fixed always firing alert --- .../alerting_example/server/alert_types/always_firing.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index 97cebe38300b08..d02406a23045e6 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -24,12 +24,12 @@ export const alertType: AlertType = { const count = (state.count ?? 0) + 1; range(instances) - .map(() => ({ id: uuid.v4(), tshirtSize: ACTION_GROUPS[random(0, 2)] })) + .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(tshirtSize); + .scheduleActions(instance.tshirtSize); }); return { From 38fb6599b0c70cac75e48f307f4d10915e141f0a Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 5 Nov 2020 18:32:29 +0000 Subject: [PATCH 12/45] fixed export of components --- .../action_form.test.tsx | 1 - .../action_connector_form/action_form.tsx | 302 +++++++++--------- .../action_type_form.tsx | 3 - .../connector_add_inline.tsx | 3 - 4 files changed, 151 insertions(+), 158 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 5c8d706da83ecf..94452e70e6bfa3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -307,7 +307,6 @@ describe('action_form', () => { const actionOption = wrapper.find( `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` ); - wrapper.debug(); expect(actionOption.exists()).toBeTruthy(); expect( wrapper diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 5379a7a99f4292..92aa59d2af2e76 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -271,165 +271,165 @@ export const ActionForm = ({ }); } - const alertActionsList = - actionTypesIndex && - actions.map((actionItem: AlertAction, index: number) => { - const actionConnector = connectors.find((field) => field.id === actionItem.id); - // connectors doesn't exists - if (!actionConnector) { - return ( - { - 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); - }} - /> - ); - } - - const actionParamsErrors: ActionTypeFormProps['actionParamsErrors'] = actionTypeRegistry - .get(actionItem.actionTypeId) - ?.validateParams(actionItem.params); - - return ( - { - 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); - }} - /> - ); - }); - - return ( + return isLoadingConnectors ? ( + + + + ) : ( - {isLoadingConnectors ? ( - + +

- - ) : ( - - -

- + + + {actionTypesIndex && + actions.map((actionItem: AlertAction, index: number) => { + const actionConnector = connectors.find((field) => field.id === actionItem.id); + // connectors doesn't exists + if (!actionConnector) { + return ( + { + 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); + }} /> -

-
- - {alertActionsList} - - {isAddActionPanelOpen ? ( - - - - -
- -
-
-
- {hasDisabledByLicenseActionTypes && ( - - -
- - - -
-
-
- )} -
- - - {isLoadingActionTypes ? ( - - - - ) : ( - actionTypeNodes - )} - -
- ) : ( - - - setIsAddActionPanelOpen(true)} - > + ); + } + + const actionParamsErrors: ActionTypeFormProps['actionParamsErrors'] = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); + + return ( + { + 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); + }} + /> + ); + })} + + {isAddActionPanelOpen ? ( + + + + +
- +
+
+
+ {hasDisabledByLicenseActionTypes && ( + + +
+ + + +
+
-
- )} + )} +
+ + + {isLoadingActionTypes ? ( + + + + ) : ( + actionTypeNodes + )} +
+ ) : ( + + + setIsAddActionPanelOpen(true)} + > + + + + )} {actionTypesIndex && activeActionItem && addModalVisible ? ( ); }; - -// eslint-disable-next-line import/no-default-export -export { ActionTypeForm as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx index 45ef4ceab461e8..97baf4a36cb4cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx @@ -151,6 +151,3 @@ export const AddConnectorInline = ({ ); }; - -// eslint-disable-next-line import/no-default-export -export { AddConnectorInline as default }; From 6fe4ae41e4ac04e6dbe48c69c13e08e23d18fd60 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Fri, 6 Nov 2020 10:44:04 +0000 Subject: [PATCH 13/45] fixed react warning --- .../examples/alerting_example/public/alert_types/astros.tsx | 6 +++--- .../sections/action_connector_form/action_form.tsx | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/examples/alerting_example/public/alert_types/astros.tsx b/x-pack/examples/alerting_example/public/alert_types/astros.tsx index 343f6b10ef85bb..e394f6aecaa01e 100644 --- a/x-pack/examples/alerting_example/public/alert_types/astros.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/astros.tsx @@ -126,9 +126,9 @@ export const PeopleinSpaceExpression: React.FunctionComponent - errs.map((e) => ( -

+ Object.entries(errors).map(([field, errs]: [string, string[]], fieldIndex) => + errs.map((e, index) => ( +

{field}:`: ${errs}`

)) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 92aa59d2af2e76..3a7341afe3e079 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -333,6 +333,7 @@ export const ActionForm = ({ actionConnector={actionConnector} actionParamsErrors={actionParamsErrors} index={index} + key={`action-form-action-at-${index}`} setActionParamsProperty={setActionParamsProperty} actionTypesIndex={actionTypesIndex} connectors={connectors} From 9d1f594d15c3695b95eb7b0f7516b677b6f3bc0b Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Fri, 6 Nov 2020 11:53:16 -0500 Subject: [PATCH 14/45] Adding flag for notifying on state change --- x-pack/plugins/alerts/common/alert.ts | 1 + x-pack/plugins/alerts/server/routes/create.ts | 1 + .../alerts/server/saved_objects/mappings.json | 3 +++ x-pack/plugins/alerts/server/types.ts | 2 ++ .../application/sections/alert_form/alert_form.tsx | 13 +++++++++++++ 5 files changed, 20 insertions(+) diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts index 79e6bb8f2cbbaf..a9b53a7194d145 100644 --- a/x-pack/plugins/alerts/common/alert.ts +++ b/x-pack/plugins/alerts/common/alert.ts @@ -68,6 +68,7 @@ export interface Alert { apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; + notifyOnStateChange: boolean; muteAll: boolean; mutedInstanceIds: string[]; executionStatus: AlertExecutionStatus; diff --git a/x-pack/plugins/alerts/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts index 91a81f6d84b714..a97f3b09a3b8a6 100644 --- a/x-pack/plugins/alerts/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -38,6 +38,7 @@ export const bodySchema = schema.object({ }), { defaultValue: [] } ), + notifyOnStateChange: schema.boolean({ defaultValue: true }), }); export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => { diff --git a/x-pack/plugins/alerts/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json index a6c92080f18be5..3df74f813c6038 100644 --- a/x-pack/plugins/alerts/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerts/server/saved_objects/mappings.json @@ -71,6 +71,9 @@ "throttle": { "type": "keyword" }, + "notifyOnStateChange": { + "type": "boolean" + }, "muteAll": { "type": "boolean" }, diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 42eef9bba10e52..0bf2864db4893e 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -149,6 +149,7 @@ export interface RawAlert extends SavedObjectAttributes { apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; + notifyOnStateChange: boolean; muteAll: boolean; mutedInstanceIds: string[]; meta?: AlertMeta; @@ -159,6 +160,7 @@ export type AlertInfoParams = Pick< RawAlert, | 'params' | 'throttle' + | 'notifyOnStateChange' | 'muteAll' | 'mutedInstanceIds' | 'name' diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 20ad9a8d7c7014..c13e99026781f3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -27,6 +27,7 @@ import { EuiEmptyPrompt, EuiLink, EuiText, + EuiSwitch, } from '@elastic/eui'; import { some, filter, map, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; @@ -480,6 +481,18 @@ export const AlertForm = ({ + + } + onChange={(e) => setAlertProperty('notifyOnStateChange', e.target.checked)} + /> From 9005c1e79c99bb6c5e1a6db9de74a82f6b478a3a Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Fri, 6 Nov 2020 15:07:40 -0500 Subject: [PATCH 15/45] Updating logic in task runner --- .../server/alert_instance/alert_instance.ts | 4 +++ .../alerts/server/task_runner/task_runner.ts | 29 ++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts index 661fb75f81c007..1cc4f1d41dc7ea 100644 --- a/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts +++ b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts @@ -51,6 +51,10 @@ export class AlertInstance< return false; } + actionGroupHasChanged(): boolean { + return this.scheduledExecutionOptions?.actionGroup !== this.meta?.lastScheduledActions?.group; + } + getScheduledActionOptions() { return this.scheduledExecutionOptions; } diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 6a49f67268d697..06ca1cf689bb99 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -154,7 +154,16 @@ export class TaskRunner { executionHandler: ReturnType, spaceId: string ): Promise { - const { throttle, muteAll, mutedInstanceIds, name, tags, createdBy, updatedBy } = alert; + const { + throttle, + notifyOnStateChange, + muteAll, + mutedInstanceIds, + name, + tags, + createdBy, + updatedBy, + } = alert; const { params: { alertId }, state: { alertInstances: alertRawInstances = {}, alertTypeState = {}, previousStartedAt }, @@ -236,15 +245,19 @@ export class TaskRunner { if (!muteAll) { const mutedInstanceIdsSet = new Set(mutedInstanceIds); - await Promise.all( - Object.entries(instancesWithScheduledActions) - .filter( + const instancesToExecute = notifyOnStateChange + ? Object.entries(instancesWithScheduledActions).filter( + ([_, alertInstance]: [string, AlertInstance]) => !alertInstance.actionGroupHasChanged() + ) + : Object.entries(instancesWithScheduledActions).filter( ([alertInstanceName, alertInstance]: [string, AlertInstance]) => !alertInstance.isThrottled(throttle) && !mutedInstanceIdsSet.has(alertInstanceName) - ) - .map(([id, alertInstance]: [string, AlertInstance]) => - this.executeAlertInstance(id, alertInstance, executionHandler) - ) + ); + + await Promise.all( + instancesToExecute.map(([id, alertInstance]: [string, AlertInstance]) => + this.executeAlertInstance(id, alertInstance, executionHandler) + ) ); } From 1422911b19803b40874a9f2030ad0cd357bf7a5f Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Fri, 6 Nov 2020 15:55:21 -0500 Subject: [PATCH 16/45] Starting to update tests --- .../alert_instance/alert_instance.test.ts | 51 +++++++++++++++++++ .../alerts/server/routes/create.test.ts | 2 + .../task_runner/alert_task_instance.test.ts | 1 + .../server/task_runner/task_runner.test.ts | 1 + 4 files changed, 55 insertions(+) diff --git a/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts index c5f93edfb74e5f..99c8cb4b43c856 100644 --- a/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts +++ b/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts @@ -72,6 +72,57 @@ describe('isThrottled', () => { }); }); +describe('actionGroupHasChanged()', () => { + test('should be false if no last scheduled and nothing scheduled', () => { + const alertInstance = new AlertInstance(); + expect(alertInstance.actionGroupHasChanged()).toEqual(false); + }); + + test('should be false if group does not change', () => { + const alertInstance = new AlertInstance({ + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + }, + }, + }); + alertInstance.scheduleActions('default'); + expect(alertInstance.actionGroupHasChanged()).toEqual(false); + }); + + test('should be true if no last scheduled and group is scheduled', () => { + const alertInstance = new AlertInstance(); + alertInstance.scheduleActions('default'); + expect(alertInstance.actionGroupHasChanged()).toEqual(true); + }); + + test('should be true if last scheduled group and next action is undefined', () => { + const alertInstance = new AlertInstance({ + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + }, + }, + }); + expect(alertInstance.actionGroupHasChanged()).toEqual(true); + }); + + test('should be true if group does change', () => { + const alertInstance = new AlertInstance({ + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + }, + }, + }); + alertInstance.scheduleActions('penguin'); + expect(alertInstance.actionGroupHasChanged()).toEqual(true); + }); +}); + describe('getScheduledActionOptions()', () => { test('defaults to undefined', () => { const alertInstance = new AlertInstance(); diff --git a/x-pack/plugins/alerts/server/routes/create.test.ts b/x-pack/plugins/alerts/server/routes/create.test.ts index 51c5d2525631d6..a515adc0db36e9 100644 --- a/x-pack/plugins/alerts/server/routes/create.test.ts +++ b/x-pack/plugins/alerts/server/routes/create.test.ts @@ -36,6 +36,7 @@ describe('createAlertRoute', () => { bar: true, }, throttle: '30s', + notifyOnStateChange: false, actions: [ { group: 'default', @@ -110,6 +111,7 @@ describe('createAlertRoute', () => { "alertTypeId": "1", "consumer": "bar", "name": "abc", + "notifyOnStateChange": false, "params": Object { "bar": true, }, diff --git a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts index cf0dd9d135e275..023e4c041f7a89 100644 --- a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts @@ -27,6 +27,7 @@ const alert: SanitizedAlert = { updatedAt: new Date(), apiKeyOwner: null, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 4d0d69010914e2..76f3e67065a41d 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -90,6 +90,7 @@ describe('Task Runner', () => { updatedAt: new Date('2019-02-12T21:01:22.479Z'), throttle: null, muteAll: false, + notifyOnStateChange: false, enabled: true, alertTypeId: '123', apiKey: '', From d37288cd05c2ed7766e93e3c0b75ab82705ab807 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 9 Nov 2020 10:51:36 -0500 Subject: [PATCH 17/45] Adding tests --- .../server/alerts_client/alerts_client.ts | 1 + x-pack/plugins/alerts/server/routes/create.ts | 2 +- .../alerts/server/routes/update.test.ts | 3 + x-pack/plugins/alerts/server/routes/update.ts | 5 +- .../server/task_runner/task_runner.test.ts | 111 ++++++++++++++++++ .../alerts/server/task_runner/task_runner.ts | 6 +- .../sections/alert_form/alert_form.test.tsx | 6 + .../sections/alert_form/alert_form.tsx | 40 +++++-- .../alert_create_flyout.ts | 5 + 9 files changed, 161 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index 14bddceb1c03dd..7a59aab184b606 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -154,6 +154,7 @@ interface UpdateOptions { actions: NormalizedAlertAction[]; params: Record; throttle: string | null; + notifyOnStateChange: boolean; }; } diff --git a/x-pack/plugins/alerts/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts index a97f3b09a3b8a6..54961ff5295713 100644 --- a/x-pack/plugins/alerts/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -38,7 +38,7 @@ export const bodySchema = schema.object({ }), { defaultValue: [] } ), - notifyOnStateChange: schema.boolean({ defaultValue: true }), + notifyOnStateChange: schema.boolean({ defaultValue: false }), }); export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => { diff --git a/x-pack/plugins/alerts/server/routes/update.test.ts b/x-pack/plugins/alerts/server/routes/update.test.ts index dedb08a9972c20..23ea0dc0d39570 100644 --- a/x-pack/plugins/alerts/server/routes/update.test.ts +++ b/x-pack/plugins/alerts/server/routes/update.test.ts @@ -41,6 +41,7 @@ describe('updateAlertRoute', () => { }, }, ], + notifyOnStateChange: false, }; it('updates an alert with proper parameters', async () => { @@ -78,6 +79,7 @@ describe('updateAlertRoute', () => { }, }, ], + notifyOnStateChange: false, }, }, ['ok'] @@ -100,6 +102,7 @@ describe('updateAlertRoute', () => { }, ], "name": "abc", + "notifyOnStateChange": false, "params": Object { "otherField": false, }, diff --git a/x-pack/plugins/alerts/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts index 9b2fe9a43810b2..dc5156053f533b 100644 --- a/x-pack/plugins/alerts/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -39,6 +39,7 @@ const bodySchema = schema.object({ }), { defaultValue: [] } ), + notifyOnStateChange: schema.boolean({ defaultValue: false }), }); export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => { @@ -62,11 +63,11 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => } const alertsClient = context.alerting.getAlertsClient(); const { id } = req.params; - const { name, actions, params, schedule, tags, throttle } = req.body; + const { name, actions, params, schedule, tags, throttle, notifyOnStateChange } = req.body; return res.ok({ body: await alertsClient.update({ id, - data: { name, actions, params, schedule, tags, throttle }, + data: { name, actions, params, schedule, tags, throttle, notifyOnStateChange }, }), }); }) diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 76f3e67065a41d..fa1a9f73a3204b 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -333,6 +333,117 @@ describe('Task Runner', () => { }); }); + test('actionsPlugin.execute is not called when notifyOnStateChange is true and alert instance state does not change', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { + meta: { + lastScheduledActions: { date: '1970-01-01T00:00:00.000Z', group: 'default' }, + }, + state: { bar: false }, + }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue({ + ...mockedAlertTypeSavedObject, + notifyOnStateChange: true, + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(0); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(2); + expect(eventLogger.logEvent).toHaveBeenCalledWith({ + event: { + action: 'execute', + outcome: 'success', + }, + kibana: { + saved_objects: [ + { + id: '1', + namespace: undefined, + rel: 'primary', + type: 'alert', + }, + ], + }, + message: "alert executed: test:1: 'alert-name'", + }); + expect(eventLogger.logEvent).toHaveBeenCalledWith({ + event: { + action: 'active-instance', + }, + kibana: { + alerting: { + instance_id: '1', + action_group_id: 'default', + }, + saved_objects: [ + { + id: '1', + namespace: undefined, + rel: 'primary', + type: 'alert', + }, + ], + }, + message: "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", + }); + }); + + test('actionsPlugin.execute is called when notifyOnStateChange is true and alert instance state has changed', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue({ + ...mockedAlertTypeSavedObject, + notifyOnStateChange: true, + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); + }); + test('includes the apiKey in the request used to initialize the actionsClient', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 6f7a044891ce36..d8f16ee1a4df58 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -247,8 +247,10 @@ export class TaskRunner { const mutedInstanceIdsSet = new Set(mutedInstanceIds); const instancesToExecute = notifyOnStateChange - ? Object.entries(instancesWithScheduledActions).filter( - ([_, alertInstance]: [string, AlertInstance]) => !alertInstance.actionGroupHasChanged() + ? Object.entries( + instancesWithScheduledActions + ).filter(([_, alertInstance]: [string, AlertInstance]) => + alertInstance.actionGroupHasChanged() ) : Object.entries(instancesWithScheduledActions).filter( ([alertInstanceName, alertInstance]: [string, AlertInstance]) => diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index 4041f6f451a23c..64f29f88e90d78 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -177,6 +177,12 @@ describe('alert_form', () => { expect(alertTypeSelectOptions.exists()).toBeFalsy(); }); + it('renders notify on state change only switch', async () => { + await setup(); + const notifyOnStateChangeSwitch = wrapper.find('[data-test-subj="notifyOnStateChange"]'); + expect(notifyOnStateChangeSwitch.exists()).toBeTruthy(); + }); + it('renders registered action types', async () => { await setup(); const alertTypeSelectOptions = wrapper.find( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index c13e99026781f3..bae671f6cad03c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -438,7 +438,7 @@ export const AlertForm = ({ - + + + + - - } - onChange={(e) => setAlertProperty('notifyOnStateChange', e.target.checked)} - /> + + } + onChange={(e) => { + if (e.target.checked) { + // unset throttle + setAlertThrottle(null); + setAlertProperty('throttle', null); + } + setAlertProperty('notifyOnStateChange', e.target.checked); + }} + /> + + { diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 0f6da936f8644d..35e8c703f87ae6 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -71,6 +71,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const alertName = generateUniqueKey(); await defineAlert(alertName); + await testSubjects.setValue('throttleInput', '10'); + await testSubjects.click('notifyOnStateChange'); + const throttleInput = await find.byCssSelector('[data-test-subj="throttleInput"]'); + expect(await throttleInput.getAttribute('value')).to.be.empty(); + await testSubjects.click('.slack-ActionTypeSelectOption'); await testSubjects.click('addNewActionConnectorButton-.slack'); const slackConnectorName = generateUniqueKey(); From 22b7a007bcd676940dd8b3113759ae3bd7816757 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 9 Nov 2020 11:56:21 -0500 Subject: [PATCH 18/45] Fixing types check --- .../server/alerts_client/tests/create.test.ts | 1 + .../tests/get_alert_instance_summary.test.ts | 1 + .../server/alerts_client/tests/update.test.ts | 14 ++++++++++++++ .../notifications/create_notifications.ts | 1 + .../notifications/update_notifications.ts | 1 + .../routes/__mocks__/request_responses.ts | 2 ++ .../lib/detection_engine/rules/create_rules.ts | 1 + .../lib/detection_engine/rules/patch_rules.mock.ts | 1 + .../lib/detection_engine/rules/patch_rules.ts | 1 + .../lib/detection_engine/rules/update_rules.ts | 1 + .../public/application/lib/alert_api.test.ts | 2 ++ .../components/alert_details.test.tsx | 1 + .../components/alert_details_route.test.tsx | 1 + .../components/alert_instances.test.tsx | 1 + .../components/alert_instances_route.test.tsx | 1 + .../alert_details/components/view_in_app.test.tsx | 1 + .../sections/alert_form/alert_edit.test.tsx | 1 + .../with_bulk_alert_api_operations.test.tsx | 1 + 18 files changed, 33 insertions(+) diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index 965ea1949bf3a8..60831156185e3b 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -57,6 +57,7 @@ function getMockData(overwrites: Record = {}): CreateOptions['d consumer: 'bar', schedule: { interval: '10s' }, throttle: null, + notifyOnStateChange: false, params: { bar: true, }, diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 9cb2a33222d23d..d22d9a3e2d098d 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -80,6 +80,7 @@ const BaseAlertInstanceSummarySavedObject: SavedObject = { apiKey: null, apiKeyOwner: null, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index 1dcde6addb9bfb..0a868036ba0867 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -66,6 +66,7 @@ describe('update()', () => { scheduledTaskId: 'task-123', params: {}, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -171,6 +172,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -386,6 +388,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', + notifyOnStateChange: false, actions: [ { group: 'default', @@ -540,6 +543,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', + notifyOnStateChange: false, actions: [ { group: 'default', @@ -657,6 +661,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -785,6 +790,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -892,6 +898,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', + notifyOnStateChange: false, actions: [ { group: 'default', @@ -953,6 +960,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -1071,6 +1079,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -1102,6 +1111,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -1138,6 +1148,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -1173,6 +1184,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [ { group: 'default', @@ -1226,6 +1238,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [], }, }); @@ -1249,6 +1262,7 @@ describe('update()', () => { bar: true, }, throttle: null, + notifyOnStateChange: false, actions: [], }, }) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts index 8f6826cec5365f..0fef36aadd2a9c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts @@ -31,5 +31,6 @@ export const createNotifications = async ({ enabled, actions: actions.map(transformRuleToAlertAction), throttle: null, + notifyOnStateChange: false, }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts index 17024c7c0d75fe..5b60a9b0b8ad6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts @@ -35,6 +35,7 @@ export const updateNotifications = async ({ ruleAlertId, }, throttle: null, + notifyOnStateChange: false, }, }); } else if (interval && !notification) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 773e84d9c88fce..9251b703a5579f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -416,6 +416,7 @@ export const getResult = (): RuleAlertType => ({ enabled: true, actions: [], throttle: null, + notifyOnStateChange: false, createdBy: 'elastic', updatedBy: 'elastic', apiKey: null, @@ -645,6 +646,7 @@ export const getNotificationResult = (): RuleNotificationAlertType => ({ }, ], throttle: null, + notifyOnStateChange: false, apiKey: null, apiKeyOwner: 'elastic', createdBy: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index 3c814ce7e66067..6a90dbe2998160 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -114,6 +114,7 @@ export const createRules = async ({ enabled, actions: actions.map(transformRuleToAlertAction), throttle: null, + notifyOnStateChange: false, }, }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index 60f1d599470e3c..6e95114fd8c62c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -104,6 +104,7 @@ const rule: SanitizedAlert = { enabled: true, actions: [], throttle: null, + notifyOnStateChange: false, createdBy: 'elastic', updatedBy: 'elastic', apiKeyOwner: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index 22b2593283696d..8552faceb1dc8e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -170,6 +170,7 @@ export const patchRules = async ({ }, actions: actions?.map(transformRuleToAlertAction) ?? rule.actions, params: nextParams, + notifyOnStateChange: false, }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index 5168affca5c624..a218a7dae87a3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -122,6 +122,7 @@ export const updateRules = async ({ schedule: { interval }, actions: actions.map(transformRuleToAlertAction), throttle: null, + notifyOnStateChange: false, params: { author, buildingBlockType, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index 0817be3796fdff..00054ecb00f535 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -547,6 +547,7 @@ describe('createAlert', () => { actions: [], params: {}, throttle: null, + notifyOnStateChange: false, createdAt: new Date('1970-01-01T00:00:00.000Z'), updatedAt: new Date('1970-01-01T00:00:00.000Z'), apiKey: null, @@ -595,6 +596,7 @@ describe('updateAlert', () => { updatedAt: new Date('1970-01-01T00:00:00.000Z'), apiKey: null, apiKeyOwner: null, + notifyOnStateChange: false, }; const resolvedValue: Alert = { ...alertToUpdate, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 70b6fb0b750dd5..741152735efbab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -767,6 +767,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx index 5ed924c37fe7a5..4e3dc959010073 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx @@ -402,6 +402,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx index 25bbe977fd76ad..8fd8a250dfae42 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx @@ -292,6 +292,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index 3a171d469d4ad6..586cb5330409a5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -131,6 +131,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx index 7e43fd22ff8c8e..80cb595e4ea66d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx @@ -103,6 +103,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 31c61f0bba7688..bdd0d04727584e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -101,6 +101,7 @@ describe('alert_edit', () => { tags: [], name: 'test alert', throttle: null, + notifyOnStateChange: false, apiKeyOwner: null, createdBy: 'elastic', updatedBy: 'elastic', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx index 72d4f8857a610a..65ced8893c619a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx @@ -262,6 +262,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { From a075514a7f2024db207fa04ab08cfbb95633d157 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 9 Nov 2020 12:47:00 -0500 Subject: [PATCH 19/45] Tests and types --- .../plugins/alerts/server/alerts_client/tests/create.test.ts | 4 ++++ .../plugins/alerts/server/alerts_client/tests/update.test.ts | 3 +++ .../alerts/server/alerts_client_conflict_retries.test.ts | 1 + .../server/lib/alert_instance_summary_from_event_log.test.ts | 1 + x-pack/plugins/alerts/server/routes/get.test.ts | 1 + x-pack/plugins/monitoring/server/alerts/base_alert.ts | 1 + .../public/application/lib/alert_api.test.ts | 2 +- 7 files changed, 12 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index 60831156185e3b..426a3d9e43cbac 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -274,6 +274,7 @@ describe('create()', () => { "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", + "notifyOnStateChange": false, "params": Object { "bar": true, }, @@ -322,6 +323,7 @@ describe('create()', () => { "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", + "notifyOnStateChange": false, "params": Object { "bar": true, }, @@ -965,6 +967,7 @@ describe('create()', () => { }, schedule: { interval: '10s' }, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], tags: ['foo'], @@ -1086,6 +1089,7 @@ describe('create()', () => { }, schedule: { interval: '10s' }, throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], tags: ['foo'], diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index 0a868036ba0867..72ecd81a454a85 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -283,6 +283,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", + "notifyOnStateChange": false, "params": Object { "bar": true, }, @@ -450,6 +451,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", + "notifyOnStateChange": false, "params": Object { "bar": true, }, @@ -606,6 +608,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", + "notifyOnStateChange": false, "params": Object { "bar": true, }, diff --git a/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts b/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts index b1ac5ac4c6783e..72bcf9f7b1e096 100644 --- a/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts @@ -105,6 +105,7 @@ async function update(success: boolean) { tags: ['bar'], params: { bar: true }, throttle: '10s', + notifyOnStateChange: false, actions: [], }, }); diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index f9e4a2908d6ce3..0b7fa79422d467 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -587,6 +587,7 @@ const BaseAlert: SanitizedAlert = { tags: [], consumer: 'alert-consumer', throttle: null, + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], params: { bar: true }, diff --git a/x-pack/plugins/alerts/server/routes/get.test.ts b/x-pack/plugins/alerts/server/routes/get.test.ts index c60177e90b79d8..d2cb379e770576 100644 --- a/x-pack/plugins/alerts/server/routes/get.test.ts +++ b/x-pack/plugins/alerts/server/routes/get.test.ts @@ -46,6 +46,7 @@ describe('getAlertRoute', () => { tags: ['foo'], enabled: true, muteAll: false, + notifyOnStateChange: false, createdBy: '', updatedBy: '', apiKey: '', diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index 4c1a4d8df2ab5f..e2a040c5b10fb1 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -177,6 +177,7 @@ export class BaseAlert { name: this.label, alertTypeId: this.type, throttle: this.defaultThrottle, + notifyOnStateChange: false, schedule: { interval: this.defaultInterval }, actions: alertActions, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index 00054ecb00f535..f75bbcd5bc7dce 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -573,7 +573,7 @@ describe('createAlert', () => { Array [ "/api/alerts/alert", Object { - "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyOnStateChange\\":false,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", }, ] `); From 8311ab866c674c83623ae7fd72c09a7cf7b4503b Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 9 Nov 2020 13:15:09 -0500 Subject: [PATCH 20/45] Tests --- x-pack/plugins/monitoring/server/alerts/base_alert.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts index c256cce362ff82..78c5886abbf681 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts @@ -64,6 +64,7 @@ describe('BaseAlert', () => { }, tags: [], throttle: '1d', + notifyOnStateChange: false, }, }); }); From e21ea91665c9b1b1abcb62ddde1632f9a19a7ba2 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 9 Nov 2020 15:02:48 -0500 Subject: [PATCH 21/45] Tests --- .../public/application/lib/alert_api.ts | 15 +++++++++++++-- .../security_and_spaces/tests/alerting/create.ts | 1 + .../security_and_spaces/tests/alerting/find.ts | 2 ++ .../security_and_spaces/tests/alerting/get.ts | 1 + .../security_and_spaces/tests/alerting/update.ts | 4 ++++ .../spaces_only/tests/alerting/create.ts | 1 + .../spaces_only/tests/alerting/find.ts | 1 + .../spaces_only/tests/alerting/get.ts | 1 + .../spaces_only/tests/alerting/update.ts | 1 + 9 files changed, 25 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index 7c2f50211d4afd..7429831209affa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -195,12 +195,23 @@ export async function updateAlert({ id, }: { http: HttpSetup; - alert: Pick; + alert: Pick< + AlertUpdates, + 'throttle' | 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notifyOnStateChange' + >; id: string; }): Promise { return await http.put(`${BASE_ALERT_API_PATH}/alert/${id}`, { body: JSON.stringify( - pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions']) + pick(alert, [ + 'throttle', + 'name', + 'tags', + 'schedule', + 'params', + 'actions', + 'notifyOnStateChange', + ]) ), }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index 19d90378e8b7a8..f617f5b0314459 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -115,6 +115,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, throttle: '1m', + notifyOnStateChange: false, updatedBy: user.username, apiKeyOwner: user.username, muteAll: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index adfe5cd27b33a1..f54b2e3f23e8b2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -75,6 +75,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { createdAt: match.createdAt, updatedAt: match.updatedAt, throttle: '1m', + notifyOnStateChange: false, updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, @@ -272,6 +273,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], + notifyOnStateChange: false, createdAt: match.createdAt, updatedAt: match.updatedAt, executionStatus: match.executionStatus, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 93e9be771ab5c6..0d03f7ee726baa 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -71,6 +71,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { updatedAt: response.body.updatedAt, createdAt: response.body.createdAt, throttle: '1m', + notifyOnStateChange: false, updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 0ad2ca226ed5d9..7c79c6f59ae81a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -126,6 +126,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: {}, }, ], + notifyOnStateChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -303,6 +304,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], + notifyOnStateChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -397,6 +399,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], + notifyOnStateChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -487,6 +490,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], + notifyOnStateChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 41f6b66c30aafa..d89923d2fd33cc 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -83,6 +83,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { updatedBy: null, apiKeyOwner: null, throttle: '1m', + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], createdAt: response.body.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index 850ec24789f5b1..e60ffbbdcacf7a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -52,6 +52,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { scheduledTaskId: match.scheduledTaskId, updatedBy: null, throttle: '1m', + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], createdAt: match.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 14a57f57c9237d..8fec926b87784f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -46,6 +46,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { updatedBy: null, apiKeyOwner: null, throttle: '1m', + notifyOnStateChange: false, muteAll: false, mutedInstanceIds: [], createdAt: response.body.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index f44a7d71318799..e608af0b64621a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -54,6 +54,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], + notifyOnStateChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, From 5e0ee21631353de2d82388868ec41d927e8d9a99 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 9 Nov 2020 15:33:14 -0500 Subject: [PATCH 22/45] Tests --- .../public/application/lib/alert_api.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index f75bbcd5bc7dce..7f678b3d19f732 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -620,7 +620,7 @@ describe('updateAlert', () => { Array [ "/api/alerts/alert/123", Object { - "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[]}", + "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\"notifyOnStateChange\":false}", }, ] `); From f81b436c14d9ebb148f352e9787be0b200e78448 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 9 Nov 2020 16:12:57 -0500 Subject: [PATCH 23/45] Tests --- .../public/application/lib/alert_api.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index 7f678b3d19f732..5ddf5bbc4d02ce 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -620,7 +620,7 @@ describe('updateAlert', () => { Array [ "/api/alerts/alert/123", Object { - "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\"notifyOnStateChange\":false}", + "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"notifyOnStateChange\\":false}", }, ] `); From df7d7af3396a8f66f0ad11e78dd19b92b0d4f52a Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 9 Nov 2020 18:06:23 -0500 Subject: [PATCH 24/45] Tests --- .../security_and_spaces/tests/alerting/update.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 7c79c6f59ae81a..633720c09811d5 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -214,6 +214,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, executionStatus: response.body.executionStatus, + notifyOnStateChange: false, }); expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); From 4896a35088ef0c93eba3e5f9bc520ebc36cfa042 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 23 Nov 2020 14:59:22 -0500 Subject: [PATCH 25/45] Renaming field to a more descriptive name. Adding migrations --- .../server/alerts_client/tests/create.test.ts | 10 +++--- .../tests/get_alert_instance_summary.test.ts | 2 +- .../server/alerts_client/tests/update.test.ts | 34 +++++++++---------- .../alerts_client_conflict_retries.test.ts | 2 +- ...rt_instance_summary_from_event_log.test.ts | 2 +- .../alerts/server/routes/create.test.ts | 4 +-- x-pack/plugins/alerts/server/routes/create.ts | 2 +- .../plugins/alerts/server/routes/get.test.ts | 2 +- .../alerts/server/routes/update.test.ts | 6 ++-- x-pack/plugins/alerts/server/routes/update.ts | 6 ++-- .../alerts/server/saved_objects/mappings.json | 2 +- .../server/saved_objects/migrations.test.ts | 12 +++++++ .../alerts/server/saved_objects/migrations.ts | 20 ++++++++--- .../task_runner/alert_task_instance.test.ts | 2 +- .../server/task_runner/task_runner.test.ts | 10 +++--- .../alerts/server/task_runner/task_runner.ts | 4 +-- x-pack/plugins/alerts/server/types.ts | 4 +-- .../server/alerts/base_alert.test.ts | 2 +- .../monitoring/server/alerts/base_alert.ts | 2 +- .../notifications/create_notifications.ts | 2 +- .../notifications/update_notifications.ts | 2 +- .../routes/__mocks__/request_responses.ts | 4 +-- .../detection_engine/rules/create_rules.ts | 2 +- .../rules/patch_rules.mock.ts | 2 +- .../lib/detection_engine/rules/patch_rules.ts | 1 + .../detection_engine/rules/update_rules.ts | 1 + .../public/application/lib/alert_api.test.ts | 8 ++--- .../public/application/lib/alert_api.ts | 4 +-- .../components/alert_details.test.tsx | 2 +- .../components/alert_details_route.test.tsx | 2 +- .../components/alert_instances.test.tsx | 2 +- .../components/alert_instances_route.test.tsx | 2 +- .../components/view_in_app.test.tsx | 2 +- .../sections/alert_form/alert_edit.test.tsx | 2 +- .../sections/alert_form/alert_form.test.tsx | 4 +-- .../sections/alert_form/alert_form.tsx | 14 ++++---- .../with_bulk_alert_api_operations.test.tsx | 2 +- .../tests/alerting/create.ts | 2 +- .../tests/alerting/find.ts | 4 +-- .../security_and_spaces/tests/alerting/get.ts | 2 +- .../tests/alerting/update.ts | 10 +++--- .../spaces_only/tests/alerting/create.ts | 2 +- .../spaces_only/tests/alerting/find.ts | 2 +- .../spaces_only/tests/alerting/get.ts | 2 +- .../spaces_only/tests/alerting/migrations.ts | 9 +++++ .../spaces_only/tests/alerting/update.ts | 2 +- .../alert_create_flyout.ts | 2 +- 47 files changed, 129 insertions(+), 94 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index 880670fb95221c..e4aaac7f1dd896 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -56,7 +56,7 @@ function getMockData(overwrites: Record = {}): CreateOptions['d consumer: 'bar', schedule: { interval: '10s' }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, params: { bar: true, }, @@ -274,7 +274,7 @@ describe('create()', () => { "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", - "notifyOnStateChange": false, + "notifyOnlyOnActionGroupChange": false, "params": Object { "bar": true, }, @@ -323,7 +323,7 @@ describe('create()', () => { "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", - "notifyOnStateChange": false, + "notifyOnlyOnActionGroupChange": false, "params": Object { "bar": true, }, @@ -982,7 +982,7 @@ describe('create()', () => { }, schedule: { interval: '10s' }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], tags: ['foo'], @@ -1105,7 +1105,7 @@ describe('create()', () => { }, schedule: { interval: '10s' }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], tags: ['foo'], diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index d429f0ca2d8838..6e6725688a9c7f 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -80,7 +80,7 @@ const BaseAlertInstanceSummarySavedObject: SavedObject = { apiKey: null, apiKeyOwner: null, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index 53f0a0097e2afe..a790533e7fad4c 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -65,7 +65,7 @@ describe('update()', () => { scheduledTaskId: 'task-123', params: {}, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -180,7 +180,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -291,7 +291,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", - "notifyOnStateChange": false, + "notifyOnlyOnActionGroupChange": false, "params": Object { "bar": true, }, @@ -415,7 +415,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -477,7 +477,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", - "notifyOnStateChange": false, + "notifyOnlyOnActionGroupChange": false, "params": Object { "bar": true, }, @@ -582,7 +582,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -645,7 +645,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", - "notifyOnStateChange": false, + "notifyOnlyOnActionGroupChange": false, "params": Object { "bar": true, }, @@ -702,7 +702,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -831,7 +831,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -939,7 +939,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -1001,7 +1001,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -1121,7 +1121,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -1153,7 +1153,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -1190,7 +1190,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -1226,7 +1226,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -1280,7 +1280,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [], }, }); @@ -1304,7 +1304,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [], }, }) diff --git a/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts b/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts index c38d4c71c85833..c732ffcccc70c1 100644 --- a/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts @@ -104,7 +104,7 @@ async function update(success: boolean) { tags: ['bar'], params: { bar: true }, throttle: '10s', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [], }, }); diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index 0b7fa79422d467..7e97cb87f175ff 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -587,7 +587,7 @@ const BaseAlert: SanitizedAlert = { tags: [], consumer: 'alert-consumer', throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], params: { bar: true }, diff --git a/x-pack/plugins/alerts/server/routes/create.test.ts b/x-pack/plugins/alerts/server/routes/create.test.ts index a515adc0db36e9..7c0d05c5346eef 100644 --- a/x-pack/plugins/alerts/server/routes/create.test.ts +++ b/x-pack/plugins/alerts/server/routes/create.test.ts @@ -36,7 +36,7 @@ describe('createAlertRoute', () => { bar: true, }, throttle: '30s', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, actions: [ { group: 'default', @@ -111,7 +111,7 @@ describe('createAlertRoute', () => { "alertTypeId": "1", "consumer": "bar", "name": "abc", - "notifyOnStateChange": false, + "notifyOnlyOnActionGroupChange": false, "params": Object { "bar": true, }, diff --git a/x-pack/plugins/alerts/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts index 54961ff5295713..95e29352e9eb27 100644 --- a/x-pack/plugins/alerts/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -38,7 +38,7 @@ export const bodySchema = schema.object({ }), { defaultValue: [] } ), - notifyOnStateChange: schema.boolean({ defaultValue: false }), + notifyOnlyOnActionGroupChange: schema.maybe(schema.boolean({ defaultValue: false })), }); export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => { diff --git a/x-pack/plugins/alerts/server/routes/get.test.ts b/x-pack/plugins/alerts/server/routes/get.test.ts index d2cb379e770576..36f161375ba2fa 100644 --- a/x-pack/plugins/alerts/server/routes/get.test.ts +++ b/x-pack/plugins/alerts/server/routes/get.test.ts @@ -46,7 +46,7 @@ describe('getAlertRoute', () => { tags: ['foo'], enabled: true, muteAll: false, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, createdBy: '', updatedBy: '', apiKey: '', diff --git a/x-pack/plugins/alerts/server/routes/update.test.ts b/x-pack/plugins/alerts/server/routes/update.test.ts index 23ea0dc0d39570..3e7c24af78ea18 100644 --- a/x-pack/plugins/alerts/server/routes/update.test.ts +++ b/x-pack/plugins/alerts/server/routes/update.test.ts @@ -41,7 +41,7 @@ describe('updateAlertRoute', () => { }, }, ], - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, }; it('updates an alert with proper parameters', async () => { @@ -79,7 +79,7 @@ describe('updateAlertRoute', () => { }, }, ], - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, }, }, ['ok'] @@ -102,7 +102,7 @@ describe('updateAlertRoute', () => { }, ], "name": "abc", - "notifyOnStateChange": false, + "notifyOnlyOnActionGroupChange": false, "params": Object { "otherField": false, }, diff --git a/x-pack/plugins/alerts/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts index dc5156053f533b..a5dd3e6743282b 100644 --- a/x-pack/plugins/alerts/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -39,7 +39,7 @@ const bodySchema = schema.object({ }), { defaultValue: [] } ), - notifyOnStateChange: schema.boolean({ defaultValue: false }), + notifyOnlyOnActionGroupChange: schema.maybe(schema.boolean({ defaultValue: false })), }); export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => { @@ -63,11 +63,11 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => } const alertsClient = context.alerting.getAlertsClient(); const { id } = req.params; - const { name, actions, params, schedule, tags, throttle, notifyOnStateChange } = req.body; + const { name, actions, params, schedule, tags, throttle, notifyOnlyOnActionGroupChange } = req.body; return res.ok({ body: await alertsClient.update({ id, - data: { name, actions, params, schedule, tags, throttle, notifyOnStateChange }, + data: { name, actions, params, schedule, tags, throttle, notifyOnlyOnActionGroupChange }, }), }); }) diff --git a/x-pack/plugins/alerts/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json index b657a1bf5997bc..8b7183a58ba594 100644 --- a/x-pack/plugins/alerts/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerts/server/saved_objects/mappings.json @@ -74,7 +74,7 @@ "throttle": { "type": "keyword" }, - "notifyOnStateChange": { + "notifyOnlyOnActionGroupChange": { "type": "boolean" }, "muteAll": { diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts index a4cbc18e13b473..5c40548991be9c 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts @@ -292,6 +292,18 @@ describe('7.11.0', () => { }, }); }); + + test('add notifyOnlyOnActionGroupChange field - default to false', () => { + const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const alert = getMockData({}); + expect(migration711(alert, { log })).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + notifyOnlyOnActionGroupChange: false, + }, + }); + }); }); function getUpdatedAt(): string { diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts index d8ebced03c5a62..4a59869fe8b8a8 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.ts @@ -37,15 +37,15 @@ export function getMigrations( ) ); - const migrationAlertUpdatedAtDate = encryptedSavedObjects.createMigration( - // migrate all documents in 7.11 in order to add the "updatedAt" field + const migrationAlertUpdatedAtAndNotifyOnlyOnActionGroupChange = encryptedSavedObjects.createMigration( + // migrate all documents in 7.11 in order to add the "updatedAt" and "notifyOnlyOnActionGroupChange" fields (doc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(setAlertUpdatedAtDate) + pipeMigrations(setAlertUpdatedAtDate, setNotifyOnlyOnActionGroupChange) ); return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), - '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtDate, '7.11.0'), + '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyOnlyOnActionGroupChange, '7.11.0'), }; } @@ -79,6 +79,18 @@ const setAlertUpdatedAtDate = ( }; }; +const setNotifyOnlyOnActionGroupChange = ( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc => { + return { + ...doc, + attributes: { + ...doc.attributes, + notifyOnlyOnActionGroupChange: false, + }, + }; +}; + const consumersToChange: Map = new Map( Object.entries({ alerting: 'alerts', diff --git a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts index 023e4c041f7a89..1bf7d72e31a949 100644 --- a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts @@ -27,7 +27,7 @@ const alert: SanitizedAlert = { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 685785c738a193..14a68cc7fad20c 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -91,7 +91,7 @@ describe('Task Runner', () => { updatedAt: new Date('2019-02-12T21:01:22.479Z'), throttle: null, muteAll: false, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, enabled: true, alertTypeId: alertType.id, apiKey: '', @@ -352,7 +352,7 @@ describe('Task Runner', () => { }); }); - test('actionsPlugin.execute is not called when notifyOnStateChange is true and alert instance state does not change', async () => { + test('actionsPlugin.execute is not called when notifyOnlyOnActionGroupChange is true and alert instance state does not change', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( @@ -380,7 +380,7 @@ describe('Task Runner', () => { ); alertsClient.get.mockResolvedValue({ ...mockedAlertTypeSavedObject, - notifyOnStateChange: true, + notifyOnlyOnActionGroupChange: true, }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ id: '1', @@ -434,7 +434,7 @@ describe('Task Runner', () => { }); }); - test('actionsPlugin.execute is called when notifyOnStateChange is true and alert instance state has changed', async () => { + test('actionsPlugin.execute is called when notifyOnlyOnActionGroupChange is true and alert instance state has changed', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( @@ -449,7 +449,7 @@ describe('Task Runner', () => { ); alertsClient.get.mockResolvedValue({ ...mockedAlertTypeSavedObject, - notifyOnStateChange: true, + notifyOnlyOnActionGroupChange: true, }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ id: '1', diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 95ce8d108540db..e0b0213660876b 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -168,7 +168,7 @@ export class TaskRunner { ): Promise { const { throttle, - notifyOnStateChange, + notifyOnlyOnActionGroupChange, muteAll, mutedInstanceIds, name, @@ -248,7 +248,7 @@ export class TaskRunner { const mutedInstanceIdsSet = new Set(mutedInstanceIds); - const instancesToExecute = notifyOnStateChange + const instancesToExecute = notifyOnlyOnActionGroupChange ? Object.entries( instancesWithScheduledActions ).filter(([_, alertInstance]: [string, AlertInstance]) => diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 8199e87bc01427..d6365a785ad73f 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -151,7 +151,7 @@ export interface RawAlert extends SavedObjectAttributes { apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; - notifyOnStateChange: boolean; + notifyOnlyOnActionGroupChange: boolean; muteAll: boolean; mutedInstanceIds: string[]; meta?: AlertMeta; @@ -162,7 +162,7 @@ export type AlertInfoParams = Pick< RawAlert, | 'params' | 'throttle' - | 'notifyOnStateChange' + | 'notifyOnlyOnActionGroupChange' | 'muteAll' | 'mutedInstanceIds' | 'name' diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts index 78c5886abbf681..7652d7084198c4 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts @@ -64,7 +64,7 @@ describe('BaseAlert', () => { }, tags: [], throttle: '1d', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, }, }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index e2a040c5b10fb1..2a31880717114b 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -177,7 +177,7 @@ export class BaseAlert { name: this.label, alertTypeId: this.type, throttle: this.defaultThrottle, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, schedule: { interval: this.defaultInterval }, actions: alertActions, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts index 0fef36aadd2a9c..6b45fbf3cc5f99 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts @@ -31,6 +31,6 @@ export const createNotifications = async ({ enabled, actions: actions.map(transformRuleToAlertAction), throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts index 5b60a9b0b8ad6e..28c796a53097f1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts @@ -35,7 +35,7 @@ export const updateNotifications = async ({ ruleAlertId, }, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, }, }); } else if (interval && !notification) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 1a125fcc9440ef..135685e5252e35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -416,7 +416,7 @@ export const getResult = (): RuleAlertType => ({ enabled: true, actions: [], throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, createdBy: 'elastic', updatedBy: 'elastic', apiKey: null, @@ -642,7 +642,7 @@ export const getNotificationResult = (): RuleNotificationAlertType => ({ }, ], throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, apiKey: null, apiKeyOwner: 'elastic', createdBy: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index 6a90dbe2998160..4cd72bf40a95c5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -114,7 +114,7 @@ export const createRules = async ({ enabled, actions: actions.map(transformRuleToAlertAction), throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, }, }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index 6e95114fd8c62c..c5f6b9c43852e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -104,7 +104,7 @@ const rule: SanitizedAlert = { enabled: true, actions: [], throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, createdBy: 'elastic', updatedBy: 'elastic', apiKeyOwner: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index 8e10fc21f040c2..ae7acfd2ec4d63 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -178,6 +178,7 @@ export const patchRules = async ({ }, actions: actions?.map(transformRuleToAlertAction) ?? rule.actions, params: removeUndefined(nextParams), + notifyOnlyOnActionGroupChange: false, }; const [validated, errors] = validate(newRule, internalRuleUpdate); if (errors != null || validated === null) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index dab8769bcaa651..eaa42237facb4d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -74,6 +74,7 @@ export const updateRules = async ({ schedule: { interval: ruleUpdate.interval ?? '5m' }, actions: throttle === 'rule' ? (ruleUpdate.actions ?? []).map(transformRuleToAlertAction) : [], throttle: null, + notifyOnlyOnActionGroupChange: false, }; const update = await alertsClient.update({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index 5ddf5bbc4d02ce..4d7c87c7ce6ffe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -547,7 +547,7 @@ describe('createAlert', () => { actions: [], params: {}, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, createdAt: new Date('1970-01-01T00:00:00.000Z'), updatedAt: new Date('1970-01-01T00:00:00.000Z'), apiKey: null, @@ -573,7 +573,7 @@ describe('createAlert', () => { Array [ "/api/alerts/alert", Object { - "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyOnStateChange\\":false,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyOnlyOnActionGroupChange\\":false,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", }, ] `); @@ -596,7 +596,7 @@ describe('updateAlert', () => { updatedAt: new Date('1970-01-01T00:00:00.000Z'), apiKey: null, apiKeyOwner: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, }; const resolvedValue: Alert = { ...alertToUpdate, @@ -620,7 +620,7 @@ describe('updateAlert', () => { Array [ "/api/alerts/alert/123", Object { - "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"notifyOnStateChange\\":false}", + "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"notifyOnlyOnActionGroupChange\\":false}", }, ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index 7429831209affa..51d5f9b4ac19a5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -197,7 +197,7 @@ export async function updateAlert({ http: HttpSetup; alert: Pick< AlertUpdates, - 'throttle' | 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notifyOnStateChange' + 'throttle' | 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notifyOnlyOnActionGroupChange' >; id: string; }): Promise { @@ -210,7 +210,7 @@ export async function updateAlert({ 'schedule', 'params', 'actions', - 'notifyOnStateChange', + 'notifyOnlyOnActionGroupChange', ]) ), }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index d848250334c4d8..d31ce02b84a904 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -767,7 +767,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx index 4e3dc959010073..7061e3378446c4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx @@ -402,7 +402,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx index 8fd8a250dfae42..11c68ac1e4d1d1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx @@ -292,7 +292,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index 586cb5330409a5..08a69b7741247c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -131,7 +131,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx index 80cb595e4ea66d..3b12672971dcac 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx @@ -103,7 +103,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index c2e5724aeddc92..3ef9772d6bbd7a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -101,7 +101,7 @@ describe('alert_edit', () => { tags: [], name: 'test alert', throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, apiKeyOwner: null, createdBy: 'elastic', updatedBy: 'elastic', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index f8b6c0e11bf6ad..06abf473edf8fa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -179,8 +179,8 @@ describe('alert_form', () => { it('renders notify on state change only switch', async () => { await setup(); - const notifyOnStateChangeSwitch = wrapper.find('[data-test-subj="notifyOnStateChange"]'); - expect(notifyOnStateChangeSwitch.exists()).toBeTruthy(); + const notifyOnlyOnActionGroupChangeSwitch = wrapper.find('[data-test-subj="notifyOnlyOnActionGroupChange"]'); + expect(notifyOnlyOnActionGroupChangeSwitch.exists()).toBeTruthy(); }); it('renders registered action types', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index f0a1474a86ca7c..a34061dd4933e4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -659,13 +659,13 @@ export const AlertForm = ({ } onChange={(e) => { @@ -674,7 +674,7 @@ export const AlertForm = ({ setAlertThrottle(null); setAlertProperty('throttle', null); } - setAlertProperty('notifyOnStateChange', e.target.checked); + setAlertProperty('notifyOnlyOnActionGroupChange', e.target.checked); }} /> @@ -685,7 +685,7 @@ export const AlertForm = ({ fullWidth min={1} compressed - disabled={alert.notifyOnStateChange} + disabled={alert.notifyOnlyOnActionGroupChange} value={alertThrottle || ''} name="throttle" data-test-subj="throttleInput" @@ -713,7 +713,7 @@ export const AlertForm = ({ { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx index 65ced8893c619a..b4d68d84a14638 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx @@ -262,7 +262,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index f617f5b0314459..1c302113851f00 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -115,7 +115,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, throttle: '1m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, updatedBy: user.username, apiKeyOwner: user.username, muteAll: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index f54b2e3f23e8b2..ff39352147464f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -75,7 +75,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { createdAt: match.createdAt, updatedAt: match.updatedAt, throttle: '1m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, @@ -273,7 +273,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, createdAt: match.createdAt, updatedAt: match.updatedAt, executionStatus: match.executionStatus, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 0d03f7ee726baa..12e9f5b0397f59 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -71,7 +71,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { updatedAt: response.body.updatedAt, createdAt: response.body.createdAt, throttle: '1m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index efe60c2d36dd98..0450485144bcef 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -127,7 +127,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: {}, }, ], - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -215,7 +215,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, executionStatus: response.body.executionStatus, - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, }); expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); @@ -306,7 +306,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -401,7 +401,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -492,7 +492,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 7eba5df69889b2..58dababc621e63 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -83,7 +83,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { updatedBy: null, apiKeyOwner: null, throttle: '1m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], createdAt: response.body.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index e60ffbbdcacf7a..e9ca06f07bb826 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -52,7 +52,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { scheduledTaskId: match.scheduledTaskId, updatedBy: null, throttle: '1m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], createdAt: match.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 8fec926b87784f..324d10f106948a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -46,7 +46,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { updatedBy: null, apiKeyOwner: null, throttle: '1m', - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, muteAll: false, mutedInstanceIds: [], createdAt: response.body.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index bd6afacf206d9d..2ca9e348a37e36 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -91,5 +91,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(response.status).to.eql(200); expect(response.body.updatedAt).to.eql('2020-06-17T15:35:39.839Z'); }); + + it('7.11.0 migrates alerts to contain `notifyOnlyOnActionGroupChange` field', async () => { + const response = await supertest.get( + `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` + ); + + expect(response.status).to.eql(200); + expect(response.body.notifyOnlyOnActionGroupChange).to.eql(false); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index e608af0b64621a..76d90270893b04 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -54,7 +54,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], - notifyOnStateChange: false, + notifyOnlyOnActionGroupChange: false, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 0cc9e694eac51b..53b99d032caf2e 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -72,7 +72,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await defineAlert(alertName); await testSubjects.setValue('throttleInput', '10'); - await testSubjects.click('notifyOnStateChange'); + await testSubjects.click('notifyOnlyOnActionGroupChange'); const throttleInput = await find.byCssSelector('[data-test-subj="throttleInput"]'); expect(await throttleInput.getAttribute('value')).to.be.empty(); From 13fea7b7444d26390d0d538d2579a5a4bb2e387f Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 23 Nov 2020 15:00:01 -0500 Subject: [PATCH 26/45] Renaming field to a more descriptive name. Adding migrations --- x-pack/plugins/alerts/server/routes/update.ts | 20 +++++++++++++++++-- .../alerts/server/saved_objects/migrations.ts | 10 ++++++++-- .../public/application/lib/alert_api.ts | 8 +++++++- .../sections/alert_form/alert_form.test.tsx | 4 +++- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/alerts/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts index a5dd3e6743282b..f390a7f3b72ff8 100644 --- a/x-pack/plugins/alerts/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -63,11 +63,27 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => } const alertsClient = context.alerting.getAlertsClient(); const { id } = req.params; - const { name, actions, params, schedule, tags, throttle, notifyOnlyOnActionGroupChange } = req.body; + const { + name, + actions, + params, + schedule, + tags, + throttle, + notifyOnlyOnActionGroupChange, + } = req.body; return res.ok({ body: await alertsClient.update({ id, - data: { name, actions, params, schedule, tags, throttle, notifyOnlyOnActionGroupChange }, + data: { + name, + actions, + params, + schedule, + tags, + throttle, + notifyOnlyOnActionGroupChange, + }, }), }); }) diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts index 4a59869fe8b8a8..29fc4d98e96a93 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.ts @@ -37,7 +37,10 @@ export function getMigrations( ) ); - const migrationAlertUpdatedAtAndNotifyOnlyOnActionGroupChange = encryptedSavedObjects.createMigration( + const migrationAlertUpdatedAtAndNotifyOnlyOnActionGroupChange = encryptedSavedObjects.createMigration< + RawAlert, + RawAlert + >( // migrate all documents in 7.11 in order to add the "updatedAt" and "notifyOnlyOnActionGroupChange" fields (doc): doc is SavedObjectUnsanitizedDoc => true, pipeMigrations(setAlertUpdatedAtDate, setNotifyOnlyOnActionGroupChange) @@ -45,7 +48,10 @@ export function getMigrations( return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), - '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyOnlyOnActionGroupChange, '7.11.0'), + '7.11.0': executeMigrationWithErrorHandling( + migrationAlertUpdatedAtAndNotifyOnlyOnActionGroupChange, + '7.11.0' + ), }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index 51d5f9b4ac19a5..f8b6dfb0152010 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -197,7 +197,13 @@ export async function updateAlert({ http: HttpSetup; alert: Pick< AlertUpdates, - 'throttle' | 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notifyOnlyOnActionGroupChange' + | 'throttle' + | 'name' + | 'tags' + | 'schedule' + | 'params' + | 'actions' + | 'notifyOnlyOnActionGroupChange' >; id: string; }): Promise { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index 06abf473edf8fa..be17e466370d1e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -179,7 +179,9 @@ describe('alert_form', () => { it('renders notify on state change only switch', async () => { await setup(); - const notifyOnlyOnActionGroupChangeSwitch = wrapper.find('[data-test-subj="notifyOnlyOnActionGroupChange"]'); + const notifyOnlyOnActionGroupChangeSwitch = wrapper.find( + '[data-test-subj="notifyOnlyOnActionGroupChange"]' + ); expect(notifyOnlyOnActionGroupChangeSwitch.exists()).toBeTruthy(); }); From 2932b4eac82557702741a5f47ac77592e8c22790 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 24 Nov 2020 07:50:32 -0500 Subject: [PATCH 27/45] Fixing tests --- .../server/saved_objects/migrations.test.ts | 3 +++ .../server/task_runner/task_runner.test.ts | 16 ++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts index 5c40548991be9c..a2e17add01ff08 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts @@ -277,6 +277,7 @@ describe('7.11.0', () => { attributes: { ...alert.attributes, updatedAt: alert.updated_at, + notifyOnlyOnActionGroupChange: false, }, }); }); @@ -289,6 +290,7 @@ describe('7.11.0', () => { attributes: { ...alert.attributes, updatedAt: alert.attributes.createdAt, + notifyOnlyOnActionGroupChange: false, }, }); }); @@ -300,6 +302,7 @@ describe('7.11.0', () => { ...alert, attributes: { ...alert.attributes, + updatedAt: alert.attributes.createdAt, notifyOnlyOnActionGroupChange: false, }, }); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 14a68cc7fad20c..6f36aa105b9349 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -397,10 +397,13 @@ describe('Task Runner', () => { expect(eventLogger.logEvent).toHaveBeenCalledTimes(2); expect(eventLogger.logEvent).toHaveBeenCalledWith({ event: { - action: 'execute', - outcome: 'success', + action: 'active-instance', }, kibana: { + alerting: { + action_group_id: 'default', + instance_id: '1', + }, saved_objects: [ { id: '1', @@ -410,16 +413,17 @@ describe('Task Runner', () => { }, ], }, - message: "alert executed: test:1: 'alert-name'", + message: "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }); expect(eventLogger.logEvent).toHaveBeenCalledWith({ + '@timestamp': '1970-01-01T00:00:00.000Z', event: { - action: 'active-instance', + action: 'execute', + outcome: 'success', }, kibana: { alerting: { - instance_id: '1', - action_group_id: 'default', + status: 'active', }, saved_objects: [ { From 7b57ff890fb36658190d7a387caf9905d1bec668 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 7 Dec 2020 23:24:34 -0500 Subject: [PATCH 28/45] Type check and tests --- .../server/alerts_client/alerts_client.ts | 2 +- .../server/alerts_client/tests/update.test.ts | 2 + x-pack/plugins/alerts/server/routes/create.ts | 2 +- .../server/task_runner/task_runner.test.ts | 82 ++++++++++--------- .../detection_engine/schemas/rule_schemas.ts | 1 + 5 files changed, 50 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index 6aa4c02799ef8a..5ba275fff5f197 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -157,7 +157,7 @@ interface UpdateOptions { actions: NormalizedAlertAction[]; params: Record; throttle: string | null; - notifyOnStateChange: boolean; + notifyOnlyOnActionGroupChange: boolean; }; } diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index 328c7371398aee..9a4a383cd9557a 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -1356,6 +1356,7 @@ describe('update()', () => { }, throttle: null, actions: [], + notifyOnlyOnActionGroupChange: false, }, }); @@ -1385,6 +1386,7 @@ describe('update()', () => { }, throttle: null, actions: [], + notifyOnlyOnActionGroupChange: false, }, }) ).rejects.toThrow(); diff --git a/x-pack/plugins/alerts/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts index 95e29352e9eb27..94d50c058627cc 100644 --- a/x-pack/plugins/alerts/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -38,7 +38,7 @@ export const bodySchema = schema.object({ }), { defaultValue: [] } ), - notifyOnlyOnActionGroupChange: schema.maybe(schema.boolean({ defaultValue: false })), + notifyOnlyOnActionGroupChange: schema.boolean({ defaultValue: false }), }); export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => { diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 9873bc4db71330..88f7c0345ffc52 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -396,47 +396,55 @@ describe('Task Runner', () => { const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; expect(eventLogger.logEvent).toHaveBeenCalledTimes(2); - expect(eventLogger.logEvent).toHaveBeenCalledWith({ - event: { - action: 'active-instance', - }, - kibana: { - alerting: { - action_group_id: 'default', - instance_id: '1', - }, - saved_objects: [ - { - id: '1', - namespace: undefined, - rel: 'primary', - type: 'alert', + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "active-instance", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + }, + ], + }, + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }, ], - }, - message: "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", - }); - expect(eventLogger.logEvent).toHaveBeenCalledWith({ - '@timestamp': '1970-01-01T00:00:00.000Z', - event: { - action: 'execute', - outcome: 'success', - }, - kibana: { - alerting: { - status: 'active', - }, - saved_objects: [ - { - id: '1', - namespace: undefined, - rel: 'primary', - type: 'alert', + Array [ + Object { + "@timestamp": "1970-01-01T00:00:00.000Z", + "event": Object { + "action": "execute", + "outcome": "success", + }, + "kibana": Object { + "alerting": Object { + "status": "active", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", }, ], - }, - message: "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", - }); + ] + `); }); test('actionsPlugin.execute is called when notifyOnlyOnActionGroupChange is true and alert instance state has changed', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts index 5bb8d6d6746f9b..d58c18a079d362 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts @@ -194,6 +194,7 @@ export const internalRuleUpdate = t.type({ actions: actionsCamel, params: ruleParams, throttle: throttleOrNull, + notifyOnlyOnActionGroupChange: t.boolean, }); export type InternalRuleUpdate = t.TypeOf; From f062f9542b35e2ec95da3ddca74fcf33a1b72793 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 7 Dec 2020 23:29:10 -0500 Subject: [PATCH 29/45] Moving schedule and notify interval to bottom of flyout. Implementing dropdown from mockup in new component --- .../alert_form/action_frequency.test.tsx | 156 +++++++++ .../sections/alert_form/action_frequency.tsx | 249 ++++++++++++++ .../sections/alert_form/alert_add.tsx | 1 + .../sections/alert_form/alert_form.test.tsx | 28 -- .../sections/alert_form/alert_form.tsx | 320 +++++++----------- .../sections/alert_form/alert_reducer.ts | 5 +- 6 files changed, 541 insertions(+), 218 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx new file mode 100644 index 00000000000000..0c661799087ade --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx @@ -0,0 +1,156 @@ +/* + * 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 from 'react'; +import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { Alert } from '../../../types'; +import { ALERTS_FEATURE_ID } from '../../../../../alerts/common'; +import { ActionFrequencyForm } from './action_frequency'; + +describe('action_frequency_form', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + const alertThrottle = null; + const alertThrottleUnit = 'm'; + const onActionFreqencyChange = jest.fn(); + const onThrottleChange = jest.fn(); + const onThrottleUnitChange = jest.fn(); + + describe('action_frequency_form new alert', () => { + let wrapper: ReactWrapper; + + async function setup(overrides = {}) { + const initialAlert = ({ + name: 'test', + params: {}, + consumer: ALERTS_FEATURE_ID, + schedule: { + interval: '1m', + }, + actions: [], + tags: [], + muteAll: false, + enabled: false, + mutedInstanceIds: [], + notifyOnlyOnActionGroupChange: true, + ...overrides, + } as unknown) as Alert; + + wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + } + + it('should correctly select notifyOnlyOnActionGroupChange if it is true', async () => { + await setup(); + const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); + expect(actionFrequencySelect.exists()).toBeTruthy(); + expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual( + 'notifyOnlyOnActionGroupChange' + ); + + expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); + }); + + it('should correctly select throttleNull selection if notifyOnlyOnActionGroupChange is false and throttle is null', async () => { + await setup({ + notifyOnlyOnActionGroupChange: false, + }); + + const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); + expect(actionFrequencySelect.exists()).toBeTruthy(); + expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('throttleNull'); + expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); + }); + + it('should correctly render throttle values if defined', async () => { + await setup({ + notifyOnlyOnActionGroupChange: false, + throttle: '20m', + }); + + const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); + expect(actionFrequencySelect.exists()).toBeTruthy(); + expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('throttleCustom'); + + const throttleInput = wrapper.find('[data-test-subj="throttleInput"]'); + expect(throttleInput.exists()).toBeTruthy(); + expect(throttleInput.at(1).prop('value')).toEqual(20); + + const throttleUnitInput = wrapper.find('[data-test-subj="throttleUnitInput"]'); + expect(throttleUnitInput.exists()).toBeTruthy(); + expect(throttleUnitInput.at(1).prop('value')).toEqual('m'); + }); + + it('should update action frequency type correctly', async () => { + await setup(); + + wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="throttleNull"]').simulate('click'); + wrapper.update(); + expect(onActionFreqencyChange).toHaveBeenCalledWith({ + notifyOnlyOnActionGroupChange: false, + throttle: null, + }); + + wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="notifyOnlyOnActionGroupChange"]').simulate('click'); + wrapper.update(); + expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); + expect(onActionFreqencyChange).toHaveBeenCalledWith({ + notifyOnlyOnActionGroupChange: true, + throttle: null, + }); + }); + + it('should renders throttle input when custom throttle is selected and update throttle value', async () => { + await setup(); + wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="throttleCustom"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeTruthy(); + expect(onActionFreqencyChange).toHaveBeenCalled(); + + const newThrottle = 17; + const throttleField = wrapper.find('[data-test-subj="throttleInput"]'); + expect(throttleField.exists()).toBeTruthy(); + throttleField.at(1).simulate('change', { target: { value: newThrottle.toString() } }); + const throttleFieldAfterUpdate = wrapper.find('[data-test-subj="throttleInput"]'); + expect(throttleFieldAfterUpdate.at(1).prop('value')).toEqual(newThrottle); + expect(onThrottleChange).toHaveBeenCalledWith(17, 'm'); + + const newThrottleUnit = 'h'; + const throttleUnitField = wrapper.find('[data-test-subj="throttleUnitInput"]'); + expect(throttleUnitField.exists()).toBeTruthy(); + throttleUnitField.at(1).simulate('change', { target: { value: newThrottleUnit } }); + const throttleUnitFieldAfterUpdate = wrapper.find('[data-test-subj="throttleUnitInput"]'); + expect(throttleUnitFieldAfterUpdate.at(1).prop('value')).toEqual(newThrottleUnit); + expect(onThrottleUnitChange).toHaveBeenCalledWith('h'); + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx new file mode 100644 index 00000000000000..024ab2a77c42ad --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx @@ -0,0 +1,249 @@ +/* + * 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, useState, useEffect, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiFlexGrid, + EuiFormRow, + EuiFieldNumber, + EuiSelect, + EuiText, + EuiSpacer, + EuiSuperSelect, + EuiSuperSelectOption, +} from '@elastic/eui'; +import { some, filter, map } from 'fp-ts/lib/Option'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { InitialAlert } from './alert_reducer'; +import { getTimeOptions } from '../../../common/lib/get_time_options'; +import { + getDurationNumberInItsUnit, + getDurationUnitValue, +} from '../../../../../alerts/common/parse_duration'; + +type ActionFreqencyType = 'notifyOnlyOnActionGroupChange' | 'throttleNull' | 'throttleCustom'; + +type ActionFrequencyOptionsType = { + description: string; + formatOpts: (throttle: number | null, throttleUnit: string) => ActionFrequencyOpts; +} & EuiSuperSelectOption; + +const ActionFrequencyOptions: ActionFrequencyOptionsType[] = [ + { + value: 'notifyOnlyOnActionGroupChange', + inputDisplay: 'Run only on status change', + description: 'Actions will run when the alert status changes.', + formatOpts: () => ({ + throttle: null, + notifyOnlyOnActionGroupChange: true, + }), + }, + { + value: 'throttleNull', + inputDisplay: 'Run every time alert is active', + description: 'Actions will run with every active alert execution.', + formatOpts: () => ({ + throttle: null, + notifyOnlyOnActionGroupChange: false, + }), + }, + { + value: 'throttleCustom', + inputDisplay: 'Set a custom action interval', + description: 'Set a custom interval for the actions to run when the alert is active.', + formatOpts: (throttle: number | null, throttleUnit: string) => ({ + throttle: throttle!, + throttleUnit, + notifyOnlyOnActionGroupChange: false, + }), + }, +]; + +const DEFAULT_ACTION_FREQUENCY_VALUE: ActionFreqencyType = 'notifyOnlyOnActionGroupChange'; + +const ACTION_FREQUENCY_OPTIONS: Array< + EuiSuperSelectOption +> = ActionFrequencyOptions.map( + ({ value, inputDisplay, description }: ActionFrequencyOptionsType) => ({ + value, + inputDisplay, + 'data-test-subj': value, + dropdownDisplay: ( + + + + + +

+ +

+
+
+ ), + }) +); + +export interface ActionFrequencyOpts { + throttle: number | null; + throttleUnit?: string; + notifyOnlyOnActionGroupChange: boolean; +} + +interface ActionFrequencyFormProps { + alert: InitialAlert; + throttle: number | null; + throttleUnit: string; + onActionFreqencyChange: (opts: ActionFrequencyOpts) => void; + onThrottleChange: (throttle: number | null, throttleUnit: string) => void; + onThrottleUnitChange: (throttleUnit: string) => void; +} + +export const ActionFrequencyForm = ({ + alert, + onActionFreqencyChange, + onThrottleChange, + onThrottleUnitChange, +}: ActionFrequencyFormProps) => { + const [alertThrottle, setAlertThrottle] = useState(null); + const [alertThrottleUnit, setAlertThrottleUnit] = useState('m'); + const [showCustomActionFrequencyOpts, setShowCustomActionFrequencyOpts] = useState( + false + ); + const [actionFrequencyValue, setActionFrequencyValue] = useState( + DEFAULT_ACTION_FREQUENCY_VALUE + ); + + useEffect(() => { + setActionFrequencyValue( + alert.notifyOnlyOnActionGroupChange + ? 'notifyOnlyOnActionGroupChange' + : alert.throttle + ? 'throttleCustom' + : 'throttleNull' + ); + + if (!alert.throttle) { + setAlertThrottle( + alert.schedule.interval ? getDurationNumberInItsUnit(alert.schedule.interval) : null + ); + setAlertThrottleUnit( + alert.schedule.interval ? getDurationUnitValue(alert.schedule.interval) : 'm' + ); + } else { + setAlertThrottle(alert.throttle ? getDurationNumberInItsUnit(alert.throttle) : null); + setAlertThrottleUnit(alert.throttle ? getDurationUnitValue(alert.throttle) : 'm'); + } + }, [alert]); + + useEffect(() => { + setShowCustomActionFrequencyOpts(actionFrequencyValue === 'throttleCustom'); + }, [actionFrequencyValue]); + + const onActionFrequencyValueChange = useCallback((newValue: ActionFreqencyType) => { + const type = ActionFrequencyOptions.find( + (opt: ActionFrequencyOptionsType) => opt.value === newValue + ); + if (type) { + onActionFreqencyChange(type.formatOpts(alertThrottle, alertThrottleUnit)); + setActionFrequencyValue(newValue); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + +

+ +

+
+
+
+ + + + + + + + + {showCustomActionFrequencyOpts && ( + + + + + + { + pipe( + some(e.target.value.trim()), + filter((value) => value !== ''), + map((value) => parseInt(value, 10)), + filter((value) => !isNaN(value)), + map((value) => { + setAlertThrottle(value); + onThrottleChange(value, alertThrottleUnit); + }) + ); + }} + /> + + + { + setAlertThrottleUnit(e.target.value); + onThrottleUnitChange(e.target.value); + }} + /> + + + + + )} + + +
+ ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index 34a4c909c65a98..22f4ae661ff0a5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -45,6 +45,7 @@ export const AlertAdd = ({ }, actions: [], tags: [], + notifyOnlyOnActionGroupChange: true, ...(initialValues ? initialValues : {}), }), [alertTypeId, consumer, initialValues] diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index e095aebca8c30b..f820ec490ac430 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -178,14 +178,6 @@ describe('alert_form', () => { expect(alertTypeSelectOptions.exists()).toBeFalsy(); }); - it('renders notify on state change only switch', async () => { - await setup(); - const notifyOnlyOnActionGroupChangeSwitch = wrapper.find( - '[data-test-subj="notifyOnlyOnActionGroupChange"]' - ); - expect(notifyOnlyOnActionGroupChangeSwitch.exists()).toBeTruthy(); - }); - it('renders registered action types', async () => { await setup(); const alertTypeSelectOptions = wrapper.find( @@ -435,26 +427,6 @@ describe('alert_form', () => { expect(alertTypeSelectOptions.exists()).toBeTruthy(); }); - it('should update throttle value', async () => { - const newThrottle = 17; - await setup(); - const throttleField = wrapper.find('[data-test-subj="throttleInput"]'); - expect(throttleField.exists()).toBeTruthy(); - throttleField.at(1).simulate('change', { target: { value: newThrottle.toString() } }); - const throttleFieldAfterUpdate = wrapper.find('[data-test-subj="throttleInput"]'); - expect(throttleFieldAfterUpdate.at(1).prop('value')).toEqual(newThrottle); - }); - - it('should unset throttle value', async () => { - const newThrottle = ''; - await setup(); - const throttleField = wrapper.find('[data-test-subj="throttleInput"]'); - expect(throttleField.exists()).toBeTruthy(); - throttleField.at(1).simulate('change', { target: { value: newThrottle } }); - const throttleFieldAfterUpdate = wrapper.find('[data-test-subj="throttleInput"]'); - expect(throttleFieldAfterUpdate.at(1).prop('value')).toEqual(newThrottle); - }); - it('renders alert type description', async () => { await setup(); const alertDescription = wrapper.find('[data-test-subj="alertDescription"]'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index d55fb8bec6e755..86c9f27a444c30 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -20,7 +20,6 @@ import { EuiComboBox, EuiFieldNumber, EuiSelect, - EuiIconTip, EuiButtonIcon, EuiHorizontalRule, EuiLoadingSpinner, @@ -29,12 +28,9 @@ import { EuiListGroup, EuiLink, EuiText, - EuiSwitch, EuiNotificationBadge, EuiErrorBoundary, } from '@elastic/eui'; -import { some, filter, map, fold } from 'fp-ts/lib/Option'; -import { pipe } from 'fp-ts/lib/pipeable'; import { capitalize, isObject } from 'lodash'; import { KibanaFeature } from '../../../../../features/public'; import { @@ -61,6 +57,7 @@ import { SolutionFilter } from './solution_filter'; import './alert_form.scss'; import { recoveredActionGroupMessage } from '../../constants'; import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params'; +import { ActionFrequencyForm, ActionFrequencyOpts } from './action_frequency'; const ENTER_KEY = 13; @@ -527,41 +524,9 @@ export const AlertForm = ({ ); - const labelForAlertChecked = ( - <> - {' '} - - - ); - - const labelForAlertRenotify = ( - <> - {' '} - - - ); - return ( - + - - - { - const newOptions = [...tagsOptions, { label: searchValue }]; - setAlertProperty( - 'tags', - newOptions.map((newOption) => newOption.label) - ); - }} - onChange={(selectedOptions: Array<{ label: string }>) => { - setAlertProperty( - 'tags', - selectedOptions.map((selectedOption) => selectedOption.label) - ); - }} - onBlur={() => { - if (!alert.tags) { - setAlertProperty('tags', []); - } - }} - /> - - - - - - - 0} - error={errors.interval} - > - - - 0} - compressed - value={alertInterval || ''} - name="interval" - data-test-subj="intervalInput" - onChange={(e) => { - const interval = - e.target.value !== '' ? parseInt(e.target.value, 10) : undefined; - setAlertInterval(interval); - setScheduleProperty('interval', `${e.target.value}${alertIntervalUnit}`); - }} - /> - - - { - setAlertIntervalUnit(e.target.value); - setScheduleProperty('interval', `${alertInterval}${e.target.value}`); - }} - /> - - - - - - - - - - - } - onChange={(e) => { - if (e.target.checked) { - // unset throttle - setAlertThrottle(null); - setAlertProperty('throttle', null); - } - setAlertProperty('notifyOnlyOnActionGroupChange', e.target.checked); - }} - /> - - - - - { - pipe( - some(e.target.value.trim()), - filter((value) => value !== ''), - map((value) => parseInt(value, 10)), - filter((value) => !isNaN(value)), - fold( - () => { - // unset throttle - setAlertThrottle(null); - setAlertProperty('throttle', null); - }, - (throttle) => { - setAlertThrottle(throttle); - setAlertProperty('throttle', `${throttle}${alertThrottleUnit}`); - } - ) - ); - }} - /> - - - { - setAlertThrottleUnit(e.target.value); - if (alertThrottle) { - setAlertProperty('throttle', `${alertThrottle}${e.target.value}`); - } - }} - /> - - - - {alertTypeModel ? ( @@ -802,6 +615,135 @@ export const AlertForm = ({ ) : ( )} + + + + + +

+ +

+
+
+
+ + + + + + + + 0} + error={errors.interval} + > + + + 0} + compressed + value={alertInterval || ''} + name="interval" + data-test-subj="intervalInput" + prepend={i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.alertSchedule.label', + { + defaultMessage: 'Every', + } + )} + onChange={(e) => { + const interval = + e.target.value !== '' ? parseInt(e.target.value, 10) : undefined; + setAlertInterval(interval); + setScheduleProperty('interval', `${e.target.value}${alertIntervalUnit}`); + }} + /> + + + { + setAlertIntervalUnit(e.target.value); + setScheduleProperty('interval', `${alertInterval}${e.target.value}`); + }} + /> + + + + + + { + setAlertThrottle(opts.throttle); + setAlertProperty('notifyOnlyOnActionGroupChange', opts.notifyOnlyOnActionGroupChange); + }} + onThrottleChange={useCallback( + (throttle: number | null, throttleUnit: string) => { + setAlertThrottle(throttle); + setAlertProperty('throttle', throttle ? `${throttle}${throttleUnit}` : null); + }, + [setAlertProperty] + )} + onThrottleUnitChange={(throttleUnit: string) => { + setAlertThrottleUnit(throttleUnit); + if (alertThrottle) { + setAlertProperty('throttle', `${alertThrottle}${throttleUnit}`); + } + }} + /> + + + + + { + const newOptions = [...tagsOptions, { label: searchValue }]; + setAlertProperty( + 'tags', + newOptions.map((newOption) => newOption.label) + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + setAlertProperty( + 'tags', + selectedOptions.map((selectedOption) => selectedOption.label) + ); + }} + onBlur={() => { + if (!alert.tags) { + setAlertProperty('tags', []); + } + }} + /> + + +
); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts index e54895318fc700..4ce5458fa356e3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts @@ -10,7 +10,10 @@ import { AlertActionParam, IntervalSchedule } from '../../../../../alerts/common import { Alert, AlertAction } from '../../../types'; export type InitialAlert = Partial & - Pick; + Pick< + Alert, + 'params' | 'consumer' | 'schedule' | 'actions' | 'tags' | 'notifyOnlyOnActionGroupChange' + >; interface CommandType< T extends From ff0ef3ba72447c5397134b3e547d3c650c8c41b2 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 08:57:30 -0500 Subject: [PATCH 30/45] Changing boolean flag to enum type and updating in triggers_actions_ui --- x-pack/plugins/alerts/common/alert.ts | 4 +- x-pack/plugins/alerts/server/routes/update.ts | 2 +- .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - .../public/application/lib/alert_api.test.ts | 10 +- .../public/application/lib/alert_api.ts | 18 +-- .../components/alert_details.test.tsx | 2 +- .../components/alert_details_route.test.tsx | 2 +- .../components/alert_instances.test.tsx | 2 +- .../components/alert_instances_route.test.tsx | 2 +- .../components/view_in_app.test.tsx | 2 +- .../alert_form/action_frequency.test.tsx | 39 +++--- .../sections/alert_form/action_frequency.tsx | 129 +++++++++--------- .../sections/alert_form/alert_add.tsx | 2 +- .../sections/alert_form/alert_edit.test.tsx | 2 +- .../sections/alert_form/alert_form.tsx | 6 +- .../sections/alert_form/alert_reducer.test.ts | 1 + .../sections/alert_form/alert_reducer.ts | 5 +- .../with_bulk_alert_api_operations.test.tsx | 2 +- .../triggers_actions_ui/public/types.ts | 2 + 20 files changed, 108 insertions(+), 132 deletions(-) diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts index 6d0a9a9191004e..d7443a08eb1dcb 100644 --- a/x-pack/plugins/alerts/common/alert.ts +++ b/x-pack/plugins/alerts/common/alert.ts @@ -27,6 +27,8 @@ export enum AlertExecutionStatusErrorReasons { Unknown = 'unknown', } +export type AlertNotifyWhenType = 'onActionGroupChange' | 'onActiveAlert' | 'onThrottleInterval'; + export interface AlertExecutionStatus { status: AlertExecutionStatuses; lastExecutionDate: Date; @@ -68,7 +70,7 @@ export interface Alert { apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; - notifyOnlyOnActionGroupChange: boolean; + notifyWhen: AlertNotifyWhenType | null; muteAll: boolean; mutedInstanceIds: string[]; executionStatus: AlertExecutionStatus; diff --git a/x-pack/plugins/alerts/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts index f390a7f3b72ff8..045bff53a3b229 100644 --- a/x-pack/plugins/alerts/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -39,7 +39,7 @@ const bodySchema = schema.object({ }), { defaultValue: [] } ), - notifyOnlyOnActionGroupChange: schema.maybe(schema.boolean({ defaultValue: false })), + notifyOnlyOnActionGroupChange: schema.boolean({ defaultValue: false }), }); export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9df495717b431c..9d3199d6b8d1cb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19668,8 +19668,6 @@ "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "コネクターを作成する", "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新規追加", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名前", - "xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "確認間隔", - "xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "条件を評価する頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "{actionTypeName}コネクターがありません", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedAlertTypes": "アラートを{operation}するには、適切な権限が付与されている必要があります。", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedAlertTypesTitle": "アラートタイプを{operation}する権限がありません。", @@ -19681,8 +19679,6 @@ "xpack.triggersActionsUI.sections.alertForm.loadingConnectorsDescription": "コネクターを読み込んでいます...", "xpack.triggersActionsUI.sections.alertForm.newAlertActionTypeEditTitle": "{actionConnectorName}", "xpack.triggersActionsUI.sections.alertForm.preconfiguredTitleMessage": "構成済み", - "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知間隔", - "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "アラートがアクティブな間にアクションを繰り返す頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "アクションタイプを選択してください", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "デフォルトアクショングループの定義がないのでアクションを追加できません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fc70ada752a94f..f05e21e71fbf38 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19688,8 +19688,6 @@ "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "创建连接器", "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新添", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名称", - "xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "检查频率", - "xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "定义评估条件的频率。", "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "无 {actionTypeName} 连接器", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedAlertTypes": "为了{operation}告警,您需要获得相应的权限。", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedAlertTypesTitle": "您尚无权{operation}任何告警类型", @@ -19701,8 +19699,6 @@ "xpack.triggersActionsUI.sections.alertForm.loadingConnectorsDescription": "正在加载连接器……", "xpack.triggersActionsUI.sections.alertForm.newAlertActionTypeEditTitle": "{actionConnectorName}", "xpack.triggersActionsUI.sections.alertForm.preconfiguredTitleMessage": "预配置", - "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知频率", - "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "定义告警处于活动状态时重复操作的频率。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "选择操作类型", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "无法添加操作,因为未定义默认操作组", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index cd74243f62302c..32b663c5693fca 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -29,7 +29,7 @@ import { mapFiltersToKql, } from './alert_api'; import uuid from 'uuid'; -import { ALERTS_FEATURE_ID } from '../../../../alerts/common'; +import { AlertNotifyWhenType, ALERTS_FEATURE_ID } from '../../../../alerts/common'; const http = httpServiceMock.createStartContract(); @@ -548,7 +548,7 @@ describe('createAlert', () => { actions: [], params: {}, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActionGroupChange' as AlertNotifyWhenType, createdAt: new Date('1970-01-01T00:00:00.000Z'), updatedAt: new Date('1970-01-01T00:00:00.000Z'), apiKey: null, @@ -574,7 +574,7 @@ describe('createAlert', () => { Array [ "/api/alerts/alert", Object { - "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyOnlyOnActionGroupChange\\":false,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyWhen\\":\\"onActionGroupChange\\",\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", }, ] `); @@ -597,7 +597,7 @@ describe('updateAlert', () => { updatedAt: new Date('1970-01-01T00:00:00.000Z'), apiKey: null, apiKeyOwner: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onThrottleInterval' as AlertNotifyWhenType, }; const resolvedValue: Alert = { ...alertToUpdate, @@ -621,7 +621,7 @@ describe('updateAlert', () => { Array [ "/api/alerts/alert/123", Object { - "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"notifyOnlyOnActionGroupChange\\":false}", + "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"notifyWhen\\":\\"onThrottleInterval\\"}", }, ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index ca0b168e8e69e6..52ab33566da74d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -197,27 +197,13 @@ export async function updateAlert({ http: HttpSetup; alert: Pick< AlertUpdates, - | 'throttle' - | 'name' - | 'tags' - | 'schedule' - | 'params' - | 'actions' - | 'notifyOnlyOnActionGroupChange' + 'throttle' | 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notifyWhen' >; id: string; }): Promise { return await http.put(`${BASE_ALERT_API_PATH}/alert/${id}`, { body: JSON.stringify( - pick(alert, [ - 'throttle', - 'name', - 'tags', - 'schedule', - 'params', - 'actions', - 'notifyOnlyOnActionGroupChange', - ]) + pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions', 'notifyWhen']) ), }); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index eb0d64d8b8d3ef..c10653d14d409c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -775,7 +775,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx index 41c09b2e64f1b9..48360647e24ee5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx @@ -397,7 +397,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx index af0e24c951b871..be68036c0f7435 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx @@ -286,7 +286,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index d1f92c72e6822e..b24d552bd5c489 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -126,7 +126,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx index 0bf9f1edf72587..f025c886e0712d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx @@ -84,7 +84,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx index 0c661799087ade..4c191d8d0eb88d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx @@ -38,7 +38,7 @@ describe('action_frequency_form', () => { muteAll: false, enabled: false, mutedInstanceIds: [], - notifyOnlyOnActionGroupChange: true, + notifyWhen: 'onActionGroupChange', ...overrides, } as unknown) as Alert; @@ -59,39 +59,37 @@ describe('action_frequency_form', () => { }); } - it('should correctly select notifyOnlyOnActionGroupChange if it is true', async () => { + it(`should correctly select 'onActionGroupChange' option on initial render`, async () => { await setup(); const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); expect(actionFrequencySelect.exists()).toBeTruthy(); - expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual( - 'notifyOnlyOnActionGroupChange' - ); + expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('onActionGroupChange'); expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); }); - it('should correctly select throttleNull selection if notifyOnlyOnActionGroupChange is false and throttle is null', async () => { + it(`should correctly select 'onActiveAlert' option on initial render`, async () => { await setup({ - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', }); const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); expect(actionFrequencySelect.exists()).toBeTruthy(); - expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('throttleNull'); + expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('onActiveAlert'); expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); }); - it('should correctly render throttle values if defined', async () => { + it(`should correctly select 'onThrottleInterval' option on initial render and render throttle inputs`, async () => { await setup({ - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onThrottleInterval', throttle: '20m', }); const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); expect(actionFrequencySelect.exists()).toBeTruthy(); - expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('throttleCustom'); + expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('onThrottleInterval'); const throttleInput = wrapper.find('[data-test-subj="throttleInput"]'); expect(throttleInput.exists()).toBeTruthy(); @@ -107,34 +105,29 @@ describe('action_frequency_form', () => { wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); wrapper.update(); - wrapper.find('button[data-test-subj="throttleNull"]').simulate('click'); + wrapper.find('button[data-test-subj="onActiveAlert"]').simulate('click'); wrapper.update(); expect(onActionFreqencyChange).toHaveBeenCalledWith({ - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', throttle: null, }); wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); wrapper.update(); - wrapper.find('button[data-test-subj="notifyOnlyOnActionGroupChange"]').simulate('click'); + wrapper.find('button[data-test-subj="onActionGroupChange"]').simulate('click'); wrapper.update(); expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); expect(onActionFreqencyChange).toHaveBeenCalledWith({ - notifyOnlyOnActionGroupChange: true, + notifyWhen: 'onActionGroupChange', throttle: null, }); }); it('should renders throttle input when custom throttle is selected and update throttle value', async () => { - await setup(); - wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); - wrapper.update(); - wrapper.find('button[data-test-subj="throttleCustom"]').simulate('click'); - wrapper.update(); - - expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeTruthy(); - expect(onActionFreqencyChange).toHaveBeenCalled(); + await setup({ + notifyWhen: 'onThrottleInterval', + }); const newThrottle = 17; const throttleField = wrapper.find('[data-test-subj="throttleInput"]'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx index 024ab2a77c42ad..978a4bb01dc6c7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx @@ -27,79 +27,85 @@ import { getDurationNumberInItsUnit, getDurationUnitValue, } from '../../../../../alerts/common/parse_duration'; +import { AlertNotifyWhenType } from '../../../types'; -type ActionFreqencyType = 'notifyOnlyOnActionGroupChange' | 'throttleNull' | 'throttleCustom'; +const DEFAULT_ACTION_FREQUENCY_VALUE: AlertNotifyWhenType = 'onActionGroupChange'; -type ActionFrequencyOptionsType = { - description: string; - formatOpts: (throttle: number | null, throttleUnit: string) => ActionFrequencyOpts; -} & EuiSuperSelectOption; - -const ActionFrequencyOptions: ActionFrequencyOptionsType[] = [ +const ACTION_FREQUENCY_OPTIONS: Array> = [ { - value: 'notifyOnlyOnActionGroupChange', + value: 'onActionGroupChange', inputDisplay: 'Run only on status change', - description: 'Actions will run when the alert status changes.', - formatOpts: () => ({ - throttle: null, - notifyOnlyOnActionGroupChange: true, - }), + 'data-test-subj': 'onActionGroupChange', + dropdownDisplay: ( + + + + + +

+ +

+
+
+ ), }, { - value: 'throttleNull', + value: 'onActiveAlert', inputDisplay: 'Run every time alert is active', - description: 'Actions will run with every active alert execution.', - formatOpts: () => ({ - throttle: null, - notifyOnlyOnActionGroupChange: false, - }), + 'data-test-subj': 'onActiveAlert', + dropdownDisplay: ( + + + + + +

+ +

+
+
+ ), }, { - value: 'throttleCustom', + value: 'onThrottleInterval', inputDisplay: 'Set a custom action interval', - description: 'Set a custom interval for the actions to run when the alert is active.', - formatOpts: (throttle: number | null, throttleUnit: string) => ({ - throttle: throttle!, - throttleUnit, - notifyOnlyOnActionGroupChange: false, - }), - }, -]; - -const DEFAULT_ACTION_FREQUENCY_VALUE: ActionFreqencyType = 'notifyOnlyOnActionGroupChange'; - -const ACTION_FREQUENCY_OPTIONS: Array< - EuiSuperSelectOption -> = ActionFrequencyOptions.map( - ({ value, inputDisplay, description }: ActionFrequencyOptionsType) => ({ - value, - inputDisplay, - 'data-test-subj': value, + 'data-test-subj': 'onThrottleInterval', dropdownDisplay: (

), - }) -); + }, +]; export interface ActionFrequencyOpts { throttle: number | null; - throttleUnit?: string; - notifyOnlyOnActionGroupChange: boolean; + notifyWhen: AlertNotifyWhenType; } interface ActionFrequencyFormProps { @@ -122,18 +128,17 @@ export const ActionFrequencyForm = ({ const [showCustomActionFrequencyOpts, setShowCustomActionFrequencyOpts] = useState( false ); - const [actionFrequencyValue, setActionFrequencyValue] = useState( + const [actionFrequencyValue, setActionFrequencyValue] = useState( DEFAULT_ACTION_FREQUENCY_VALUE ); useEffect(() => { - setActionFrequencyValue( - alert.notifyOnlyOnActionGroupChange - ? 'notifyOnlyOnActionGroupChange' - : alert.throttle - ? 'throttleCustom' - : 'throttleNull' - ); + if (alert.notifyWhen) { + setActionFrequencyValue(alert.notifyWhen); + } else { + // If 'notifyWhen' is not set, derive value from existence of throttle value + setActionFrequencyValue(alert.throttle ? 'onThrottleInterval' : 'onActiveAlert'); + } if (!alert.throttle) { setAlertThrottle( @@ -149,17 +154,15 @@ export const ActionFrequencyForm = ({ }, [alert]); useEffect(() => { - setShowCustomActionFrequencyOpts(actionFrequencyValue === 'throttleCustom'); + setShowCustomActionFrequencyOpts(actionFrequencyValue === 'onThrottleInterval'); }, [actionFrequencyValue]); - const onActionFrequencyValueChange = useCallback((newValue: ActionFreqencyType) => { - const type = ActionFrequencyOptions.find( - (opt: ActionFrequencyOptionsType) => opt.value === newValue - ); - if (type) { - onActionFreqencyChange(type.formatOpts(alertThrottle, alertThrottleUnit)); - setActionFrequencyValue(newValue); - } + const onActionFrequencyValueChange = useCallback((newValue: AlertNotifyWhenType) => { + onActionFreqencyChange({ + notifyWhen: newValue, + throttle: newValue === 'onThrottleInterval' ? alertThrottle! : null, + }); + setActionFrequencyValue(newValue); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index a4c3744e1e63db..5ab2c7f5a586ca 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -59,7 +59,7 @@ const AlertAdd = ({ }, actions: [], tags: [], - notifyOnlyOnActionGroupChange: true, + notifyWhen: 'onActionGroupChange', ...(initialValues ? initialValues : {}), }), [alertTypeId, consumer, initialValues] diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 420bf2ed3c8126..25f830df58df53 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -103,7 +103,7 @@ describe('alert_edit', () => { tags: [], name: 'test alert', throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, apiKeyOwner: null, createdBy: 'elastic', updatedBy: 'elastic', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 7f89bf5da761c1..460986743de2f4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -698,9 +698,9 @@ export const AlertForm = ({ alert={alert} throttle={alertThrottle} throttleUnit={alertThrottleUnit} - onActionFreqencyChange={(opts: ActionFrequencyOpts) => { - setAlertThrottle(opts.throttle); - setAlertProperty('notifyOnlyOnActionGroupChange', opts.notifyOnlyOnActionGroupChange); + onActionFreqencyChange={({ throttle, notifyWhen }: ActionFrequencyOpts) => { + setAlertThrottle(throttle); + setAlertProperty('notifyWhen', notifyWhen); }} onThrottleChange={useCallback( (throttle: number | null, throttleUnit: string) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts index 4e4d8e237aa2fd..71f486cb311b94 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts @@ -18,6 +18,7 @@ describe('alert reducer', () => { }, actions: [], tags: [], + notifyWhen: 'onActionGroupChange', } as unknown) as Alert; }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts index 4ce5458fa356e3..b86e0d1555315e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts @@ -10,10 +10,7 @@ import { AlertActionParam, IntervalSchedule } from '../../../../../alerts/common import { Alert, AlertAction } from '../../../types'; export type InitialAlert = Partial & - Pick< - Alert, - 'params' | 'consumer' | 'schedule' | 'actions' | 'tags' | 'notifyOnlyOnActionGroupChange' - >; + Pick; interface CommandType< T extends diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx index f85132f951bc9a..4de4ea02e567a2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx @@ -250,7 +250,7 @@ function mockAlert(overloads: Partial = {}): Alert { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 1e11102516fc13..f94983495f215d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -20,6 +20,7 @@ import { AlertInstanceStatus, RawAlertInstance, AlertingFrameworkHealth, + AlertNotifyWhenType, } from '../../alerts/common'; export { Alert, @@ -30,6 +31,7 @@ export { AlertInstanceStatus, RawAlertInstance, AlertingFrameworkHealth, + AlertNotifyWhenType, }; export { ActionType }; From e3314987a0777ad33e72290a1e22ffdaa70bb112 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 10:52:09 -0500 Subject: [PATCH 31/45] Changing boolean flag to enum type and updating in alerts plugin --- x-pack/plugins/alerts/common/alert.ts | 3 +- .../common/alert_notify_when_type.test.ts | 18 + .../alerts/common/alert_notify_when_type.ts | 15 + x-pack/plugins/alerts/common/index.ts | 1 + .../server/alerts_client/alerts_client.ts | 20 +- .../server/alerts_client/tests/create.test.ts | 434 +++++++++++++++++- .../server/alerts_client/tests/find.test.ts | 3 + .../server/alerts_client/tests/get.test.ts | 2 + .../tests/get_alert_instance_summary.test.ts | 2 +- .../server/alerts_client/tests/update.test.ts | 44 +- .../alerts_client_conflict_retries.test.ts | 2 +- ...rt_instance_summary_from_event_log.test.ts | 2 +- .../alerts/server/routes/create.test.ts | 5 +- x-pack/plugins/alerts/server/routes/create.ts | 4 +- .../plugins/alerts/server/routes/get.test.ts | 2 +- .../alerts/server/routes/update.test.ts | 6 +- x-pack/plugins/alerts/server/routes/update.ts | 16 +- .../alerts/server/saved_objects/mappings.json | 4 +- .../server/saved_objects/migrations.test.ts | 21 +- .../alerts/server/saved_objects/migrations.ts | 16 +- .../task_runner/alert_task_instance.test.ts | 2 +- .../server/task_runner/task_runner.test.ts | 10 +- .../alerts/server/task_runner/task_runner.ts | 23 +- x-pack/plugins/alerts/server/types.ts | 5 +- 24 files changed, 575 insertions(+), 85 deletions(-) create mode 100644 x-pack/plugins/alerts/common/alert_notify_when_type.test.ts create mode 100644 x-pack/plugins/alerts/common/alert_notify_when_type.ts diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts index d7443a08eb1dcb..e0e73e978f775a 100644 --- a/x-pack/plugins/alerts/common/alert.ts +++ b/x-pack/plugins/alerts/common/alert.ts @@ -5,6 +5,7 @@ */ import { SavedObjectAttribute, SavedObjectAttributes } from 'kibana/server'; +import { AlertNotifyWhenType } from './alert_notify_when_type'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AlertTypeState = Record; @@ -27,8 +28,6 @@ export enum AlertExecutionStatusErrorReasons { Unknown = 'unknown', } -export type AlertNotifyWhenType = 'onActionGroupChange' | 'onActiveAlert' | 'onThrottleInterval'; - export interface AlertExecutionStatus { status: AlertExecutionStatuses; lastExecutionDate: Date; diff --git a/x-pack/plugins/alerts/common/alert_notify_when_type.test.ts b/x-pack/plugins/alerts/common/alert_notify_when_type.test.ts new file mode 100644 index 00000000000000..ad0b0430c6c1fb --- /dev/null +++ b/x-pack/plugins/alerts/common/alert_notify_when_type.test.ts @@ -0,0 +1,18 @@ +/* + * 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 { validateNotifyWhenType } from './alert_notify_when_type'; + +test('validates valid notify when type', () => { + expect(validateNotifyWhenType('onActionGroupChange')).toBeUndefined(); + expect(validateNotifyWhenType('onActiveAlert')).toBeUndefined(); + expect(validateNotifyWhenType('onThrottleInterval')).toBeUndefined(); +}); +test('returns error string if input is not valid notify when type', () => { + expect(validateNotifyWhenType('randomString')).toEqual( + `string is not a valid AlertNotifyWhenType: randomString` + ); +}); diff --git a/x-pack/plugins/alerts/common/alert_notify_when_type.ts b/x-pack/plugins/alerts/common/alert_notify_when_type.ts new file mode 100644 index 00000000000000..a0a00e258a29c7 --- /dev/null +++ b/x-pack/plugins/alerts/common/alert_notify_when_type.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +const AlertNotifyWhenTypeValues = ['onActionGroupChange', 'onActiveAlert', 'onThrottleInterval']; +export type AlertNotifyWhenType = typeof AlertNotifyWhenTypeValues[number]; + +export function validateNotifyWhenType(notifyWhen: string) { + if (AlertNotifyWhenTypeValues.includes(notifyWhen)) { + return; + } + return `string is not a valid AlertNotifyWhenType: ${notifyWhen}`; +} diff --git a/x-pack/plugins/alerts/common/index.ts b/x-pack/plugins/alerts/common/index.ts index 4d0e7bf7eb0bc7..811e761522dfb8 100644 --- a/x-pack/plugins/alerts/common/index.ts +++ b/x-pack/plugins/alerts/common/index.ts @@ -13,6 +13,7 @@ export * from './alert_task_instance'; export * from './alert_navigation'; export * from './alert_instance_summary'; export * from './builtin_action_groups'; +export * from './alert_notify_when_type'; export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index 5ba275fff5f197..50c17af1194247 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -29,6 +29,7 @@ import { AlertTaskState, AlertInstanceSummary, AlertExecutionStatusValues, + AlertNotifyWhenType, } from '../types'; import { validateAlertTypeParams, alertExecutionStatusFromRaw } from '../lib'; import { @@ -157,7 +158,7 @@ interface UpdateOptions { actions: NormalizedAlertAction[]; params: Record; throttle: string | null; - notifyOnlyOnActionGroupChange: boolean; + notifyWhen: AlertNotifyWhenType | null; }; } @@ -252,6 +253,8 @@ export class AlertsClient { const createTime = Date.now(); const { references, actions } = await this.denormalizeActions(data.actions); + const notifyWhen = this.validateAlertNotifyWhenType(data.notifyWhen, data.throttle); + const rawAlert: RawAlert = { ...data, ...this.apiKeyAsAlertAttributes(createdAPIKey, username), @@ -263,6 +266,7 @@ export class AlertsClient { params: validatedAlertTypeParams as RawAlert['params'], muteAll: false, mutedInstanceIds: [], + notifyWhen, executionStatus: { status: 'pending', lastExecutionDate: new Date().toISOString(), @@ -695,6 +699,7 @@ export class AlertsClient { ? await this.createAPIKey(this.generateAPIKeyName(alertType.id, data.name)) : null; const apiKeyAttributes = this.apiKeyAsAlertAttributes(createdAPIKey, username); + const notifyWhen = this.validateAlertNotifyWhenType(data.notifyWhen, data.throttle); let updatedObject: SavedObject; const createAttributes = this.updateMeta({ @@ -703,6 +708,7 @@ export class AlertsClient { ...apiKeyAttributes, params: validatedAlertTypeParams as RawAlert['params'], actions, + notifyWhen, updatedBy: username, updatedAt: new Date().toISOString(), }); @@ -1327,7 +1333,7 @@ export class AlertsClient { private getPartialAlertFromRaw( id: string, - { createdAt, updatedAt, meta, scheduledTaskId, ...rawAlert }: Partial, + { createdAt, updatedAt, meta, notifyWhen, scheduledTaskId, ...rawAlert }: Partial, references: SavedObjectReference[] | undefined ): PartialAlert { // Not the prettiest code here, but if we want to use most of the @@ -1342,6 +1348,7 @@ export class AlertsClient { const executionStatus = alertExecutionStatusFromRaw(this.logger, id, rawAlert.executionStatus); return { id, + notifyWhen, ...rawAlertWithoutExecutionStatus, // we currently only support the Interval Schedule type // Once we support additional types, this type signature will likely change @@ -1375,6 +1382,15 @@ export class AlertsClient { } } + private validateAlertNotifyWhenType( + notifyWhen: AlertNotifyWhenType | null, + throttle: string | null + ): AlertNotifyWhenType { + // We allow notifyWhen to be null for backwards compatibility. If it is null, determine its + // value based on whether the throttle is set to a value or null + return notifyWhen ? notifyWhen! : throttle ? 'onThrottleInterval' : 'onActiveAlert'; + } + private async denormalizeActions( alertActions: NormalizedAlertAction[] ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index 65ffc975e721cc..4e273ee3a9e449 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -68,7 +68,7 @@ function getMockData(overwrites: Record = {}): CreateOptions['d consumer: 'bar', schedule: { interval: '10s' }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, params: { bar: true, }, @@ -342,7 +342,7 @@ describe('create()', () => { "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", - "notifyOnlyOnActionGroupChange": false, + "notifyWhen": null, "params": Object { "bar": true, }, @@ -391,7 +391,7 @@ describe('create()', () => { "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", - "notifyOnlyOnActionGroupChange": false, + "notifyWhen": "onActiveAlert", "params": Object { "bar": true, }, @@ -491,6 +491,7 @@ describe('create()', () => { }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + notifyWhen: 'onActiveAlert', actions: [ { group: 'default', @@ -590,6 +591,7 @@ describe('create()', () => { "alertTypeId": "123", "createdAt": 2019-02-12T21:01:22.479Z, "id": "1", + "notifyWhen": "onActiveAlert", "params": Object { "bar": true, }, @@ -629,6 +631,7 @@ describe('create()', () => { }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + notifyWhen: 'onActiveAlert', actions: [ { group: 'default', @@ -665,6 +668,7 @@ describe('create()', () => { "createdAt": 2019-02-12T21:01:22.479Z, "enabled": false, "id": "1", + "notifyWhen": "onActiveAlert", "params": Object { "bar": true, }, @@ -743,6 +747,426 @@ describe('create()', () => { expect(alertsClientParams.createAPIKey).toHaveBeenCalledWith('Alerting: 123/my alert name'); }); + test('should create alert with given notifyWhen value if notifyWhen is not null', async () => { + const data = getMockData({ notifyWhen: 'onActionGroupChange', throttle: '10m' }); + const createdAttributes = { + ...data, + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: '2019-02-12T21:01:22.479Z', + createdBy: 'elastic', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + muteAll: false, + mutedInstanceIds: [], + notifyWhen: 'onActionGroupChange', + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + }; + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: createdAttributes, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + const result = await alertsClient.create({ data }); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + { + actions: [ + { + actionRef: 'action_0', + group: 'default', + actionTypeId: 'test', + params: { foo: true }, + }, + ], + alertTypeId: '123', + consumer: 'bar', + name: 'abc', + params: { bar: true }, + apiKey: null, + apiKeyOwner: null, + createdBy: 'elastic', + createdAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + enabled: true, + meta: { + versionApiKeyLastmodified: 'v7.10.0', + }, + schedule: { interval: '10s' }, + throttle: '10m', + notifyWhen: 'onActionGroupChange', + muteAll: false, + mutedInstanceIds: [], + tags: ['foo'], + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + error: null, + }, + }, + { + id: 'mock-saved-object-id', + references: [ + { + id: '1', + name: 'action_0', + type: 'action', + }, + ], + } + ); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "consumer": "bar", + "createdAt": 2019-02-12T21:01:22.479Z, + "createdBy": "elastic", + "enabled": true, + "id": "1", + "muteAll": false, + "mutedInstanceIds": Array [], + "name": "abc", + "notifyWhen": "onActionGroupChange", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "scheduledTaskId": "task-123", + "tags": Array [ + "foo", + ], + "throttle": "10m", + "updatedAt": 2019-02-12T21:01:22.479Z, + "updatedBy": "elastic", + } + `); + }); + + test('should create alert with notifyWhen = onThrottleInterval if notifyWhen is null and throttle is set', async () => { + const data = getMockData({ throttle: '10m' }); + const createdAttributes = { + ...data, + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: '2019-02-12T21:01:22.479Z', + createdBy: 'elastic', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + muteAll: false, + mutedInstanceIds: [], + notifyWhen: 'onThrottleInterval', + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + }; + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: createdAttributes, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + const result = await alertsClient.create({ data }); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + { + actions: [ + { + actionRef: 'action_0', + group: 'default', + actionTypeId: 'test', + params: { foo: true }, + }, + ], + alertTypeId: '123', + consumer: 'bar', + name: 'abc', + params: { bar: true }, + apiKey: null, + apiKeyOwner: null, + createdBy: 'elastic', + createdAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + enabled: true, + meta: { + versionApiKeyLastmodified: 'v7.10.0', + }, + schedule: { interval: '10s' }, + throttle: '10m', + notifyWhen: 'onThrottleInterval', + muteAll: false, + mutedInstanceIds: [], + tags: ['foo'], + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + error: null, + }, + }, + { + id: 'mock-saved-object-id', + references: [ + { + id: '1', + name: 'action_0', + type: 'action', + }, + ], + } + ); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "consumer": "bar", + "createdAt": 2019-02-12T21:01:22.479Z, + "createdBy": "elastic", + "enabled": true, + "id": "1", + "muteAll": false, + "mutedInstanceIds": Array [], + "name": "abc", + "notifyWhen": "onThrottleInterval", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "scheduledTaskId": "task-123", + "tags": Array [ + "foo", + ], + "throttle": "10m", + "updatedAt": 2019-02-12T21:01:22.479Z, + "updatedBy": "elastic", + } + `); + }); + + test('should create alert with notifyWhen = onActiveAlert if notifyWhen is null and throttle is null', async () => { + const data = getMockData(); + const createdAttributes = { + ...data, + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: '2019-02-12T21:01:22.479Z', + createdBy: 'elastic', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + muteAll: false, + mutedInstanceIds: [], + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + }; + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: createdAttributes, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + const result = await alertsClient.create({ data }); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + { + actions: [ + { + actionRef: 'action_0', + group: 'default', + actionTypeId: 'test', + params: { foo: true }, + }, + ], + alertTypeId: '123', + consumer: 'bar', + name: 'abc', + params: { bar: true }, + apiKey: null, + apiKeyOwner: null, + createdBy: 'elastic', + createdAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + enabled: true, + meta: { + versionApiKeyLastmodified: 'v7.10.0', + }, + schedule: { interval: '10s' }, + throttle: null, + notifyWhen: 'onActiveAlert', + muteAll: false, + mutedInstanceIds: [], + tags: ['foo'], + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + error: null, + }, + }, + { + id: 'mock-saved-object-id', + references: [ + { + id: '1', + name: 'action_0', + type: 'action', + }, + ], + } + ); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "consumer": "bar", + "createdAt": 2019-02-12T21:01:22.479Z, + "createdBy": "elastic", + "enabled": true, + "id": "1", + "muteAll": false, + "mutedInstanceIds": Array [], + "name": "abc", + "notifyWhen": "onActiveAlert", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "scheduledTaskId": "task-123", + "tags": Array [ + "foo", + ], + "throttle": null, + "updatedAt": 2019-02-12T21:01:22.479Z, + "updatedBy": "elastic", + } + `); + }); + test('should validate params', async () => { const data = getMockData(); alertTypeRegistry.get.mockReturnValue({ @@ -1052,7 +1476,7 @@ describe('create()', () => { }, schedule: { interval: '10s' }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', muteAll: false, mutedInstanceIds: [], tags: ['foo'], @@ -1176,7 +1600,7 @@ describe('create()', () => { }, schedule: { interval: '10s' }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', muteAll: false, mutedInstanceIds: [], tags: ['foo'], diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts index 232d48e258256a..ff64150dc2b795 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts @@ -85,6 +85,7 @@ describe('find()', () => { }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + notifyWhen: 'onActiveAlert', actions: [ { group: 'default', @@ -143,6 +144,7 @@ describe('find()', () => { "alertTypeId": "myType", "createdAt": 2019-02-12T21:01:22.479Z, "id": "1", + "notifyWhen": "onActiveAlert", "params": Object { "bar": true, }, @@ -234,6 +236,7 @@ describe('find()', () => { Object { "actions": Array [], "id": "1", + "notifyWhen": undefined, "schedule": undefined, "tags": Array [ "myTag", diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts index 32ac57459795eb..e3e3630d379ea6 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts @@ -72,6 +72,7 @@ describe('get()', () => { }, }, ], + notifyWhen: 'onActiveAlert', }, references: [ { @@ -96,6 +97,7 @@ describe('get()', () => { "alertTypeId": "123", "createdAt": 2019-02-12T21:01:22.479Z, "id": "1", + "notifyWhen": "onActiveAlert", "params": Object { "bar": true, }, diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index d2e3314a5774cd..c2c2efbe024b97 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -80,7 +80,7 @@ const BaseAlertInstanceSummarySavedObject: SavedObject = { apiKey: null, apiKeyOwner: null, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index 9a4a383cd9557a..42cec57b555de2 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -70,7 +70,7 @@ describe('update()', () => { scheduledTaskId: 'task-123', params: {}, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -145,6 +145,7 @@ describe('update()', () => { }, }, ], + notifyWhen: 'onActiveAlert', scheduledTaskId: 'task-123', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -186,7 +187,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', actions: [ { group: 'default', @@ -243,6 +244,7 @@ describe('update()', () => { "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "id": "1", + "notifyWhen": "onActiveAlert", "params": Object { "bar": true, }, @@ -297,7 +299,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", - "notifyOnlyOnActionGroupChange": false, + "notifyWhen": "onActiveAlert", "params": Object { "bar": true, }, @@ -371,6 +373,7 @@ describe('update()', () => { }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + notifyWhen: 'onThrottleInterval', actions: [ { group: 'default', @@ -421,7 +424,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -449,6 +452,7 @@ describe('update()', () => { "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "id": "1", + "notifyWhen": "onThrottleInterval", "params": Object { "bar": true, }, @@ -483,7 +487,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", - "notifyOnlyOnActionGroupChange": false, + "notifyWhen": "onThrottleInterval", "params": Object { "bar": true, }, @@ -545,6 +549,7 @@ describe('update()', () => { params: { bar: true, }, + notifyWhen: 'onThrottleInterval', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), actions: [ @@ -588,7 +593,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onThrottleInterval', actions: [ { group: 'default', @@ -617,6 +622,7 @@ describe('update()', () => { "createdAt": 2019-02-12T21:01:22.479Z, "enabled": false, "id": "1", + "notifyWhen": "onThrottleInterval", "params": Object { "bar": true, }, @@ -651,7 +657,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", - "notifyOnlyOnActionGroupChange": false, + "notifyWhen": "onThrottleInterval", "params": Object { "bar": true, }, @@ -709,7 +715,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -838,7 +844,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -946,7 +952,7 @@ describe('update()', () => { bar: true, }, throttle: '5m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -1008,7 +1014,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -1129,7 +1135,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -1161,7 +1167,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -1198,7 +1204,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -1234,7 +1240,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [ { group: 'default', @@ -1288,7 +1294,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [], }, }); @@ -1312,7 +1318,7 @@ describe('update()', () => { bar: true, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [], }, }) @@ -1356,7 +1362,7 @@ describe('update()', () => { }, throttle: null, actions: [], - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, }, }); @@ -1386,7 +1392,7 @@ describe('update()', () => { }, throttle: null, actions: [], - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, }, }) ).rejects.toThrow(); diff --git a/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts b/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts index f2334f3a526cdb..aaa70a2594a5ec 100644 --- a/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts @@ -105,7 +105,7 @@ async function update(success: boolean) { tags: ['bar'], params: { bar: true }, throttle: '10s', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, actions: [], }, }); diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index 902f00015bebe4..b98c7a85b10e58 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -635,7 +635,7 @@ const BaseAlert: SanitizedAlert = { tags: [], consumer: 'alert-consumer', throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], params: { bar: true }, diff --git a/x-pack/plugins/alerts/server/routes/create.test.ts b/x-pack/plugins/alerts/server/routes/create.test.ts index 7c0d05c5346eef..90c075f129b8c3 100644 --- a/x-pack/plugins/alerts/server/routes/create.test.ts +++ b/x-pack/plugins/alerts/server/routes/create.test.ts @@ -36,7 +36,7 @@ describe('createAlertRoute', () => { bar: true, }, throttle: '30s', - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActionGroupChange', actions: [ { group: 'default', @@ -57,6 +57,7 @@ describe('createAlertRoute', () => { apiKey: '', apiKeyOwner: '', mutedInstanceIds: [], + notifyWhen: 'onActionGroupChange', createdAt, updatedAt, id: '123', @@ -111,7 +112,7 @@ describe('createAlertRoute', () => { "alertTypeId": "1", "consumer": "bar", "name": "abc", - "notifyOnlyOnActionGroupChange": false, + "notifyWhen": "onActionGroupChange", "params": Object { "bar": true, }, diff --git a/x-pack/plugins/alerts/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts index 94d50c058627cc..fd9208b5b33136 100644 --- a/x-pack/plugins/alerts/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -16,7 +16,7 @@ import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { validateDurationSchema } from '../lib'; import { handleDisabledApiKeysError } from './lib/error_handler'; -import { Alert, BASE_ALERT_API_PATH } from '../types'; +import { Alert, BASE_ALERT_API_PATH, validateNotifyWhenType } from '../types'; export const bodySchema = schema.object({ name: schema.string(), @@ -38,7 +38,7 @@ export const bodySchema = schema.object({ }), { defaultValue: [] } ), - notifyOnlyOnActionGroupChange: schema.boolean({ defaultValue: false }), + notifyWhen: schema.nullable(schema.string({ validate: validateNotifyWhenType })), }); export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => { diff --git a/x-pack/plugins/alerts/server/routes/get.test.ts b/x-pack/plugins/alerts/server/routes/get.test.ts index 36f161375ba2fa..51ac64bbef182e 100644 --- a/x-pack/plugins/alerts/server/routes/get.test.ts +++ b/x-pack/plugins/alerts/server/routes/get.test.ts @@ -46,7 +46,7 @@ describe('getAlertRoute', () => { tags: ['foo'], enabled: true, muteAll: false, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActionGroupChange', createdBy: '', updatedBy: '', apiKey: '', diff --git a/x-pack/plugins/alerts/server/routes/update.test.ts b/x-pack/plugins/alerts/server/routes/update.test.ts index 3e7c24af78ea18..1750a3e0744041 100644 --- a/x-pack/plugins/alerts/server/routes/update.test.ts +++ b/x-pack/plugins/alerts/server/routes/update.test.ts @@ -41,7 +41,7 @@ describe('updateAlertRoute', () => { }, }, ], - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActionGroupChange', }; it('updates an alert with proper parameters', async () => { @@ -79,7 +79,7 @@ describe('updateAlertRoute', () => { }, }, ], - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActionGroupChange', }, }, ['ok'] @@ -102,7 +102,7 @@ describe('updateAlertRoute', () => { }, ], "name": "abc", - "notifyOnlyOnActionGroupChange": false, + "notifyWhen": "onActionGroupChange", "params": Object { "otherField": false, }, diff --git a/x-pack/plugins/alerts/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts index 045bff53a3b229..68c7ed8719c071 100644 --- a/x-pack/plugins/alerts/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -16,7 +16,7 @@ import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { validateDurationSchema } from '../lib'; import { handleDisabledApiKeysError } from './lib/error_handler'; -import { BASE_ALERT_API_PATH } from '../../common'; +import { BASE_ALERT_API_PATH, validateNotifyWhenType } from '../../common'; const paramSchema = schema.object({ id: schema.string(), @@ -39,7 +39,7 @@ const bodySchema = schema.object({ }), { defaultValue: [] } ), - notifyOnlyOnActionGroupChange: schema.boolean({ defaultValue: false }), + notifyWhen: schema.nullable(schema.string({ validate: validateNotifyWhenType })), }); export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => { @@ -63,15 +63,7 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => } const alertsClient = context.alerting.getAlertsClient(); const { id } = req.params; - const { - name, - actions, - params, - schedule, - tags, - throttle, - notifyOnlyOnActionGroupChange, - } = req.body; + const { name, actions, params, schedule, tags, throttle, notifyWhen } = req.body; return res.ok({ body: await alertsClient.update({ id, @@ -82,7 +74,7 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => schedule, tags, throttle, - notifyOnlyOnActionGroupChange, + notifyWhen, }, }), }); diff --git a/x-pack/plugins/alerts/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json index 8b7183a58ba594..68e6113575716d 100644 --- a/x-pack/plugins/alerts/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerts/server/saved_objects/mappings.json @@ -74,8 +74,8 @@ "throttle": { "type": "keyword" }, - "notifyOnlyOnActionGroupChange": { - "type": "boolean" + "notifyWhen": { + "type": "string" }, "muteAll": { "type": "boolean" diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts index a2e17add01ff08..abbce7a009b994 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts @@ -277,7 +277,7 @@ describe('7.11.0', () => { attributes: { ...alert.attributes, updatedAt: alert.updated_at, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', }, }); }); @@ -290,12 +290,12 @@ describe('7.11.0', () => { attributes: { ...alert.attributes, updatedAt: alert.attributes.createdAt, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', }, }); }); - test('add notifyOnlyOnActionGroupChange field - default to false', () => { + test('add notifyWhen=onActiveAlert when throttle is null', () => { const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; const alert = getMockData({}); expect(migration711(alert, { log })).toEqual({ @@ -303,7 +303,20 @@ describe('7.11.0', () => { attributes: { ...alert.attributes, updatedAt: alert.attributes.createdAt, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', + }, + }); + }); + + test('add notifyWhen=onActiveAlert when throttle is set', () => { + const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const alert = getMockData({ throttle: '5m' }); + expect(migration711(alert, { log })).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + updatedAt: alert.attributes.createdAt, + notifyWhen: 'onThrottleInterval', }, }); }); diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts index 29fc4d98e96a93..1b9c5dac23b88e 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.ts @@ -37,21 +37,18 @@ export function getMigrations( ) ); - const migrationAlertUpdatedAtAndNotifyOnlyOnActionGroupChange = encryptedSavedObjects.createMigration< + const migrationAlertUpdatedAtAndNotifyWhen = encryptedSavedObjects.createMigration< RawAlert, RawAlert >( - // migrate all documents in 7.11 in order to add the "updatedAt" and "notifyOnlyOnActionGroupChange" fields + // migrate all documents in 7.11 in order to add the "updatedAt" and "notifyWhen" fields (doc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(setAlertUpdatedAtDate, setNotifyOnlyOnActionGroupChange) + pipeMigrations(setAlertUpdatedAtDate, setNotifyWhen) ); return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), - '7.11.0': executeMigrationWithErrorHandling( - migrationAlertUpdatedAtAndNotifyOnlyOnActionGroupChange, - '7.11.0' - ), + '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyWhen, '7.11.0'), }; } @@ -85,14 +82,15 @@ const setAlertUpdatedAtDate = ( }; }; -const setNotifyOnlyOnActionGroupChange = ( +const setNotifyWhen = ( doc: SavedObjectUnsanitizedDoc ): SavedObjectUnsanitizedDoc => { + const notifyWhen = doc.attributes.throttle ? 'onThrottleInterval' : 'onActiveAlert'; return { ...doc, attributes: { ...doc.attributes, - notifyOnlyOnActionGroupChange: false, + notifyWhen, }, }; }; diff --git a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts index 1bf7d72e31a949..09236ec5e0ad1a 100644 --- a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts @@ -27,7 +27,7 @@ const alert: SanitizedAlert = { updatedAt: new Date(), apiKeyOwner: null, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], executionStatus: { diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 88f7c0345ffc52..2b6685db1b6cc5 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -92,7 +92,7 @@ describe('Task Runner', () => { updatedAt: new Date('2019-02-12T21:01:22.479Z'), throttle: null, muteAll: false, - notifyOnlyOnActionGroupChange: false, + notifyWhen: 'onActiveAlert', enabled: true, alertTypeId: alertType.id, apiKey: '', @@ -353,7 +353,7 @@ describe('Task Runner', () => { }); }); - test('actionsPlugin.execute is not called when notifyOnlyOnActionGroupChange is true and alert instance state does not change', async () => { + test('actionsPlugin.execute is not called when notifyWhen=onActionGroupChange and alert instance state does not change', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( @@ -381,7 +381,7 @@ describe('Task Runner', () => { ); alertsClient.get.mockResolvedValue({ ...mockedAlertTypeSavedObject, - notifyOnlyOnActionGroupChange: true, + notifyWhen: 'onActionGroupChange', }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ id: '1', @@ -447,7 +447,7 @@ describe('Task Runner', () => { `); }); - test('actionsPlugin.execute is called when notifyOnlyOnActionGroupChange is true and alert instance state has changed', async () => { + test('actionsPlugin.execute is called when notifyWhen=onActionGroupChange and alert instance state has changed', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( @@ -462,7 +462,7 @@ describe('Task Runner', () => { ); alertsClient.get.mockResolvedValue({ ...mockedAlertTypeSavedObject, - notifyOnlyOnActionGroupChange: true, + notifyWhen: 'onActionGroupChange', }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ id: '1', diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 37735bb2042756..c18b42381113a0 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -168,7 +168,7 @@ export class TaskRunner { ): Promise { const { throttle, - notifyOnlyOnActionGroupChange, + notifyWhen, muteAll, mutedInstanceIds, name, @@ -249,16 +249,17 @@ export class TaskRunner { const mutedInstanceIdsSet = new Set(mutedInstanceIds); - const instancesToExecute = notifyOnlyOnActionGroupChange - ? Object.entries( - instancesWithScheduledActions - ).filter(([_, alertInstance]: [string, AlertInstance]) => - alertInstance.actionGroupHasChanged() - ) - : Object.entries(instancesWithScheduledActions).filter( - ([alertInstanceName, alertInstance]: [string, AlertInstance]) => - !alertInstance.isThrottled(throttle) && !mutedInstanceIdsSet.has(alertInstanceName) - ); + const instancesToExecute = + notifyWhen === 'onActionGroupChange' + ? Object.entries( + instancesWithScheduledActions + ).filter(([_, alertInstance]: [string, AlertInstance]) => + alertInstance.actionGroupHasChanged() + ) + : Object.entries(instancesWithScheduledActions).filter( + ([alertInstanceName, alertInstance]: [string, AlertInstance]) => + !alertInstance.isThrottled(throttle) && !mutedInstanceIdsSet.has(alertInstanceName) + ); await Promise.all( instancesToExecute.map(([id, alertInstance]: [string, AlertInstance]) => diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 2af4495350152a..20488325395ae3 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -28,6 +28,7 @@ import { AlertExecutionStatuses, AlertExecutionStatusErrorReasons, AlertsHealth, + AlertNotifyWhenType, } from '../common'; export type WithoutQueryAndParams = Pick>; @@ -152,7 +153,7 @@ export interface RawAlert extends SavedObjectAttributes { apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; - notifyOnlyOnActionGroupChange: boolean; + notifyWhen: AlertNotifyWhenType | null; muteAll: boolean; mutedInstanceIds: string[]; meta?: AlertMeta; @@ -163,7 +164,7 @@ export type AlertInfoParams = Pick< RawAlert, | 'params' | 'throttle' - | 'notifyOnlyOnActionGroupChange' + | 'notifyWhen' | 'muteAll' | 'mutedInstanceIds' | 'name' From d77863909953092f12afd06d397343fa9e77447b Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 11:22:44 -0500 Subject: [PATCH 32/45] Fixing types check --- x-pack/plugins/monitoring/server/alerts/base_alert.test.ts | 2 +- x-pack/plugins/monitoring/server/alerts/base_alert.ts | 2 +- .../detection_engine/notifications/create_notifications.ts | 2 +- .../detection_engine/notifications/update_notifications.ts | 2 +- .../detection_engine/routes/__mocks__/request_responses.ts | 4 ++-- .../server/lib/detection_engine/rules/create_rules.ts | 2 +- .../server/lib/detection_engine/rules/patch_rules.mock.ts | 2 +- .../server/lib/detection_engine/rules/patch_rules.ts | 2 +- .../server/lib/detection_engine/rules/update_rules.ts | 2 +- .../server/lib/detection_engine/schemas/rule_converters.ts | 1 + .../server/lib/detection_engine/schemas/rule_schemas.ts | 3 ++- 11 files changed, 13 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts index 7652d7084198c4..f1d321af0af797 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.test.ts @@ -64,7 +64,7 @@ describe('BaseAlert', () => { }, tags: [], throttle: '1d', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, }, }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index 2a31880717114b..ba8e2d8adb1a63 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -177,7 +177,7 @@ export class BaseAlert { name: this.label, alertTypeId: this.type, throttle: this.defaultThrottle, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, schedule: { interval: this.defaultInterval }, actions: alertActions, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts index 6b45fbf3cc5f99..5731a51aeabc19 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts @@ -31,6 +31,6 @@ export const createNotifications = async ({ enabled, actions: actions.map(transformRuleToAlertAction), throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts index 28c796a53097f1..d6c8973215117e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts @@ -35,7 +35,7 @@ export const updateNotifications = async ({ ruleAlertId, }, throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, }, }); } else if (interval && !notification) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index f35442a4174c7c..26fb6314afacb7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -401,7 +401,7 @@ export const getResult = (): RuleAlertType => ({ enabled: true, actions: [], throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, createdBy: 'elastic', updatedBy: 'elastic', apiKey: null, @@ -627,7 +627,7 @@ export const getNotificationResult = (): RuleNotificationAlertType => ({ }, ], throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, apiKey: null, apiKeyOwner: 'elastic', createdBy: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index 4cd72bf40a95c5..0519a98df1fae8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -114,7 +114,7 @@ export const createRules = async ({ enabled, actions: actions.map(transformRuleToAlertAction), throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, }, }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index 1134969fc2b513..b2303d48b0517a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -105,7 +105,7 @@ const rule: SanitizedAlert = { enabled: true, actions: [], throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, createdBy: 'elastic', updatedBy: 'elastic', apiKeyOwner: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index ae7acfd2ec4d63..c86526cee9302f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -172,13 +172,13 @@ export const patchRules = async ({ const newRule = { tags: addTags(tags ?? rule.tags, rule.params.ruleId, rule.params.immutable), throttle: null, + notifyWhen: null, name: calculateName({ updatedName: name, originalName: rule.name }), schedule: { interval: calculateInterval(interval, rule.schedule.interval), }, actions: actions?.map(transformRuleToAlertAction) ?? rule.actions, params: removeUndefined(nextParams), - notifyOnlyOnActionGroupChange: false, }; const [validated, errors] = validate(newRule, internalRuleUpdate); if (errors != null || validated === null) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index eaa42237facb4d..c63bd01cd18133 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -74,7 +74,7 @@ export const updateRules = async ({ schedule: { interval: ruleUpdate.interval ?? '5m' }, actions: throttle === 'rule' ? (ruleUpdate.actions ?? []).map(transformRuleToAlertAction) : [], throttle: null, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, }; const update = await alertsClient.update({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index 86d85cd2a066e0..7585f1a1b510b3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -143,6 +143,7 @@ export const convertCreateAPIToInternalSchema = ( enabled: input.enabled ?? true, actions: input.throttle === 'rule' ? (input.actions ?? []).map(transformRuleToAlertAction) : [], throttle: null, + notifyWhen: null, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts index d58c18a079d362..0af9d6ac4377d3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts @@ -182,6 +182,7 @@ export const internalRuleCreate = t.type({ actions: actionsCamel, params: ruleParams, throttle: throttleOrNull, + notifyWhen: t.null, }); export type InternalRuleCreate = t.TypeOf; @@ -194,7 +195,7 @@ export const internalRuleUpdate = t.type({ actions: actionsCamel, params: ruleParams, throttle: throttleOrNull, - notifyOnlyOnActionGroupChange: t.boolean, + notifyWhen: t.null, }); export type InternalRuleUpdate = t.TypeOf; From e31ee5640daa71903c9bf20b2bc9ba7e5b1eb6a1 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 11:26:04 -0500 Subject: [PATCH 33/45] Fixing monitoring jest tests --- x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx index e4ee805c4b48f2..32519d263517d1 100644 --- a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx +++ b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx @@ -156,6 +156,10 @@ describe('alert_form', () => { }); it('should update throttle value', async () => { + wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="onThrottleInterval"]').simulate('click'); + wrapper.update(); const newThrottle = 17; const throttleField = wrapper.find('[data-test-subj="throttleInput"]'); expect(throttleField.exists()).toBeTruthy(); From 01dbeb430a87afd7ac7293eadbc2f8df14149d29 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 11:37:44 -0500 Subject: [PATCH 34/45] Changing last references to old variable names --- .../plugins/alerts/server/saved_objects/mappings.json | 2 +- .../security_and_spaces/tests/alerting/create.ts | 2 +- .../security_and_spaces/tests/alerting/find.ts | 4 ++-- .../security_and_spaces/tests/alerting/get.ts | 2 +- .../security_and_spaces/tests/alerting/update.ts | 10 +++++----- .../spaces_only/tests/alerting/create.ts | 2 +- .../spaces_only/tests/alerting/find.ts | 2 +- .../spaces_only/tests/alerting/get.ts | 2 +- .../spaces_only/tests/alerting/migrations.ts | 4 ++-- .../spaces_only/tests/alerting/update.ts | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/alerts/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json index 68e6113575716d..f0c5c28ecaeafc 100644 --- a/x-pack/plugins/alerts/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerts/server/saved_objects/mappings.json @@ -75,7 +75,7 @@ "type": "keyword" }, "notifyWhen": { - "type": "string" + "type": "keyword" }, "muteAll": { "type": "boolean" diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index 1c302113851f00..91d9c8d5e0cad2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -115,7 +115,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, throttle: '1m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, updatedBy: user.username, apiKeyOwner: user.username, muteAll: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index ff39352147464f..0ee22b1c7cdb65 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -75,7 +75,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { createdAt: match.createdAt, updatedAt: match.updatedAt, throttle: '1m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, @@ -273,7 +273,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, createdAt: match.createdAt, updatedAt: match.updatedAt, executionStatus: match.executionStatus, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 12e9f5b0397f59..509295c4c205ff 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -71,7 +71,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { updatedAt: response.body.updatedAt, createdAt: response.body.createdAt, throttle: '1m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index e0ec6f2abe187a..781f65163aed46 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -126,7 +126,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: {}, }, ], - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -214,7 +214,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, executionStatus: response.body.executionStatus, - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, }); expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); @@ -305,7 +305,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -400,7 +400,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -493,7 +493,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 58dababc621e63..84f3dc8ce66403 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -83,7 +83,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { updatedBy: null, apiKeyOwner: null, throttle: '1m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], createdAt: response.body.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index e9ca06f07bb826..5ae705c136d787 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -52,7 +52,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { scheduledTaskId: match.scheduledTaskId, updatedBy: null, throttle: '1m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], createdAt: match.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 324d10f106948a..6670a9a7a71947 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -46,7 +46,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { updatedBy: null, apiKeyOwner: null, throttle: '1m', - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, muteAll: false, mutedInstanceIds: [], createdAt: response.body.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index 2ca9e348a37e36..56866b36a292b5 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -92,13 +92,13 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(response.body.updatedAt).to.eql('2020-06-17T15:35:39.839Z'); }); - it('7.11.0 migrates alerts to contain `notifyOnlyOnActionGroupChange` field', async () => { + it('7.11.0 migrates alerts to contain `notifyWhen` field', async () => { const response = await supertest.get( `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` ); expect(response.status).to.eql(200); - expect(response.body.notifyOnlyOnActionGroupChange).to.eql(false); + expect(response.body.notifyWhen).to.eql('onActiveAlert'); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index 76d90270893b04..cf2ca3236bbf9a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -54,7 +54,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], - notifyOnlyOnActionGroupChange: false, + notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, From 35f60eb4be4e3e40199b563207438075f4c8300f Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 12:41:08 -0500 Subject: [PATCH 35/45] Moving form inputs back to the top --- .../sections/alert_form/action_frequency.tsx | 155 ++++++----- .../sections/alert_form/alert_form.tsx | 248 +++++++++--------- 2 files changed, 192 insertions(+), 211 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx index 978a4bb01dc6c7..1c1dd716ef3124 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx @@ -9,8 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, - EuiTitle, - EuiFlexGrid, + EuiIconTip, EuiFormRow, EuiFieldNumber, EuiSelect, @@ -166,87 +165,83 @@ export const ActionFrequencyForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const labelForAlertRenotify = ( + <> + {' '} + + + ); + return ( - - - -

- -

-
-
-
- - - - + + + - - - - - {showCustomActionFrequencyOpts && ( - - - - - - { - pipe( - some(e.target.value.trim()), - filter((value) => value !== ''), - map((value) => parseInt(value, 10)), - filter((value) => !isNaN(value)), - map((value) => { - setAlertThrottle(value); - onThrottleChange(value, alertThrottleUnit); - }) - ); - }} - /> - - - { - setAlertThrottleUnit(e.target.value); - onThrottleUnitChange(e.target.value); - }} - /> - - - - - )} - - + {showCustomActionFrequencyOpts && ( + + + + + + { + pipe( + some(e.target.value.trim()), + filter((value) => value !== ''), + map((value) => parseInt(value, 10)), + filter((value) => !isNaN(value)), + map((value) => { + setAlertThrottle(value); + onThrottleChange(value, alertThrottleUnit); + }) + ); + }} + /> + + + { + setAlertThrottleUnit(e.target.value); + onThrottleUnitChange(e.target.value); + }} + /> + + + + + )} +
+
+
); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 460986743de2f4..265ea3d63e1848 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -20,6 +20,7 @@ import { EuiComboBox, EuiFieldNumber, EuiSelect, + EuiIconTip, EuiButtonIcon, EuiHorizontalRule, EuiLoadingSpinner, @@ -533,9 +534,25 @@ export const AlertForm = ({ ); + const labelForAlertChecked = ( + <> + {' '} + + + ); + return ( - + 0 && alert.name !== undefined} - compressed name="name" data-test-subj="alertNameInput" value={alert.name || ''} @@ -568,6 +584,105 @@ export const AlertForm = ({ /> + + + { + const newOptions = [...tagsOptions, { label: searchValue }]; + setAlertProperty( + 'tags', + newOptions.map((newOption) => newOption.label) + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + setAlertProperty( + 'tags', + selectedOptions.map((selectedOption) => selectedOption.label) + ); + }} + onBlur={() => { + if (!alert.tags) { + setAlertProperty('tags', []); + } + }} + /> + + + + + + + 0} + error={errors.interval} + > + + + 0} + value={alertInterval || ''} + name="interval" + data-test-subj="intervalInput" + onChange={(e) => { + const interval = + e.target.value !== '' ? parseInt(e.target.value, 10) : undefined; + setAlertInterval(interval); + setScheduleProperty('interval', `${e.target.value}${alertIntervalUnit}`); + }} + /> + + + { + setAlertIntervalUnit(e.target.value); + setScheduleProperty('interval', `${alertInterval}${e.target.value}`); + }} + /> + + + + + + { + setAlertThrottle(throttle); + setAlertProperty('notifyWhen', notifyWhen); + }} + onThrottleChange={useCallback( + (throttle: number | null, throttleUnit: string) => { + setAlertThrottle(throttle); + setAlertProperty('throttle', throttle ? `${throttle}${throttleUnit}` : null); + }, + [setAlertProperty] + )} + onThrottleUnitChange={(throttleUnit: string) => { + setAlertThrottleUnit(throttleUnit); + if (alertThrottle) { + setAlertProperty('throttle', `${alertThrottle}${throttleUnit}`); + } + }} + /> + {alertTypeModel ? ( @@ -624,135 +739,6 @@ export const AlertForm = ({ ) : ( )} - - - - - -

- -

-
-
-
- - - - - - - - 0} - error={errors.interval} - > - - - 0} - compressed - value={alertInterval || ''} - name="interval" - data-test-subj="intervalInput" - prepend={i18n.translate( - 'xpack.triggersActionsUI.sections.alertForm.alertSchedule.label', - { - defaultMessage: 'Every', - } - )} - onChange={(e) => { - const interval = - e.target.value !== '' ? parseInt(e.target.value, 10) : undefined; - setAlertInterval(interval); - setScheduleProperty('interval', `${e.target.value}${alertIntervalUnit}`); - }} - /> - - - { - setAlertIntervalUnit(e.target.value); - setScheduleProperty('interval', `${alertInterval}${e.target.value}`); - }} - /> - - - - - - { - setAlertThrottle(throttle); - setAlertProperty('notifyWhen', notifyWhen); - }} - onThrottleChange={useCallback( - (throttle: number | null, throttleUnit: string) => { - setAlertThrottle(throttle); - setAlertProperty('throttle', throttle ? `${throttle}${throttleUnit}` : null); - }, - [setAlertProperty] - )} - onThrottleUnitChange={(throttleUnit: string) => { - setAlertThrottleUnit(throttleUnit); - if (alertThrottle) { - setAlertProperty('throttle', `${alertThrottle}${throttleUnit}`); - } - }} - /> - - - - - { - const newOptions = [...tagsOptions, { label: searchValue }]; - setAlertProperty( - 'tags', - newOptions.map((newOption) => newOption.label) - ); - }} - onChange={(selectedOptions: Array<{ label: string }>) => { - setAlertProperty( - 'tags', - selectedOptions.map((selectedOption) => selectedOption.label) - ); - }} - onBlur={() => { - if (!alert.tags) { - setAlertProperty('tags', []); - } - }} - /> - - -
); }; From 819d9598c7b9ca1d8810f436312ffb879d2b8de4 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 14:28:38 -0500 Subject: [PATCH 36/45] Renaming to alert_notify_when --- .../public/alerts/alert_form.test.tsx | 2 +- .../sections/alert_form/alert_form.tsx | 21 ++-- ...cy.test.tsx => alert_notify_when.test.tsx} | 69 ++++++------ ...on_frequency.tsx => alert_notify_when.tsx} | 103 +++++++----------- 4 files changed, 79 insertions(+), 116 deletions(-) rename x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/{action_frequency.test.tsx => alert_notify_when.test.tsx} (66%) rename x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/{action_frequency.tsx => alert_notify_when.tsx} (61%) diff --git a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx index 32519d263517d1..369f03ab8cb115 100644 --- a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx +++ b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx @@ -156,7 +156,7 @@ describe('alert_form', () => { }); it('should update throttle value', async () => { - wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); + wrapper.find('button[data-test-subj="notifyWhenSelect"]').simulate('click'); wrapper.update(); wrapper.find('button[data-test-subj="onThrottleInterval"]').simulate('click'); wrapper.update(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 265ea3d63e1848..2d7e31d6b5a00a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -60,7 +60,7 @@ import './alert_form.scss'; import { useKibana } from '../../../common/lib/kibana'; import { recoveredActionGroupMessage } from '../../constants'; import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params'; -import { ActionFrequencyForm, ActionFrequencyOpts } from './action_frequency'; +import { AlertNotifyWhen } from './alert_notify_when'; const ENTER_KEY = 13; @@ -660,27 +660,24 @@ export const AlertForm = ({
- { - setAlertThrottle(throttle); - setAlertProperty('notifyWhen', notifyWhen); - }} + onNotifyWhenChange={useCallback( + (notifyWhen) => { + setAlertProperty('notifyWhen', notifyWhen); + }, + [setAlertProperty] + )} onThrottleChange={useCallback( (throttle: number | null, throttleUnit: string) => { setAlertThrottle(throttle); + setAlertThrottleUnit(throttleUnit); setAlertProperty('throttle', throttle ? `${throttle}${throttleUnit}` : null); }, [setAlertProperty] )} - onThrottleUnitChange={(throttleUnit: string) => { - setAlertThrottleUnit(throttleUnit); - if (alertThrottle) { - setAlertProperty('throttle', `${alertThrottle}${throttleUnit}`); - } - }} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx similarity index 66% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx index 4c191d8d0eb88d..62e35229c90222 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx @@ -9,18 +9,15 @@ import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { Alert } from '../../../types'; import { ALERTS_FEATURE_ID } from '../../../../../alerts/common'; -import { ActionFrequencyForm } from './action_frequency'; +import { AlertNotifyWhen } from './alert_notify_when'; -describe('action_frequency_form', () => { +describe('alert_notify_when', () => { beforeEach(() => { jest.resetAllMocks(); }); - const alertThrottle = null; - const alertThrottleUnit = 'm'; - const onActionFreqencyChange = jest.fn(); + const onNotifyWhenChange = jest.fn(); const onThrottleChange = jest.fn(); - const onThrottleUnitChange = jest.fn(); describe('action_frequency_form new alert', () => { let wrapper: ReactWrapper; @@ -43,13 +40,12 @@ describe('action_frequency_form', () => { } as unknown) as Alert; wrapper = mountWithIntl( - ); @@ -59,12 +55,18 @@ describe('action_frequency_form', () => { }); } + it(`should determine initial selection from throttle value if 'notifyWhen' is null`, async () => { + await setup({ notifyWhen: null }); + const notifyWhenSelect = wrapper.find('[data-test-subj="notifyWhenSelect"]'); + expect(notifyWhenSelect.exists()).toBeTruthy(); + expect(notifyWhenSelect.first().prop('valueOfSelected')).toEqual('onActiveAlert'); + }); + it(`should correctly select 'onActionGroupChange' option on initial render`, async () => { await setup(); - const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); - expect(actionFrequencySelect.exists()).toBeTruthy(); - expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('onActionGroupChange'); - + const notifyWhenSelect = wrapper.find('[data-test-subj="notifyWhenSelect"]'); + expect(notifyWhenSelect.exists()).toBeTruthy(); + expect(notifyWhenSelect.first().prop('valueOfSelected')).toEqual('onActionGroupChange'); expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); }); @@ -73,10 +75,9 @@ describe('action_frequency_form', () => { await setup({ notifyWhen: 'onActiveAlert', }); - - const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); - expect(actionFrequencySelect.exists()).toBeTruthy(); - expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('onActiveAlert'); + const notifyWhenSelect = wrapper.find('[data-test-subj="notifyWhenSelect"]'); + expect(notifyWhenSelect.exists()).toBeTruthy(); + expect(notifyWhenSelect.first().prop('valueOfSelected')).toEqual('onActiveAlert'); expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); }); @@ -84,16 +85,14 @@ describe('action_frequency_form', () => { it(`should correctly select 'onThrottleInterval' option on initial render and render throttle inputs`, async () => { await setup({ notifyWhen: 'onThrottleInterval', - throttle: '20m', }); - - const actionFrequencySelect = wrapper.find('[data-test-subj="actionFrequencySelect"]'); - expect(actionFrequencySelect.exists()).toBeTruthy(); - expect(actionFrequencySelect.first().prop('valueOfSelected')).toEqual('onThrottleInterval'); + const notifyWhenSelect = wrapper.find('[data-test-subj="notifyWhenSelect"]'); + expect(notifyWhenSelect.exists()).toBeTruthy(); + expect(notifyWhenSelect.first().prop('valueOfSelected')).toEqual('onThrottleInterval'); const throttleInput = wrapper.find('[data-test-subj="throttleInput"]'); expect(throttleInput.exists()).toBeTruthy(); - expect(throttleInput.at(1).prop('value')).toEqual(20); + expect(throttleInput.at(1).prop('value')).toEqual(1); const throttleUnitInput = wrapper.find('[data-test-subj="throttleUnitInput"]'); expect(throttleUnitInput.exists()).toBeTruthy(); @@ -103,25 +102,21 @@ describe('action_frequency_form', () => { it('should update action frequency type correctly', async () => { await setup(); - wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); + wrapper.find('button[data-test-subj="notifyWhenSelect"]').simulate('click'); wrapper.update(); wrapper.find('button[data-test-subj="onActiveAlert"]').simulate('click'); wrapper.update(); - expect(onActionFreqencyChange).toHaveBeenCalledWith({ - notifyWhen: 'onActiveAlert', - throttle: null, - }); + expect(onNotifyWhenChange).toHaveBeenCalledWith('onActiveAlert'); + expect(onThrottleChange).toHaveBeenCalledWith(null, 'm'); - wrapper.find('button[data-test-subj="actionFrequencySelect"]').simulate('click'); + wrapper.find('button[data-test-subj="notifyWhenSelect"]').simulate('click'); wrapper.update(); wrapper.find('button[data-test-subj="onActionGroupChange"]').simulate('click'); wrapper.update(); expect(wrapper.find('[data-test-subj="throttleInput"]').exists()).toBeFalsy(); expect(wrapper.find('[data-test-subj="throttleUnitInput"]').exists()).toBeFalsy(); - expect(onActionFreqencyChange).toHaveBeenCalledWith({ - notifyWhen: 'onActionGroupChange', - throttle: null, - }); + expect(onNotifyWhenChange).toHaveBeenCalledWith('onActionGroupChange'); + expect(onThrottleChange).toHaveBeenCalledWith(null, 'm'); }); it('should renders throttle input when custom throttle is selected and update throttle value', async () => { @@ -141,9 +136,7 @@ describe('action_frequency_form', () => { const throttleUnitField = wrapper.find('[data-test-subj="throttleUnitInput"]'); expect(throttleUnitField.exists()).toBeTruthy(); throttleUnitField.at(1).simulate('change', { target: { value: newThrottleUnit } }); - const throttleUnitFieldAfterUpdate = wrapper.find('[data-test-subj="throttleUnitInput"]'); - expect(throttleUnitFieldAfterUpdate.at(1).prop('value')).toEqual(newThrottleUnit); - expect(onThrottleUnitChange).toHaveBeenCalledWith('h'); + expect(onThrottleChange).toHaveBeenCalledWith(null, 'h'); }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx similarity index 61% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx index 1c1dd716ef3124..157e00f33c7356 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/action_frequency.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx @@ -22,15 +22,11 @@ import { some, filter, map } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; import { InitialAlert } from './alert_reducer'; import { getTimeOptions } from '../../../common/lib/get_time_options'; -import { - getDurationNumberInItsUnit, - getDurationUnitValue, -} from '../../../../../alerts/common/parse_duration'; import { AlertNotifyWhenType } from '../../../types'; -const DEFAULT_ACTION_FREQUENCY_VALUE: AlertNotifyWhenType = 'onActionGroupChange'; +const DEFAULT_NOTIFY_WHEN_VALUE: AlertNotifyWhenType = 'onActionGroupChange'; -const ACTION_FREQUENCY_OPTIONS: Array> = [ +const NOTIFY_WHEN_OPTIONS: Array> = [ { value: 'onActionGroupChange', inputDisplay: 'Run only on status change', @@ -40,14 +36,14 @@ const ACTION_FREQUENCY_OPTIONS: Array>

@@ -63,14 +59,14 @@ const ACTION_FREQUENCY_OPTIONS: Array>

@@ -86,14 +82,14 @@ const ACTION_FREQUENCY_OPTIONS: Array>

@@ -102,66 +98,44 @@ const ACTION_FREQUENCY_OPTIONS: Array> }, ]; -export interface ActionFrequencyOpts { - throttle: number | null; - notifyWhen: AlertNotifyWhenType; -} - -interface ActionFrequencyFormProps { +interface AlertNotifyWhenProps { alert: InitialAlert; throttle: number | null; throttleUnit: string; - onActionFreqencyChange: (opts: ActionFrequencyOpts) => void; + onNotifyWhenChange: (notifyWhen: AlertNotifyWhenType) => void; onThrottleChange: (throttle: number | null, throttleUnit: string) => void; - onThrottleUnitChange: (throttleUnit: string) => void; } -export const ActionFrequencyForm = ({ +export const AlertNotifyWhen = ({ alert, - onActionFreqencyChange, + throttle, + throttleUnit, + onNotifyWhenChange, onThrottleChange, - onThrottleUnitChange, -}: ActionFrequencyFormProps) => { - const [alertThrottle, setAlertThrottle] = useState(null); - const [alertThrottleUnit, setAlertThrottleUnit] = useState('m'); - const [showCustomActionFrequencyOpts, setShowCustomActionFrequencyOpts] = useState( - false - ); - const [actionFrequencyValue, setActionFrequencyValue] = useState( - DEFAULT_ACTION_FREQUENCY_VALUE +}: AlertNotifyWhenProps) => { + const [alertThrottle, setAlertThrottle] = useState(throttle || 1); + const [showCustomThrottleOpts, setShowCustomThrottleOpts] = useState(false); + const [notifyWhenValue, setNotifyWhenValue] = useState( + DEFAULT_NOTIFY_WHEN_VALUE ); useEffect(() => { if (alert.notifyWhen) { - setActionFrequencyValue(alert.notifyWhen); + setNotifyWhenValue(alert.notifyWhen); } else { // If 'notifyWhen' is not set, derive value from existence of throttle value - setActionFrequencyValue(alert.throttle ? 'onThrottleInterval' : 'onActiveAlert'); - } - - if (!alert.throttle) { - setAlertThrottle( - alert.schedule.interval ? getDurationNumberInItsUnit(alert.schedule.interval) : null - ); - setAlertThrottleUnit( - alert.schedule.interval ? getDurationUnitValue(alert.schedule.interval) : 'm' - ); - } else { - setAlertThrottle(alert.throttle ? getDurationNumberInItsUnit(alert.throttle) : null); - setAlertThrottleUnit(alert.throttle ? getDurationUnitValue(alert.throttle) : 'm'); + setNotifyWhenValue(alert.throttle ? 'onThrottleInterval' : 'onActiveAlert'); } }, [alert]); useEffect(() => { - setShowCustomActionFrequencyOpts(actionFrequencyValue === 'onThrottleInterval'); - }, [actionFrequencyValue]); + setShowCustomThrottleOpts(notifyWhenValue === 'onThrottleInterval'); + }, [notifyWhenValue]); - const onActionFrequencyValueChange = useCallback((newValue: AlertNotifyWhenType) => { - onActionFreqencyChange({ - notifyWhen: newValue, - throttle: newValue === 'onThrottleInterval' ? alertThrottle! : null, - }); - setActionFrequencyValue(newValue); + const onNotifyWhenValueChange = useCallback((newValue: AlertNotifyWhenType) => { + onThrottleChange(newValue === 'onThrottleInterval' ? alertThrottle : null, throttleUnit); + onNotifyWhenChange(newValue); + setNotifyWhenValue(newValue); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -187,12 +161,12 @@ export const ActionFrequencyForm = ({ - {showCustomActionFrequencyOpts && ( + {showCustomThrottleOpts && ( @@ -201,11 +175,11 @@ export const ActionFrequencyForm = ({ !isNaN(value)), map((value) => { setAlertThrottle(value); - onThrottleChange(value, alertThrottleUnit); + onThrottleChange(value, throttleUnit); }) ); }} @@ -227,11 +201,10 @@ export const ActionFrequencyForm = ({ { - setAlertThrottleUnit(e.target.value); - onThrottleUnitChange(e.target.value); + onThrottleChange(throttle, e.target.value); }} /> From f835ef1c8e4a5fbe3575338b6efdb7229338cd58 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 16:00:22 -0500 Subject: [PATCH 37/45] Updating functional tests --- .../common/lib/get_test_alert_data.ts | 1 + .../security_and_spaces/tests/alerting/create.ts | 2 +- .../security_and_spaces/tests/alerting/find.ts | 4 ++-- .../security_and_spaces/tests/alerting/get.ts | 2 +- .../security_and_spaces/tests/alerting/update.ts | 14 +++++++++----- .../spaces_only/tests/alerting/create.ts | 2 +- .../spaces_only/tests/alerting/find.ts | 2 +- .../spaces_only/tests/alerting/get.ts | 2 +- .../spaces_only/tests/alerting/update.ts | 2 +- x-pack/test/functional/services/uptime/alerts.ts | 2 ++ .../triggers_actions_ui/alert_create_flyout.ts | 5 ++--- 11 files changed, 22 insertions(+), 16 deletions(-) diff --git a/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts b/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts index 2e7a4e325094c2..e4db829cc283a0 100644 --- a/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts +++ b/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts @@ -13,6 +13,7 @@ export function getTestAlertData(overwrites = {}) { consumer: 'alertsFixture', schedule: { interval: '1m' }, throttle: '1m', + notifyWhen: 'onThrottleInterval', actions: [], params: {}, ...overwrites, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index 91d9c8d5e0cad2..720a0f20648f24 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -115,7 +115,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, throttle: '1m', - notifyWhen: null, + notifyWhen: 'onThrottleInterval', updatedBy: user.username, apiKeyOwner: user.username, muteAll: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 0ee22b1c7cdb65..55b148f0c50195 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -75,7 +75,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { createdAt: match.createdAt, updatedAt: match.updatedAt, throttle: '1m', - notifyWhen: null, + notifyWhen: 'onThrottleInterval', updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, @@ -273,7 +273,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], - notifyWhen: null, + notifyWhen: 'onThrottleInterval', createdAt: match.createdAt, updatedAt: match.updatedAt, executionStatus: match.executionStatus, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 509295c4c205ff..87d7b2327dd613 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -71,7 +71,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { updatedAt: response.body.updatedAt, createdAt: response.body.createdAt, throttle: '1m', - notifyWhen: null, + notifyWhen: 'onThrottleInterval', updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 781f65163aed46..b3ad00bd1ce8b0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -73,6 +73,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }, ], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) @@ -126,7 +127,6 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: {}, }, ], - notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -172,6 +172,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) @@ -214,7 +215,6 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, executionStatus: response.body.executionStatus, - notifyWhen: null, }); expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); @@ -256,6 +256,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) @@ -305,7 +306,6 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -351,6 +351,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) @@ -400,7 +401,6 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -455,6 +455,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) @@ -493,7 +494,6 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: user.username, muteAll: false, mutedInstanceIds: [], - notifyWhen: null, scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, @@ -534,6 +534,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) @@ -803,6 +804,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '1m' }, actions: [], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) @@ -868,6 +870,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '1m' }, actions: [], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) @@ -943,6 +946,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '1s' }, actions: [], throttle: '1m', + notifyWhen: 'onThrottleInterval', }; const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 84f3dc8ce66403..8bf0a2a0f034fd 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -83,7 +83,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { updatedBy: null, apiKeyOwner: null, throttle: '1m', - notifyWhen: null, + notifyWhen: 'onThrottleInterval', muteAll: false, mutedInstanceIds: [], createdAt: response.body.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index 5ae705c136d787..ffe25cfe684ac9 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -52,7 +52,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { scheduledTaskId: match.scheduledTaskId, updatedBy: null, throttle: '1m', - notifyWhen: null, + notifyWhen: 'onThrottleInterval', muteAll: false, mutedInstanceIds: [], createdAt: match.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 6670a9a7a71947..8323e26585329f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -46,7 +46,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { updatedBy: null, apiKeyOwner: null, throttle: '1m', - notifyWhen: null, + notifyWhen: 'onThrottleInterval', muteAll: false, mutedInstanceIds: [], createdAt: response.body.createdAt, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index cf2ca3236bbf9a..f7e6a402e40615 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -54,7 +54,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], - notifyWhen: null, + notifyWhen: 'onThrottleInterval', scheduledTaskId: createdAlert.scheduledTaskId, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, diff --git a/x-pack/test/functional/services/uptime/alerts.ts b/x-pack/test/functional/services/uptime/alerts.ts index fa0c035b9183e5..67b80e2ddf327e 100644 --- a/x-pack/test/functional/services/uptime/alerts.ts +++ b/x-pack/test/functional/services/uptime/alerts.ts @@ -38,6 +38,8 @@ export function UptimeAlertsProvider({ getService }: FtrProviderContext) { return testSubjects.setValue('intervalInput', value); }, async setAlertThrottleInterval(value: string) { + await testSubjects.click('notifyWhenSelect'); + await testSubjects.click('onThrottleInterval'); return testSubjects.setValue('throttleInput', value); }, async setAlertExpressionValue( diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 8de94eb6efcbe3..188c1d259ef92b 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -71,10 +71,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const alertName = generateUniqueKey(); await defineAlert(alertName); + await testSubjects.click('notifyWhenSelect'); + await testSubjects.click('onThrottleInterval'); await testSubjects.setValue('throttleInput', '10'); - await testSubjects.click('notifyOnlyOnActionGroupChange'); - const throttleInput = await find.byCssSelector('[data-test-subj="throttleInput"]'); - expect(await throttleInput.getAttribute('value')).to.be.empty(); await testSubjects.click('.slack-ActionTypeSelectOption'); await testSubjects.click('addNewActionConnectorButton-.slack'); From 2b5c53ce1c74ba9fc6096ad25c696beb1119bfe7 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 16:35:32 -0500 Subject: [PATCH 38/45] Adding new functional test for notifyWhen onActionGroupChange --- .../spaces_only/tests/alerting/index.ts | 1 + .../spaces_only/tests/alerting/notify_when.ts | 181 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index d8a0f279222c7a..2b24a75fab8441 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -34,6 +34,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./alerts_space1')); loadTestFile(require.resolve('./alerts_default_space')); loadTestFile(require.resolve('./builtin_alert_types')); + loadTestFile(require.resolve('./notify_when')); // note that this test will destroy existing spaces loadTestFile(require.resolve('./migrations')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts new file mode 100644 index 00000000000000..a34ba1a728fb25 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts @@ -0,0 +1,181 @@ +/* + * 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 expect from '@kbn/expect'; + +import { Spaces } from '../../scenarios'; +import { getUrlPrefix, ObjectRemover, getTestAlertData, getEventLog } from '../../../common/lib'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { IValidatedEvent } from '../../../../../plugins/event_log/server'; + +// eslint-disable-next-line import/no-default-export +export default function createNotifyWhenTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const retry = getService('retry'); + + describe('notifyWhen', () => { + const objectRemover = new ObjectRemover(supertest); + + afterEach(async () => await objectRemover.removeAll()); + + it(`alert with notifyWhen=onActiveAlert should always execute actions `, async () => { + const { body: defaultAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My Default Action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const { body: recoveredAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My Recovered Action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const pattern = { + instance: [true, true, true, false, true, true], + }; + const expectedActionGroupBasedOnPattern = pattern.instance.map((active: boolean) => + active ? 'default' : 'recovered' + ); + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + alertTypeId: 'test.patternFiring', + params: { pattern }, + schedule: { interval: '1s' }, + throttle: null, + notifyWhen: 'onActiveAlert', + actions: [ + { + id: defaultAction.id, + group: 'default', + params: {}, + }, + { + id: recoveredAction.id, + group: 'recovered', + params: {}, + }, + ], + }) + ) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + provider: 'alerting', + actions: new Map([ + ['execute-action', { gte: 6 }], + ['new-instance', { equal: 2 }], + ]), + }); + }); + + const executeActionEvents = getEventsByAction(events, 'execute-action'); + const executeActionEventsActionGroup = executeActionEvents.map( + (event) => event?.kibana?.alerting?.action_group_id + ); + expect(executeActionEventsActionGroup).to.eql(expectedActionGroupBasedOnPattern); + }); + + it(`alert with notifyWhen=onActionGroupChange should only execute actions when action group changes`, async () => { + const { body: defaultAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My Default Action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const { body: recoveredAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My Recovered Action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const pattern = { + instance: [true, true, false, false, true, false], + }; + const expectedActionGroupBasedOnPattern = ['default', 'recovered', 'default', 'recovered']; + + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + alertTypeId: 'test.patternFiring', + params: { pattern }, + schedule: { interval: '1s' }, + throttle: null, + notifyWhen: 'onActionGroupChange', + actions: [ + { + id: defaultAction.id, + group: 'default', + params: {}, + }, + { + id: recoveredAction.id, + group: 'recovered', + params: {}, + }, + ], + }) + ) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + provider: 'alerting', + actions: new Map([ + ['execute-action', { gte: 4 }], + ['new-instance', { equal: 2 }], + ]), + }); + }); + + const executeActionEvents = getEventsByAction(events, 'execute-action'); + const executeActionEventsActionGroup = executeActionEvents.map( + (event) => event?.kibana?.alerting?.action_group_id + ); + expect(executeActionEventsActionGroup).to.eql(expectedActionGroupBasedOnPattern); + }); + }); +} + +function getEventsByAction(events: IValidatedEvent[], action: string) { + return events.filter((event) => event?.event?.action === action); +} From 1adf049d982c4e537fc7e7d995b8b72eb5d0242c Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 8 Dec 2020 21:34:55 -0500 Subject: [PATCH 39/45] Updating wording --- x-pack/plugins/translations/translations/ja-JP.json | 4 ++++ x-pack/plugins/translations/translations/zh-CN.json | 4 ++++ .../application/sections/alert_form/alert_notify_when.tsx | 6 +++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f55d736bfd1f95..1f36a5a71537b7 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19622,6 +19622,8 @@ "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "コネクターを作成する", "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新規追加", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名前", + "xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "確認間隔", + "xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "条件を評価する頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "{actionTypeName}コネクターがありません", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedAlertTypes": "アラートを{operation}するには、適切な権限が付与されている必要があります。", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedAlertTypesTitle": "アラートタイプを{operation}する権限がありません。", @@ -19633,6 +19635,8 @@ "xpack.triggersActionsUI.sections.alertForm.loadingConnectorsDescription": "コネクターを読み込んでいます...", "xpack.triggersActionsUI.sections.alertForm.newAlertActionTypeEditTitle": "{actionConnectorName}", "xpack.triggersActionsUI.sections.alertForm.preconfiguredTitleMessage": "構成済み", + "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知間隔", + "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "アラートがアクティブな間にアクションを繰り返す頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "アクションタイプを選択してください", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "デフォルトアクショングループの定義がないのでアクションを追加できません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ab8953bc6b2071..3151b863cc4a1b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19641,6 +19641,8 @@ "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "创建连接器", "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新添", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名称", + "xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "检查频率", + "xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "定义评估条件的频率。", "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "无 {actionTypeName} 连接器", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedAlertTypes": "为了{operation}告警,您需要获得相应的权限。", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedAlertTypesTitle": "您尚无权{operation}任何告警类型", @@ -19652,6 +19654,8 @@ "xpack.triggersActionsUI.sections.alertForm.loadingConnectorsDescription": "正在加载连接器……", "xpack.triggersActionsUI.sections.alertForm.newAlertActionTypeEditTitle": "{actionConnectorName}", "xpack.triggersActionsUI.sections.alertForm.preconfiguredTitleMessage": "预配置", + "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知频率", + "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "定义告警处于活动状态时重复操作的频率。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "选择操作类型", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "无法添加操作,因为未定义默认操作组", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx index 157e00f33c7356..da872484dda4a2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx @@ -42,7 +42,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [

@@ -65,7 +65,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [

@@ -88,7 +88,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [

From b2f6610c825f1d9ad52fe297c56a1b6773382db8 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 10 Dec 2020 11:36:55 -0500 Subject: [PATCH 40/45] Incorporating action subgroups into logic --- .../alert_instance/alert_instance.test.ts | 77 ++++++++++++++++--- .../server/alert_instance/alert_instance.ts | 28 ++++--- .../server/task_runner/task_runner.test.ts | 61 ++++++++++++++- .../alerts/server/task_runner/task_runner.ts | 2 +- 4 files changed, 147 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts index 334b4a7d16224b..b428f6c1a91348 100644 --- a/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts +++ b/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts @@ -72,10 +72,10 @@ describe('isThrottled', () => { }); }); -describe('actionGroupHasChanged()', () => { +describe('scheduledActionGroupOrSubgroupHasChanged()', () => { test('should be false if no last scheduled and nothing scheduled', () => { const alertInstance = new AlertInstance(); - expect(alertInstance.actionGroupHasChanged()).toEqual(false); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); }); test('should be false if group does not change', () => { @@ -88,16 +88,24 @@ describe('actionGroupHasChanged()', () => { }, }); alertInstance.scheduleActions('default'); - expect(alertInstance.actionGroupHasChanged()).toEqual(false); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); }); - test('should be true if no last scheduled and group is scheduled', () => { - const alertInstance = new AlertInstance(); - alertInstance.scheduleActions('default'); - expect(alertInstance.actionGroupHasChanged()).toEqual(true); + test('should be false if group and subgroup does not change', () => { + const alertInstance = new AlertInstance({ + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + subgroup: 'subgroup', + }, + }, + }); + alertInstance.scheduleActionsWithSubGroup('default', 'subgroup'); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); }); - test('should be true if last scheduled group and next action is undefined', () => { + test('should be false if group does not change and subgroup goes from undefined to defined', () => { const alertInstance = new AlertInstance({ meta: { lastScheduledActions: { @@ -106,7 +114,28 @@ describe('actionGroupHasChanged()', () => { }, }, }); - expect(alertInstance.actionGroupHasChanged()).toEqual(true); + alertInstance.scheduleActionsWithSubGroup('default', 'subgroup'); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); + }); + + test('should be false if group does not change and subgroup goes from defined to undefined', () => { + const alertInstance = new AlertInstance({ + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + subgroup: 'subgroup', + }, + }, + }); + alertInstance.scheduleActions('default'); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false); + }); + + test('should be true if no last scheduled and has scheduled action', () => { + const alertInstance = new AlertInstance(); + alertInstance.scheduleActions('default'); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); }); test('should be true if group does change', () => { @@ -119,7 +148,35 @@ describe('actionGroupHasChanged()', () => { }, }); alertInstance.scheduleActions('penguin'); - expect(alertInstance.actionGroupHasChanged()).toEqual(true); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); + }); + + test('should be true if group does change and subgroup does change', () => { + const alertInstance = new AlertInstance({ + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + subgroup: 'subgroup', + }, + }, + }); + alertInstance.scheduleActionsWithSubGroup('penguin', 'fish'); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); + }); + + test('should be true if group does not change and subgroup does change', () => { + const alertInstance = new AlertInstance({ + meta: { + lastScheduledActions: { + date: new Date(), + group: 'default', + subgroup: 'subgroup', + }, + }, + }); + alertInstance.scheduleActionsWithSubGroup('default', 'fish'); + expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true); }); }); diff --git a/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts index 0d4db8750d3f7b..8841f3115d547d 100644 --- a/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts +++ b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts @@ -71,18 +71,28 @@ export class AlertInstance< } scheduledActionGroupOrSubgroupHasChanged(): boolean { - return ( - !!this.meta.lastScheduledActions && - !!this.scheduledExecutionOptions && - (!this.scheduledActionGroupIsUnchanged( - this.meta.lastScheduledActions, - this.scheduledExecutionOptions - ) || + if (!this.meta.lastScheduledActions && this.scheduledExecutionOptions) { + // it is considered a change when there are no previous scheduled actions + // and new scheduled actions + return true; + } + + if (this.meta.lastScheduledActions && this.scheduledExecutionOptions) { + // compare previous and new scheduled actions if both exist + return ( + !this.scheduledActionGroupIsUnchanged( + this.meta.lastScheduledActions, + this.scheduledExecutionOptions + ) || !this.scheduledActionSubgroupIsUnchanged( this.meta.lastScheduledActions, this.scheduledExecutionOptions - )) - ); + ) + ); + } + + // no previous and no new scheduled actions + return false; } private scheduledActionGroupIsUnchanged( diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 2234ff377fc26b..d3d0a54417ee3b 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -638,7 +638,66 @@ describe('Task Runner', () => { ); const taskRunner = new TaskRunner( alertType, - mockedTaskInstance, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { + meta: { lastScheduledActions: { group: 'newGroup', date: new Date().toISOString() } }, + state: { bar: false }, + }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue({ + ...mockedAlertTypeSavedObject, + notifyWhen: 'onActionGroupChange', + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); + }); + + test('actionsPlugin.execute is called when notifyWhen=onActionGroupChange and alert instance state subgroup has changed', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + executorServices + .alertInstanceFactory('1') + .scheduleActionsWithSubGroup('default', 'subgroup1'); + } + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { + meta: { + lastScheduledActions: { + group: 'default', + subgroup: 'newSubgroup', + date: new Date().toISOString(), + }, + }, + state: { bar: false }, + }, + }, + }, + }, taskRunnerFactoryInitializerParams ); alertsClient.get.mockResolvedValue({ diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 2db5fe08df59a3..2073528f2c75ef 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -270,7 +270,7 @@ export class TaskRunner { notifyWhen === 'onActionGroupChange' ? Object.entries(instancesWithScheduledActions).filter( ([alertInstanceName, alertInstance]: [string, AlertInstance]) => { - const shouldExecuteAction = alertInstance.actionGroupHasChanged(); + const shouldExecuteAction = alertInstance.scheduledActionGroupOrSubgroupHasChanged(); if (!shouldExecuteAction) { this.logger.debug( `skipping scheduling of actions for '${alertInstanceName}' in alert ${alertLabel}: instance is active but action group has not changed` From 6765bab1340e6d70502eab03eb3965b58c7d1e5c Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 10 Dec 2020 11:52:18 -0500 Subject: [PATCH 41/45] PR fixes --- .../alerts/common/alert_notify_when_type.ts | 8 +++++-- .../server/alerts_client/alerts_client.ts | 19 ++++++--------- .../lib/get_alert_notify_when_type.test.ts | 23 +++++++++++++++++++ .../server/lib/get_alert_notify_when_type.ts | 16 +++++++++++++ x-pack/plugins/alerts/server/lib/index.ts | 1 + 5 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/alerts/server/lib/get_alert_notify_when_type.test.ts create mode 100644 x-pack/plugins/alerts/server/lib/get_alert_notify_when_type.ts diff --git a/x-pack/plugins/alerts/common/alert_notify_when_type.ts b/x-pack/plugins/alerts/common/alert_notify_when_type.ts index a0a00e258a29c7..4ae4be0ac20ab9 100644 --- a/x-pack/plugins/alerts/common/alert_notify_when_type.ts +++ b/x-pack/plugins/alerts/common/alert_notify_when_type.ts @@ -4,11 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -const AlertNotifyWhenTypeValues = ['onActionGroupChange', 'onActiveAlert', 'onThrottleInterval']; +const AlertNotifyWhenTypeValues = [ + 'onActionGroupChange', + 'onActiveAlert', + 'onThrottleInterval', +] as const; export type AlertNotifyWhenType = typeof AlertNotifyWhenTypeValues[number]; export function validateNotifyWhenType(notifyWhen: string) { - if (AlertNotifyWhenTypeValues.includes(notifyWhen)) { + if (AlertNotifyWhenTypeValues.includes(notifyWhen as AlertNotifyWhenType)) { return; } return `string is not a valid AlertNotifyWhenType: ${notifyWhen}`; diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index 50c17af1194247..b1696696b30444 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -31,7 +31,11 @@ import { AlertExecutionStatusValues, AlertNotifyWhenType, } from '../types'; -import { validateAlertTypeParams, alertExecutionStatusFromRaw } from '../lib'; +import { + validateAlertTypeParams, + alertExecutionStatusFromRaw, + getAlertNotifyWhenType, +} from '../lib'; import { GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, @@ -253,7 +257,7 @@ export class AlertsClient { const createTime = Date.now(); const { references, actions } = await this.denormalizeActions(data.actions); - const notifyWhen = this.validateAlertNotifyWhenType(data.notifyWhen, data.throttle); + const notifyWhen = getAlertNotifyWhenType(data.notifyWhen, data.throttle); const rawAlert: RawAlert = { ...data, @@ -699,7 +703,7 @@ export class AlertsClient { ? await this.createAPIKey(this.generateAPIKeyName(alertType.id, data.name)) : null; const apiKeyAttributes = this.apiKeyAsAlertAttributes(createdAPIKey, username); - const notifyWhen = this.validateAlertNotifyWhenType(data.notifyWhen, data.throttle); + const notifyWhen = getAlertNotifyWhenType(data.notifyWhen, data.throttle); let updatedObject: SavedObject; const createAttributes = this.updateMeta({ @@ -1382,15 +1386,6 @@ export class AlertsClient { } } - private validateAlertNotifyWhenType( - notifyWhen: AlertNotifyWhenType | null, - throttle: string | null - ): AlertNotifyWhenType { - // We allow notifyWhen to be null for backwards compatibility. If it is null, determine its - // value based on whether the throttle is set to a value or null - return notifyWhen ? notifyWhen! : throttle ? 'onThrottleInterval' : 'onActiveAlert'; - } - private async denormalizeActions( alertActions: NormalizedAlertAction[] ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { diff --git a/x-pack/plugins/alerts/server/lib/get_alert_notify_when_type.test.ts b/x-pack/plugins/alerts/server/lib/get_alert_notify_when_type.test.ts new file mode 100644 index 00000000000000..51eb1277a61c9c --- /dev/null +++ b/x-pack/plugins/alerts/server/lib/get_alert_notify_when_type.test.ts @@ -0,0 +1,23 @@ +/* + * 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 { getAlertNotifyWhenType } from './get_alert_notify_when_type'; + +test(`should return 'notifyWhen' value if value is set and throttle is null`, () => { + expect(getAlertNotifyWhenType('onActionGroupChange', null)).toEqual('onActionGroupChange'); +}); + +test(`should return 'notifyWhen' value if value is set and throttle is defined`, () => { + expect(getAlertNotifyWhenType('onActionGroupChange', '10m')).toEqual('onActionGroupChange'); +}); + +test(`should return 'onThrottleInterval' value if 'notifyWhen' is null and throttle is defined`, () => { + expect(getAlertNotifyWhenType(null, '10m')).toEqual('onThrottleInterval'); +}); + +test(`should return 'onActiveAlert' value if 'notifyWhen' is null and throttle is null`, () => { + expect(getAlertNotifyWhenType(null, null)).toEqual('onActiveAlert'); +}); diff --git a/x-pack/plugins/alerts/server/lib/get_alert_notify_when_type.ts b/x-pack/plugins/alerts/server/lib/get_alert_notify_when_type.ts new file mode 100644 index 00000000000000..c871ba0c6e60aa --- /dev/null +++ b/x-pack/plugins/alerts/server/lib/get_alert_notify_when_type.ts @@ -0,0 +1,16 @@ +/* + * 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 { AlertNotifyWhenType } from '../types'; + +export function getAlertNotifyWhenType( + notifyWhen: AlertNotifyWhenType | null, + throttle: string | null +): AlertNotifyWhenType { + // We allow notifyWhen to be null for backwards compatibility. If it is null, determine its + // value based on whether the throttle is set to a value or null + return notifyWhen ? notifyWhen! : throttle ? 'onThrottleInterval' : 'onActiveAlert'; +} diff --git a/x-pack/plugins/alerts/server/lib/index.ts b/x-pack/plugins/alerts/server/lib/index.ts index 32047ae5cbfa83..d4662c02c03174 100644 --- a/x-pack/plugins/alerts/server/lib/index.ts +++ b/x-pack/plugins/alerts/server/lib/index.ts @@ -7,6 +7,7 @@ export { parseDuration, validateDurationSchema } from '../../common/parse_duration'; export { LicenseState } from './license_state'; export { validateAlertTypeParams } from './validate_alert_type_params'; +export { getAlertNotifyWhenType } from './get_alert_notify_when_type'; export { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason'; export { executionStatusFromState, From 86deabd0c173d5c49ca9edd4e89ebdeb03295b05 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 10 Dec 2020 12:43:35 -0500 Subject: [PATCH 42/45] Updating functional test --- .../spaces_only/tests/alerting/notify_when.ts | 95 ++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts index a34ba1a728fb25..234fbb580210b4 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts @@ -85,7 +85,7 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext id: createdAlert.id, provider: 'alerting', actions: new Map([ - ['execute-action', { gte: 6 }], + ['execute-action', { gte: 6 }], // one more action (for recovery) will be executed after the last pattern fires ['new-instance', { equal: 2 }], ]), }); @@ -98,7 +98,7 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext expect(executeActionEventsActionGroup).to.eql(expectedActionGroupBasedOnPattern); }); - it(`alert with notifyWhen=onActionGroupChange should only execute actions when action group changes`, async () => { + it(`alert with notifyWhen=onActionGroupChange should execute actions when action group changes`, async () => { const { body: defaultAction } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) .set('kbn-xsrf', 'foo') @@ -173,6 +173,97 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext ); expect(executeActionEventsActionGroup).to.eql(expectedActionGroupBasedOnPattern); }); + + it(`alert with notifyWhen=onActionGroupChange should only execute actions when action subgroup changes`, async () => { + const { body: defaultAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My Default Action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const { body: recoveredAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My Recovered Action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const pattern = { + instance: [ + 'subgroup1', + 'subgroup1', + false, + false, + 'subgroup1', + 'subgroup2', + 'subgroup2', + false, + ], + }; + const expectedActionGroupBasedOnPattern = [ + 'default', + 'recovered', + 'default', + 'default', + 'recovered', + ]; + + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + alertTypeId: 'test.patternFiring', + params: { pattern }, + schedule: { interval: '1s' }, + throttle: null, + notifyWhen: 'onActionGroupChange', + actions: [ + { + id: defaultAction.id, + group: 'default', + params: {}, + }, + { + id: recoveredAction.id, + group: 'recovered', + params: {}, + }, + ], + }) + ) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + provider: 'alerting', + actions: new Map([ + ['execute-action', { gte: 5 }], + ['new-instance', { equal: 2 }], + ]), + }); + }); + + const executeActionEvents = getEventsByAction(events, 'execute-action'); + const executeActionEventsActionGroup = executeActionEvents.map( + (event) => event?.kibana?.alerting?.action_group_id + ); + expect(executeActionEventsActionGroup).to.eql(expectedActionGroupBasedOnPattern); + }); }); } From 85ed88591b863d4160e6f57e1a554d75e988f17a Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 10 Dec 2020 12:57:02 -0500 Subject: [PATCH 43/45] Fixing types check --- x-pack/plugins/alerts/server/routes/create.ts | 5 +++-- x-pack/plugins/alerts/server/routes/update.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/alerts/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts index fd9208b5b33136..f54aec8fe0cf05 100644 --- a/x-pack/plugins/alerts/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -16,7 +16,7 @@ import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { validateDurationSchema } from '../lib'; import { handleDisabledApiKeysError } from './lib/error_handler'; -import { Alert, BASE_ALERT_API_PATH, validateNotifyWhenType } from '../types'; +import { Alert, AlertNotifyWhenType, BASE_ALERT_API_PATH, validateNotifyWhenType } from '../types'; export const bodySchema = schema.object({ name: schema.string(), @@ -62,7 +62,8 @@ export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => } const alertsClient = context.alerting.getAlertsClient(); const alert = req.body; - const alertRes: Alert = await alertsClient.create({ data: alert }); + const notifyWhen = alert?.notifyWhen ? (alert.notifyWhen as AlertNotifyWhenType) : null; + const alertRes: Alert = await alertsClient.create({ data: { ...alert, notifyWhen } }); return res.ok({ body: alertRes, }); diff --git a/x-pack/plugins/alerts/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts index 68c7ed8719c071..96b3156525f79f 100644 --- a/x-pack/plugins/alerts/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -16,7 +16,7 @@ import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { validateDurationSchema } from '../lib'; import { handleDisabledApiKeysError } from './lib/error_handler'; -import { BASE_ALERT_API_PATH, validateNotifyWhenType } from '../../common'; +import { AlertNotifyWhenType, BASE_ALERT_API_PATH, validateNotifyWhenType } from '../../common'; const paramSchema = schema.object({ id: schema.string(), @@ -74,7 +74,7 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => schedule, tags, throttle, - notifyWhen, + notifyWhen: notifyWhen as AlertNotifyWhenType, }, }), }); From dd3b29a04853d1a16c44df7248e69d381988257b Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 10 Dec 2020 13:02:10 -0500 Subject: [PATCH 44/45] Changing default throttle interval to hour --- .../public/application/sections/alert_form/alert_form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 9b10af81a5dc6b..3259e405e3f70d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -167,7 +167,7 @@ export const AlertForm = ({ alert.throttle ? getDurationNumberInItsUnit(alert.throttle) : null ); const [alertThrottleUnit, setAlertThrottleUnit] = useState( - alert.throttle ? getDurationUnitValue(alert.throttle) : 'm' + alert.throttle ? getDurationUnitValue(alert.throttle) : 'h' ); const [defaultActionGroupId, setDefaultActionGroupId] = useState(undefined); const [alertTypesIndex, setAlertTypesIndex] = useState(null); From 46e8025b5caa5a65b096416ff72169c489f43a17 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 10 Dec 2020 13:26:05 -0500 Subject: [PATCH 45/45] Fixing types check --- x-pack/plugins/alerts/server/routes/update.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/alerts/server/routes/update.test.ts b/x-pack/plugins/alerts/server/routes/update.test.ts index 1750a3e0744041..89619bd8537071 100644 --- a/x-pack/plugins/alerts/server/routes/update.test.ts +++ b/x-pack/plugins/alerts/server/routes/update.test.ts @@ -10,6 +10,7 @@ import { mockLicenseState } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib/license_api_access'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { alertsClientMock } from '../alerts_client.mock'; +import { AlertNotifyWhenType } from '../../common'; const alertsClient = alertsClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -41,7 +42,7 @@ describe('updateAlertRoute', () => { }, }, ], - notifyWhen: 'onActionGroupChange', + notifyWhen: 'onActionGroupChange' as AlertNotifyWhenType, }; it('updates an alert with proper parameters', async () => {