From 36f728b4d121a660a15ba3248587c42493db151d Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Fri, 28 Feb 2020 17:26:24 -0800 Subject: [PATCH 01/13] Implemented edit alert functionality --- .../threshold/constants/aggregation_types.ts | 17 -- .../threshold/constants/comparators.ts | 13 -- .../threshold/constants/index.ts | 8 - .../threshold/expression.tsx | 4 +- .../builtin_alert_types/threshold/index.ts | 2 +- .../builtin_alert_types/threshold/types.ts | 2 +- .../application/context/alerts_context.tsx | 2 - .../alert_add.test.tsx | 0 .../{alert_add => alert_form}/alert_add.tsx | 12 +- .../sections/alert_form/alert_edit.tsx | 192 ++++++++++++++++++ .../alert_form.test.tsx | 0 .../{alert_add => alert_form}/alert_form.tsx | 12 -- .../alert_reducer.test.ts | 0 .../alert_reducer.ts | 0 .../{alert_add => alert_form}/index.ts | 1 + .../alerts_list/components/alerts_list.tsx | 46 ++++- .../common/constants/aggregation_types.ts | 4 +- .../triggers_actions_ui/public/index.ts | 2 +- 18 files changed, 251 insertions(+), 66 deletions(-) delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/aggregation_types.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/comparators.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/index.ts rename x-pack/plugins/triggers_actions_ui/public/application/sections/{alert_add => alert_form}/alert_add.test.tsx (100%) rename x-pack/plugins/triggers_actions_ui/public/application/sections/{alert_add => alert_form}/alert_add.tsx (96%) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx rename x-pack/plugins/triggers_actions_ui/public/application/sections/{alert_add => alert_form}/alert_form.test.tsx (100%) rename x-pack/plugins/triggers_actions_ui/public/application/sections/{alert_add => alert_form}/alert_form.tsx (98%) rename x-pack/plugins/triggers_actions_ui/public/application/sections/{alert_add => alert_form}/alert_reducer.test.ts (100%) rename x-pack/plugins/triggers_actions_ui/public/application/sections/{alert_add => alert_form}/alert_reducer.ts (100%) rename x-pack/plugins/triggers_actions_ui/public/application/sections/{alert_add => alert_form}/index.ts (87%) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/aggregation_types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/aggregation_types.ts deleted file mode 100644 index 68c2818502b2c..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/aggregation_types.ts +++ /dev/null @@ -1,17 +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. - */ - -export const AGGREGATION_TYPES: { [key: string]: string } = { - COUNT: 'count', - - AVERAGE: 'avg', - - SUM: 'sum', - - MIN: 'min', - - MAX: 'max', -}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/comparators.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/comparators.ts deleted file mode 100644 index 21b350c0f8ce4..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/comparators.ts +++ /dev/null @@ -1,13 +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. - */ - -export const COMPARATORS: { [key: string]: string } = { - GREATER_THAN: '>', - GREATER_THAN_OR_EQUALS: '>=', - BETWEEN: 'between', - LESS_THAN: '<', - LESS_THAN_OR_EQUALS: '<=', -}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/index.ts deleted file mode 100644 index f88ee5ee23f90..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/constants/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export { COMPARATORS } from './comparators'; -export { AGGREGATION_TYPES } from './aggregation_types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index f7d2b8f60157f..5a8a182ccbb28 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -245,7 +245,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { + selectedOptions={([index] || []).map((anIndex: string) => { return { label: anIndex, value: anIndex, @@ -349,7 +349,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { setIndexPopoverOpen(true); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts index e388149311075..a94d2319d6e4d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts @@ -11,7 +11,7 @@ import { builtInGroupByTypes, builtInAggregationTypes } from '../../../../common export function getAlertType(): AlertTypeModel { return { - id: 'threshold', + id: '.index-threshold', name: 'Index Threshold', iconClass: 'alert', alertParamsExpression: IndexThresholdAlertTypeExpression, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts index 356b0fbbc0845..649e19bbe849d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts @@ -25,7 +25,7 @@ export interface GroupByType { } export interface IndexThresholdAlertParams { - index: string[]; + index: string; timeField?: string; aggType: string; aggField?: string; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx index 04090d2c6428d..1ffebed2eb002 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx @@ -12,8 +12,6 @@ import { TypeRegistry } from '../type_registry'; import { AlertTypeModel, ActionTypeModel } from '../../types'; export interface AlertsContextValue { - addFlyoutVisible: boolean; - setAddFlyoutVisibility: React.Dispatch>; reloadAlerts?: () => Promise; http: HttpSetup; alertTypeRegistry: TypeRegistry; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.test.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index 20ba9f5a49715..d4c08d4ecc79d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -27,11 +27,19 @@ import { createAlert } from '../../lib/alert_api'; interface AlertAddProps { consumer: string; + addFlyoutVisible: boolean; + setAddFlyoutVisibility: React.Dispatch>; alertTypeId?: string; canChangeTrigger?: boolean; } -export const AlertAdd = ({ consumer, canChangeTrigger, alertTypeId }: AlertAddProps) => { +export const AlertAdd = ({ + consumer, + addFlyoutVisible, + setAddFlyoutVisibility, + canChangeTrigger, + alertTypeId, +}: AlertAddProps) => { const initialAlert = ({ params: {}, consumer, @@ -51,8 +59,6 @@ export const AlertAdd = ({ consumer, canChangeTrigger, alertTypeId }: AlertAddPr }; const { - addFlyoutVisible, - setAddFlyoutVisibility, reloadAlerts, http, toastNotifications, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx new file mode 100644 index 0000000000000..9566341c61cc4 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -0,0 +1,192 @@ +/* + * 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, { useCallback, useReducer, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiTitle, + EuiFlyoutHeader, + EuiFlyout, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, + EuiFlyoutBody, + EuiPortal, + EuiBetaBadge, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useAlertsContext } from '../../context/alerts_context'; +import { Alert, AlertAction, IErrorObject } from '../../../types'; +import { AlertForm, validateBaseProperties } from './alert_form'; +import { alertReducer } from './alert_reducer'; +import { createAlert } from '../../lib/alert_api'; + +interface AlertEditProps { + initialAlert: Alert; + editFlyoutVisible: boolean; + setEditFlyoutVisibility: React.Dispatch>; +} + +export const AlertEdit = ({ + initialAlert, + editFlyoutVisible, + setEditFlyoutVisibility, +}: AlertEditProps) => { + const [{ alert }, dispatch] = useReducer(alertReducer, { alert: initialAlert }); + const [isSaving, setIsSaving] = useState(false); + + const { + reloadAlerts, + http, + toastNotifications, + alertTypeRegistry, + actionTypeRegistry, + } = useAlertsContext(); + + const closeFlyout = useCallback(() => { + setEditFlyoutVisibility(false); + setServerError(null); + }, [setEditFlyoutVisibility]); + + const [serverError, setServerError] = useState<{ + body: { message: string; error: string }; + } | null>(null); + + if (!editFlyoutVisible) { + return null; + } + + const alertType = alertTypeRegistry.get(alert.alertTypeId); + if (!alertType) { + return null; + } + const errors = { + ...(alertType ? alertType.validate(alert.params).errors : []), + ...validateBaseProperties(alert).errors, + } as IErrorObject; + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + + const actionsErrors = alert.actions.reduce( + (acc: Record, alertAction: AlertAction) => { + const actionType = actionTypeRegistry.get(alertAction.actionTypeId); + if (!actionType) { + return { ...acc }; + } + const actionValidationErrors = actionType.validateParams(alertAction.params); + return { ...acc, [alertAction.id]: actionValidationErrors }; + }, + {} + ) as Record; + + const hasActionErrors = !!Object.entries(actionsErrors) + .map(([, actionErrors]) => actionErrors) + .find((actionErrors: { errors: IErrorObject }) => { + return !!Object.keys(actionErrors.errors).find( + errorKey => actionErrors.errors[errorKey].length >= 1 + ); + }); + + async function onSaveAlert(): Promise { + try { + const newAlert = await createAlert({ http, alert }); + if (toastNotifications) { + toastNotifications.addSuccess( + i18n.translate('xpack.triggersActionsUI.sections.alertForm.saveSuccessNotificationText', { + defaultMessage: "Saved '{alertName}'", + values: { + alertName: newAlert.name, + }, + }) + ); + } + return newAlert; + } catch (errorRes) { + setServerError(errorRes); + return undefined; + } + } + + return ( + + + + +

+ +   + +

+
+
+ + + + + + + + {i18n.translate('xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + + { + setIsSaving(true); + const savedAlert = await onSaveAlert(); + setIsSaving(false); + if (savedAlert) { + closeFlyout(); + if (reloadAlerts) { + reloadAlerts(); + } + } + }} + > + + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.test.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx similarity index 98% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 18dc88f54e907..d615e0172092a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -155,18 +155,6 @@ export const AlertForm = ({ (async () => { try { const alertTypes = await loadAlertTypes({ http }); - // temp hack of API result - alertTypes.push({ - id: 'threshold', - actionGroups: [ - { id: 'alert', name: 'Alert' }, - { id: 'warning', name: 'Warning' }, - { id: 'ifUnacknowledged', name: 'If unacknowledged' }, - ], - name: 'threshold', - actionVariables: ['ctx.metadata.name', 'ctx.metadata.test'], - defaultActionGroupId: 'alert', - }); const index: AlertTypeIndex = {}; for (const alertTypeItem of alertTypes) { index[alertTypeItem.id] = alertTypeItem; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_reducer.test.ts rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_reducer.ts rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/index.ts similarity index 87% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/index.ts rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/index.ts index f88a8bb1c49d0..83ed9671238b1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/index.ts @@ -5,3 +5,4 @@ */ export { AlertAdd } from './alert_add'; +export { AlertEdit } from './alert_edit'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 49e25dfbbf957..f594cb2cb0f30 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -24,7 +24,7 @@ import { useHistory } from 'react-router-dom'; import { AlertsContextProvider } from '../../../context/alerts_context'; import { useAppDependencies } from '../../../app_context'; import { ActionType, Alert, AlertTableItem, AlertTypeIndex, Pagination } from '../../../../types'; -import { AlertAdd } from '../../alert_add'; +import { AlertAdd, AlertEdit } from '../../alert_form'; import { BulkOperationPopover } from '../../common/components/bulk_operation_popover'; import { AlertQuickEditButtonsWithApi as AlertQuickEditButtons } from '../../common/components/alert_quick_edit_buttons'; import { CollapsedItemActionsWithApi as CollapsedItemActions } from './collapsed_item_actions'; @@ -84,6 +84,8 @@ export const AlertsList: React.FunctionComponent = () => { data: [], totalItemCount: 0, }); + const [editedAlertItem, setEditedAlertItem] = useState(undefined); + const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); useEffect(() => { loadAlertsData(); @@ -158,6 +160,11 @@ export const AlertsList: React.FunctionComponent = () => { } } + async function editItem(alertTableItem: AlertTableItem) { + setEditedAlertItem(alertTableItem); + setEditFlyoutVisibility(true); + } + const alertsTableColumns = [ { field: 'name', @@ -210,6 +217,27 @@ export const AlertsList: React.FunctionComponent = () => { truncateText: false, 'data-test-subj': 'alertsTableCell-interval', }, + { + field: '', + name: '', + width: '50px', + actions: canSave + ? [ + { + render: (item: AlertTableItem) => { + return ( + editItem(item)}> + + + ); + }, + }, + ] + : [], + }, { name: '', width: '40px', @@ -396,8 +424,6 @@ export const AlertsList: React.FunctionComponent = () => { {(alertTypesState.isLoading || alertsState.isLoading) && } { dataFieldsFormats: dataPlugin.fieldFormats, }} > - + + {editedAlertItem ? ( + + ) : null} ); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts b/x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts index fb0fde1b9d9a5..56c97724f9323 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts @@ -8,7 +8,7 @@ import { AggregationType } from '../types'; export enum AGGREGATION_TYPES { COUNT = 'count', - AVERAGE = 'avg', + AVERAGE = 'average', SUM = 'sum', MIN = 'min', MAX = 'max', @@ -21,7 +21,7 @@ export const builtInAggregationTypes: { [key: string]: AggregationType } = { value: AGGREGATION_TYPES.COUNT, validNormalizedTypes: [], }, - avg: { + average: { text: 'average()', fieldRequired: true, validNormalizedTypes: ['number'], diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index f13ed5983d0d1..0be0a919112f8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext } from 'src/core/public'; import { Plugin } from './plugin'; export { AlertsContextProvider } from './application/context/alerts_context'; -export { AlertAdd } from './application/sections/alert_add'; +export { AlertAdd } from './application/sections/alert_form'; export function plugin(ctx: PluginInitializerContext) { return new Plugin(ctx); From 4ce956be3220f49c07fcfe78d469c99460195083 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Fri, 28 Feb 2020 19:49:05 -0800 Subject: [PATCH 02/13] Added unit tests --- .../sections/alert_form/alert_add.test.tsx | 8 +- .../sections/alert_form/alert_edit.test.tsx | 162 ++++++++++++++++++ .../sections/alert_form/alert_form.test.tsx | 4 - .../common/expression_items/when.test.tsx | 6 +- .../apps/triggers_actions_ui/alerts.ts | 4 +- 5 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 05adccf982b7f..5b373c0e75306 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -90,8 +90,6 @@ describe('alert_add', () => { wrapper = mountWithIntl( {}, reloadAlerts: () => { return new Promise(() => {}); }, @@ -102,7 +100,11 @@ describe('alert_add', () => { uiSettings: deps.uiSettings, }} > - + {}} + /> ); } 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 new file mode 100644 index 0000000000000..000981ea25978 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -0,0 +1,162 @@ +/* + * 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 * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { act } from 'react-dom/test-utils'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { AlertAdd } from './alert_add'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ValidationResult } from '../../../types'; +import { AppDeps } from '../../app'; +import { AlertsContextProvider } from '../../context/alerts_context'; +import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; +import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { ReactWrapper } from 'enzyme'; +import { AlertEdit } from './alert_edit'; +const actionTypeRegistry = actionTypeRegistryMock.create(); +const alertTypeRegistry = alertTypeRegistryMock.create(); + +describe('alert_edit', () => { + let deps: AppDeps | null; + let wrapper: ReactWrapper; + + beforeAll(async () => { + const mockes = coreMock.createSetup(); + const [ + { + chrome, + docLinks, + application: { capabilities }, + }, + ] = await mockes.getStartServices(); + deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + dataPlugin: dataPluginMock.createStartContract(), + charts: chartPluginMock.createStartContract(), + capabilities: { + ...capabilities, + alerting: { + delete: true, + save: true, + show: true, + }, + }, + setBreadcrumbs: jest.fn(), + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: alertTypeRegistry as any, + }; + const alertType = { + id: 'my-alert-type', + iconClass: 'test', + name: 'test-alert', + validate: (): ValidationResult => { + return { errors: {} }; + }, + alertParamsExpression: () => , + }; + + const actionTypeModel = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + + const alert = { + id: 'ab5661e0-197e-45ee-b477-302d89193b5e', + params: { + aggType: 'average', + threshold: [1000, 5000], + index: 'kibana_sample_data_flights', + timeField: 'timestamp', + aggField: 'DistanceMiles', + window: '1s', + comparator: 'between', + }, + consumer: 'alerting', + alertTypeId: 'my-alert-type', + enabled: false, + schedule: { interval: '1m' }, + actions: [ + { + actionTypeId: 'my-action-type', + group: 'threshold met', + params: { message: 'Alert [{{ctx.metadata.name}}] has exceeded the threshold' }, + message: 'Alert [{{ctx.metadata.name}}] has exceeded the threshold', + id: '917f5d41-fbc4-4056-a8ad-ac592f7dcee2', + }, + ], + tags: [], + name: 'Serhii', + throttle: null, + apiKeyOwner: null, + createdBy: 'elastic', + updatedBy: 'elastic', + createdAt: new Date(), + muteAll: false, + mutedInstanceIds: [], + updatedAt: new Date(), + }; + actionTypeRegistry.get.mockReturnValueOnce(actionTypeModel); + actionTypeRegistry.has.mockReturnValue(true); + alertTypeRegistry.list.mockReturnValue([alertType]); + alertTypeRegistry.get.mockReturnValue(alertType); + alertTypeRegistry.has.mockReturnValue(true); + actionTypeRegistry.list.mockReturnValue([actionTypeModel]); + actionTypeRegistry.has.mockReturnValue(true); + + await act(async () => { + if (deps) { + wrapper = mountWithIntl( + { + return new Promise(() => {}); + }, + http: deps.http, + actionTypeRegistry: deps.actionTypeRegistry, + alertTypeRegistry: deps.alertTypeRegistry, + toastNotifications: deps.toastNotifications, + uiSettings: deps.uiSettings, + }} + > + {}} + initialAlert={alert} + /> + + ); + } + }); + await waitForRender(wrapper); + }); + + it('renders alert add flyout', () => { + expect(wrapper.find('[data-test-subj="addAlertFlyoutTitle"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="saveAlertButton"]').exists()).toBeTruthy(); + }); +}); + +async function waitForRender(wrapper: ReactWrapper) { + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); +} 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 aa71621f1a914..0c22ce0fca80c 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 @@ -104,8 +104,6 @@ describe('alert_form', () => { wrapper = mountWithIntl( {}, reloadAlerts: () => { return new Promise(() => {}); }, @@ -180,8 +178,6 @@ describe('alert_form', () => { wrapper = mountWithIntl( {}, reloadAlerts: () => { return new Promise(() => {}); }, diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/when.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/when.test.tsx index 02b6bf24977c9..e7221b08c128f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/when.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/when.test.tsx @@ -27,7 +27,7 @@ describe('when expression', () => { }, Object { "text": "average()", - "value": "avg", + "value": "average", }, Object { "text": "sum()", @@ -96,10 +96,10 @@ describe('when expression', () => { it('renders when popover title', () => { const onChangeSelectedAggType = jest.fn(); const wrapper = shallow( - + ); wrapper.simulate('click'); - expect(wrapper.find('[value="avg"]').length > 0).toBeTruthy(); + expect(wrapper.find('[value="average"]').length > 0).toBeTruthy(); expect(wrapper.contains(when)).toBeTruthy(); }); }); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 84081309c18d9..4d93305b19dd4 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -53,7 +53,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInput.clearValue(); await nameInput.type(alertName); - await testSubjects.click('threshold-SelectOption'); + await testSubjects.click('.index-threshold-SelectOption'); await testSubjects.click('.slack-ActionTypeSelectOption'); await testSubjects.click('createActionConnectorButton'); @@ -79,7 +79,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const variableMenuButton = await testSubjects.find('variableMenuButton-0'); await variableMenuButton.click(); - await testSubjects.click('selectIndexExpression'); + await testSubjects.click('selectIf.index-threshold-SelectOptionndexExpression'); await find.clickByCssSelector('[data-test-subj="cancelSaveAlertButton"]'); From 82f29e6389285e4fc7eaeb396fd810fb947f79d4 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 2 Mar 2020 08:45:58 -0800 Subject: [PATCH 03/13] Added functional test for edit alert --- .../sections/alert_form/alert_edit.test.tsx | 1 - .../sections/alert_form/alert_edit.tsx | 14 +++--- .../alerts_list/components/alerts_list.tsx | 6 ++- .../apps/triggers_actions_ui/alerts.ts | 49 +++++++++++++++++-- 4 files changed, 57 insertions(+), 13 deletions(-) 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 000981ea25978..a9aca5374d259 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 @@ -7,7 +7,6 @@ import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { act } from 'react-dom/test-utils'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { AlertAdd } from './alert_add'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult } from '../../../types'; import { AppDeps } from '../../app'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 9566341c61cc4..949fb70f30c8d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -96,7 +96,7 @@ export const AlertEdit = ({ if (toastNotifications) { toastNotifications.addSuccess( i18n.translate('xpack.triggersActionsUI.sections.alertForm.saveSuccessNotificationText', { - defaultMessage: "Saved '{alertName}'", + defaultMessage: "Updated '{alertName}'", values: { alertName: newAlert.name, }, @@ -114,7 +114,7 @@ export const AlertEdit = ({ - {i18n.translate('xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel', { + {i18n.translate('xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel', { defaultMessage: 'Cancel', })} @@ -161,7 +161,7 @@ export const AlertEdit = ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index f594cb2cb0f30..cc25a0003fdd0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -226,7 +226,11 @@ export const AlertsList: React.FunctionComponent = () => { { render: (item: AlertTableItem) => { return ( - editItem(item)}> + editItem(item)} + > { await loggingMessageInput.clearValue(); await loggingMessageInput.type('test message'); - await testSubjects.click('slackAddVariableButton'); - const variableMenuButton = await testSubjects.find('variableMenuButton-0'); - await variableMenuButton.click(); + // TODO: uncomment variables test when server API will be ready + // await testSubjects.click('slackAddVariableButton'); + // const variableMenuButton = await testSubjects.find('variableMenuButton-0'); + // await variableMenuButton.click(); await testSubjects.click('selectIf.index-threshold-SelectOptionndexExpression'); - await find.clickByCssSelector('[data-test-subj="cancelSaveAlertButton"]'); + await find.clickByCssSelector('[data-test-subj="saveAlertButton"]'); // TODO: implement saving to the server, when threshold API will be ready }); @@ -102,6 +103,46 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ]); }); + it('should edit an alert', async () => { + const createdAlert = await createAlert(); + await pageObjects.common.navigateToApp('triggersActions'); + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResults).to.eql([ + { + name: createdAlert.name, + tagsText: 'foo, bar', + alertType: 'Test: Noop', + interval: '1m', + }, + ]); + + await find.clickByCssSelector(`[data-test-subj="alertEditLink-${createdAlert.id}"] button`); + + const updatedAlertName = 'Changed Alert Name'; + const nameInputToUpdate = await testSubjects.find('alertNameInput'); + await nameInputToUpdate.click(); + await nameInputToUpdate.clearValue(); + await nameInputToUpdate.type(updatedAlertName); + + await find.clickByCssSelector('[data-test-subj="saveEditedAlertButton"]:not(disabled)'); + + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Updated '${updatedAlertName}'`); + + await pageObjects.triggersActionsUI.searchAlerts(updatedAlertName); + + const searchResultsAfterEdit = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResultsAfterEdit).to.eql([ + { + name: updatedAlertName, + tagsText: 'foo, bar', + alertType: 'Test: Noop', + interval: '1m', + }, + ]); + }); + it('should search for tags', async () => { const createdAlert = await createAlert(); await pageObjects.common.navigateToApp('triggersActions'); From 971db47de1cb0cdcab20cb28ad5d256d90478252 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 2 Mar 2020 11:05:19 -0800 Subject: [PATCH 04/13] Fixed failed tests --- .../components/builtin_alert_types/threshold/expression.tsx | 4 ++-- .../components/builtin_alert_types/threshold/types.ts | 2 +- .../public/application/sections/alert_form/alert_add.tsx | 2 +- .../application/sections/alert_form/alert_edit.test.tsx | 4 ++-- .../public/application/sections/alert_form/alert_edit.tsx | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index 5a8a182ccbb28..f7d2b8f60157f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -245,7 +245,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { + selectedOptions={(index || []).map((anIndex: string) => { return { label: anIndex, value: anIndex, @@ -349,7 +349,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { setIndexPopoverOpen(true); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts index 649e19bbe849d..356b0fbbc0845 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts @@ -25,7 +25,7 @@ export interface GroupByType { } export interface IndexThresholdAlertParams { - index: string; + index: string[]; timeField?: string; aggType: string; aggField?: string; 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 d4c08d4ecc79d..a26b01205a21c 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 @@ -112,7 +112,7 @@ export const AlertAdd = ({ const newAlert = await createAlert({ http, alert }); if (toastNotifications) { toastNotifications.addSuccess( - i18n.translate('xpack.triggersActionsUI.sections.alertForm.saveSuccessNotificationText', { + i18n.translate('xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText', { defaultMessage: "Saved '{alertName}'", values: { alertName: newAlert.name, 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 a9aca5374d259..2517da136bb38 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 @@ -149,8 +149,8 @@ describe('alert_edit', () => { }); it('renders alert add flyout', () => { - expect(wrapper.find('[data-test-subj="addAlertFlyoutTitle"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="saveAlertButton"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="editAlertFlyoutTitle"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="saveEditedAlertButton"]').exists()).toBeTruthy(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 949fb70f30c8d..25fc9345204ef 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -95,7 +95,7 @@ export const AlertEdit = ({ const newAlert = await createAlert({ http, alert }); if (toastNotifications) { toastNotifications.addSuccess( - i18n.translate('xpack.triggersActionsUI.sections.alertForm.saveSuccessNotificationText', { + i18n.translate('xpack.triggersActionsUI.sections.alertEdit.saveSuccessNotificationText', { defaultMessage: "Updated '{alertName}'", values: { alertName: newAlert.name, @@ -120,7 +120,7 @@ export const AlertEdit = ({ ownFocus > - +

- + {i18n.translate('xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel', { defaultMessage: 'Cancel', })} From 490f7720412ee0dfd179e9abd154e1e53dddbd9c Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 2 Mar 2020 15:20:55 -0800 Subject: [PATCH 05/13] Fixed edit api --- .../threshold/expression.tsx | 18 ++++---- .../public/application/lib/alert_api.test.ts | 2 +- .../public/application/lib/alert_api.ts | 9 +++- .../sections/alert_form/alert_edit.test.tsx | 42 +++++++++---------- .../sections/alert_form/alert_edit.tsx | 4 +- .../apps/triggers_actions_ui/alerts.ts | 15 ++++++- 6 files changed, 53 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index f7d2b8f60157f..654c69939da6d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -46,8 +46,6 @@ const DEFAULT_VALUES = { THRESHOLD_COMPARATOR: COMPARATORS.GREATER_THAN, TIME_WINDOW_SIZE: 5, TIME_WINDOW_UNIT: 'm', - TRIGGER_INTERVAL_SIZE: 1, - TRIGGER_INTERVAL_UNIT: 'm', THRESHOLD: [1000, 5000], GROUP_BY: 'all', }; @@ -136,14 +134,14 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { setAlertProperty('params', { - aggType: DEFAULT_VALUES.AGGREGATION_TYPE, - termSize: DEFAULT_VALUES.TERM_SIZE, - thresholdComparator: DEFAULT_VALUES.THRESHOLD_COMPARATOR, - timeWindowSize: DEFAULT_VALUES.TIME_WINDOW_SIZE, - timeWindowUnit: DEFAULT_VALUES.TIME_WINDOW_UNIT, - triggerIntervalUnit: DEFAULT_VALUES.TRIGGER_INTERVAL_UNIT, - groupBy: DEFAULT_VALUES.GROUP_BY, - threshold: DEFAULT_VALUES.THRESHOLD, + ...alertParams, + aggType: aggType ?? DEFAULT_VALUES.AGGREGATION_TYPE, + termSize: termSize ?? DEFAULT_VALUES.TERM_SIZE, + thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, + timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, + timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, + groupBy: groupBy ?? DEFAULT_VALUES.GROUP_BY, + threshold: threshold ?? DEFAULT_VALUES.THRESHOLD, }); }; 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 1e53e7d983848..ebbfb0fc4b76f 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 @@ -443,7 +443,7 @@ describe('updateAlert', () => { Array [ "/api/alert/123", Object { - "body": "{\\"throttle\\":\\"1m\\",\\"consumer\\":\\"alerting\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[]}", }, ] `); 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 e0ecae976146c..eff115d7d1d07 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 @@ -126,7 +126,14 @@ export async function updateAlert({ id: string; }): Promise { return await http.put(`${BASE_ALERT_API_PATH}/${id}`, { - body: JSON.stringify(alert), + body: JSON.stringify({ + throttle: alert.throttle, + name: alert.name, + tags: alert.tags, + schedule: alert.schedule, + params: alert.params, + actions: alert.actions, + }), }); } 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 2517da136bb38..e196a3bb62624 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 @@ -122,28 +122,26 @@ describe('alert_edit', () => { actionTypeRegistry.has.mockReturnValue(true); await act(async () => { - if (deps) { - wrapper = mountWithIntl( - { - return new Promise(() => {}); - }, - http: deps.http, - actionTypeRegistry: deps.actionTypeRegistry, - alertTypeRegistry: deps.alertTypeRegistry, - toastNotifications: deps.toastNotifications, - uiSettings: deps.uiSettings, - }} - > - {}} - initialAlert={alert} - /> - - ); - } + wrapper = mountWithIntl( + { + return new Promise(() => {}); + }, + http: deps!.http, + actionTypeRegistry: deps!.actionTypeRegistry, + alertTypeRegistry: deps!.alertTypeRegistry, + toastNotifications: deps!.toastNotifications, + uiSettings: deps!.uiSettings, + }} + > + {}} + initialAlert={alert} + /> + + ); }); await waitForRender(wrapper); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 25fc9345204ef..34bfdcfe4e813 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -23,7 +23,7 @@ import { useAlertsContext } from '../../context/alerts_context'; import { Alert, AlertAction, IErrorObject } from '../../../types'; import { AlertForm, validateBaseProperties } from './alert_form'; import { alertReducer } from './alert_reducer'; -import { createAlert } from '../../lib/alert_api'; +import { updateAlert } from '../../lib/alert_api'; interface AlertEditProps { initialAlert: Alert; @@ -92,7 +92,7 @@ export const AlertEdit = ({ async function onSaveAlert(): Promise { try { - const newAlert = await createAlert({ http, alert }); + const newAlert = await updateAlert({ http, alert, id: alert.id }); if (toastNotifications) { toastNotifications.addSuccess( i18n.translate('xpack.triggersActionsUI.sections.alertEdit.saveSuccessNotificationText', { diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 74f5b423a9421..2d9c17504f4bf 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -84,7 +84,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="saveAlertButton"]'); - // TODO: implement saving to the server, when threshold API will be ready + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Saved '${alertName}'`); + + await pageObjects.triggersActionsUI.searchAlerts(alertName); + + const searchResultsAfterEdit = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResultsAfterEdit).to.eql([ + { + name: alertName, + tagsText: 'foo, bar', + alertType: 'Test: Noop', + interval: '1m', + }, + ]); }); it('should search for alert', async () => { From 47cbad57d1dab036de4d843489e23945d7536631 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 3 Mar 2020 09:40:56 -0800 Subject: [PATCH 06/13] Fixed due to comments --- .../public/application/lib/alert_api.ts | 12 +-- .../sections/alert_form/alert_edit.test.tsx | 78 ++++++------------- .../sections/alert_form/alert_edit.tsx | 1 - .../alerts_list/components/alerts_list.tsx | 2 +- .../common/constants/aggregation_types.ts | 4 +- .../common/expression_items/when.test.tsx | 6 +- .../apps/triggers_actions_ui/alerts.ts | 55 +++++++++---- 7 files changed, 75 insertions(+), 83 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 eff115d7d1d07..ff6b4ba17c6d9 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 @@ -8,6 +8,7 @@ import { HttpSetup } from 'kibana/public'; import * as t from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; +import { pick } from 'lodash'; import { alertStateSchema } from '../../../../alerting/common'; import { BASE_ALERT_API_PATH } from '../constants'; import { Alert, AlertType, AlertWithoutId, AlertTaskState } from '../../types'; @@ -126,14 +127,9 @@ export async function updateAlert({ id: string; }): Promise { return await http.put(`${BASE_ALERT_API_PATH}/${id}`, { - body: JSON.stringify({ - throttle: alert.throttle, - name: alert.name, - tags: alert.tags, - schedule: alert.schedule, - params: alert.params, - actions: alert.actions, - }), + body: JSON.stringify( + pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions']) + ), }); } 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 e196a3bb62624..7f6a8759bbe07 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 @@ -4,52 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ import * as React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { act } from 'react-dom/test-utils'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult } from '../../../types'; -import { AppDeps } from '../../app'; import { AlertsContextProvider } from '../../context/alerts_context'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks'; -import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { ReactWrapper } from 'enzyme'; import { AlertEdit } from './alert_edit'; const actionTypeRegistry = actionTypeRegistryMock.create(); const alertTypeRegistry = alertTypeRegistryMock.create(); describe('alert_edit', () => { - let deps: AppDeps | null; + let deps: any; let wrapper: ReactWrapper; beforeAll(async () => { const mockes = coreMock.createSetup(); - const [ - { - chrome, - docLinks, - application: { capabilities }, - }, - ] = await mockes.getStartServices(); deps = { - chrome, - docLinks, toastNotifications: mockes.notifications.toasts, - injectedMetadata: mockes.injectedMetadata, http: mockes.http, uiSettings: mockes.uiSettings, - dataPlugin: dataPluginMock.createStartContract(), - charts: chartPluginMock.createStartContract(), - capabilities: { - ...capabilities, - alerting: { - delete: true, - save: true, - show: true, - }, - }, - setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, }; @@ -121,29 +97,31 @@ describe('alert_edit', () => { actionTypeRegistry.list.mockReturnValue([actionTypeModel]); actionTypeRegistry.has.mockReturnValue(true); + wrapper = mountWithIntl( + { + return new Promise(() => {}); + }, + http: deps!.http, + actionTypeRegistry: deps!.actionTypeRegistry, + alertTypeRegistry: deps!.alertTypeRegistry, + toastNotifications: deps!.toastNotifications, + uiSettings: deps!.uiSettings, + }} + > + {}} + initialAlert={alert} + /> + + ); + // Wait for active space to resolve before requesting the component to update await act(async () => { - wrapper = mountWithIntl( - { - return new Promise(() => {}); - }, - http: deps!.http, - actionTypeRegistry: deps!.actionTypeRegistry, - alertTypeRegistry: deps!.alertTypeRegistry, - toastNotifications: deps!.toastNotifications, - uiSettings: deps!.uiSettings, - }} - > - {}} - initialAlert={alert} - /> - - ); + await nextTick(); + wrapper.update(); }); - await waitForRender(wrapper); }); it('renders alert add flyout', () => { @@ -151,9 +129,3 @@ describe('alert_edit', () => { expect(wrapper.find('[data-test-subj="saveEditedAlertButton"]').exists()).toBeTruthy(); }); }); - -async function waitForRender(wrapper: ReactWrapper) { - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); -} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 34bfdcfe4e813..82225b882975c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -106,7 +106,6 @@ export const AlertEdit = ({ return newAlert; } catch (errorRes) { setServerError(errorRes); - return undefined; } } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index cc25a0003fdd0..2975b1ef6eba2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -227,8 +227,8 @@ export const AlertsList: React.FunctionComponent = () => { render: (item: AlertTableItem) => { return ( editItem(item)} > { }, Object { "text": "average()", - "value": "average", + "value": "avg", }, Object { "text": "sum()", @@ -96,10 +96,10 @@ describe('when expression', () => { it('renders when popover title', () => { const onChangeSelectedAggType = jest.fn(); const wrapper = shallow( - + ); wrapper.simulate('click'); - expect(wrapper.find('[value="average"]').length > 0).toBeTruthy(); + expect(wrapper.find('[value="avg"]').length > 0).toBeTruthy(); expect(wrapper.contains(when)).toBeTruthy(); }); }); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index e1e11622baf6a..3473640cdc308 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -84,8 +84,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Saved '${alertName}'`); await pageObjects.triggersActionsUI.searchAlerts(alertName); - const searchResultsAfterEdit = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResultsAfterEdit).to.eql([ + const searchResultsAfterSave = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResultsAfterSave).to.eql([ { name: alertName, tagsText: '', @@ -112,20 +112,45 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should edit an alert', async () => { - const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); - const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResults).to.eql([ - { - name: createdAlert.name, - tagsText: 'foo, bar', - alertType: 'Test: Noop', - interval: '1m', - }, - ]); + const alertName = generateUniqueKey(); + await pageObjects.triggersActionsUI.clickCreateAlertButton(); + const nameInput = await testSubjects.find('alertNameInput'); + await nameInput.click(); + await nameInput.clearValue(); + await nameInput.type(alertName); + await testSubjects.click('.index-threshold-SelectOption'); + await testSubjects.click('selectIndexExpression'); + const comboBox = await find.byCssSelector('#indexSelectSearchBox'); + await comboBox.click(); + await comboBox.type('k'); + const filterSelectItem = await find.byCssSelector(`.euiFilterSelectItem`); + await filterSelectItem.click(); + await testSubjects.click('thresholdAlertTimeFieldSelect'); + const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); + await fieldOptions[1].click(); + await testSubjects.click('.slack-ActionTypeSelectOption'); + await testSubjects.click('createActionConnectorButton'); + const connectorNameInput = await testSubjects.find('nameInput'); + await connectorNameInput.click(); + await connectorNameInput.clearValue(); + const connectorName = generateUniqueKey(); + await connectorNameInput.type(connectorName); + const slackWebhookUrlInput = await testSubjects.find('slackWebhookUrlInput'); + await slackWebhookUrlInput.click(); + await slackWebhookUrlInput.clearValue(); + await slackWebhookUrlInput.type('https://test'); + await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + const loggingMessageInput = await testSubjects.find('slackMessageTextArea'); + await loggingMessageInput.click(); + await loggingMessageInput.clearValue(); + await loggingMessageInput.type('test message'); + await find.clickByCssSelector('[data-test-subj="saveAlertButton"]'); + await pageObjects.common.closeToast(); + await pageObjects.triggersActionsUI.searchAlerts(alertName); + const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResultsBeforeEdit.length).to.eql(1); - await find.clickByCssSelector(`[data-test-subj="alertEditLink-${createdAlert.id}"] button`); + await find.clickByCssSelector(`[data-test-subj="alertsTableCell-editLink"]`); const updatedAlertName = 'Changed Alert Name'; const nameInputToUpdate = await testSubjects.find('alertNameInput'); From f7f99e54cf6b3f4cf712f569c62d5611d46f038c Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 3 Mar 2020 10:52:05 -0800 Subject: [PATCH 07/13] Fixed functional test --- .../apps/triggers_actions_ui/alerts.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 3473640cdc308..31764816fac80 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -60,6 +60,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('thresholdAlertTimeFieldSelect'); const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); await fieldOptions[1].click(); + await nameInput.click(); await testSubjects.click('.slack-ActionTypeSelectOption'); await testSubjects.click('createActionConnectorButton'); const connectorNameInput = await testSubjects.find('nameInput'); @@ -128,6 +129,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('thresholdAlertTimeFieldSelect'); const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); await fieldOptions[1].click(); + await nameInput.click(); + await nameInput.click(); await testSubjects.click('.slack-ActionTypeSelectOption'); await testSubjects.click('createActionConnectorButton'); const connectorNameInput = await testSubjects.find('nameInput'); @@ -140,6 +143,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await slackWebhookUrlInput.clearValue(); await slackWebhookUrlInput.type('https://test'); await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + await pageObjects.common.closeToast(); const loggingMessageInput = await testSubjects.find('slackMessageTextArea'); await loggingMessageInput.click(); await loggingMessageInput.clearValue(); @@ -162,15 +166,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Updated '${updatedAlertName}'`); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(updatedAlertName); const searchResultsAfterEdit = await pageObjects.triggersActionsUI.getAlertsList(); expect(searchResultsAfterEdit).to.eql([ { name: updatedAlertName, - tagsText: 'foo, bar', - alertType: 'Test: Noop', + tagsText: '', + alertType: 'Index Threshold', interval: '1m', }, ]); From 2b2dff2572685468d0521e0543e42716513f143c Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 3 Mar 2020 15:14:01 -0800 Subject: [PATCH 08/13] Fixed tests --- .../sections/alert_form/alert_edit.tsx | 4 +- .../public/application/type_registry.ts | 12 +++- .../apps/triggers_actions_ui/alerts.ts | 61 ++++++------------- 3 files changed, 28 insertions(+), 49 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 82225b882975c..06d21c05582e0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -61,9 +61,7 @@ export const AlertEdit = ({ } const alertType = alertTypeRegistry.get(alert.alertTypeId); - if (!alertType) { - return null; - } + const errors = { ...(alertType ? alertType.validate(alert.params).errors : []), ...validateBaseProperties(alert).errors, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts index 3390d8910a45f..d3d052663dc71 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts @@ -43,9 +43,17 @@ export class TypeRegistry { /** * Returns an object type, null if not registered */ - public get(id: string): T | null { + public get(id: string): T { if (!this.has(id)) { - return null; + throw i18n.translate( + 'xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage', + { + defaultMessage: 'Action type "{id}" is not registered.', + values: { + id, + }, + } + ); } return this.objectTypes.get(id)!; } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 31764816fac80..6b3103dce75c2 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -18,15 +18,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const supertest = getService('supertest'); const find = getService('find'); - async function createAlert() { + async function createAlert(alertTypeId?: string, name?: string) { const { body: createdAlert } = await supertest .post(`/api/alert`) .set('kbn-xsrf', 'foo') .send({ enabled: true, - name: generateUniqueKey(), + name: name ?? generateUniqueKey(), tags: ['foo', 'bar'], - alertTypeId: 'test.noop', + alertTypeId: alertTypeId ?? 'test.noop', consumer: 'test', schedule: { interval: '1m' }, throttle: '1m', @@ -113,48 +113,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should edit an alert', async () => { - const alertName = generateUniqueKey(); - await pageObjects.triggersActionsUI.clickCreateAlertButton(); - const nameInput = await testSubjects.find('alertNameInput'); - await nameInput.click(); - await nameInput.clearValue(); - await nameInput.type(alertName); - await testSubjects.click('.index-threshold-SelectOption'); - await testSubjects.click('selectIndexExpression'); - const comboBox = await find.byCssSelector('#indexSelectSearchBox'); - await comboBox.click(); - await comboBox.type('k'); - const filterSelectItem = await find.byCssSelector(`.euiFilterSelectItem`); - await filterSelectItem.click(); - await testSubjects.click('thresholdAlertTimeFieldSelect'); - const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); - await fieldOptions[1].click(); - await nameInput.click(); - await nameInput.click(); - await testSubjects.click('.slack-ActionTypeSelectOption'); - await testSubjects.click('createActionConnectorButton'); - const connectorNameInput = await testSubjects.find('nameInput'); - await connectorNameInput.click(); - await connectorNameInput.clearValue(); - const connectorName = generateUniqueKey(); - await connectorNameInput.type(connectorName); - const slackWebhookUrlInput = await testSubjects.find('slackWebhookUrlInput'); - await slackWebhookUrlInput.click(); - await slackWebhookUrlInput.clearValue(); - await slackWebhookUrlInput.type('https://test'); - await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); - await pageObjects.common.closeToast(); - const loggingMessageInput = await testSubjects.find('slackMessageTextArea'); - await loggingMessageInput.click(); - await loggingMessageInput.clearValue(); - await loggingMessageInput.type('test message'); - await find.clickByCssSelector('[data-test-subj="saveAlertButton"]'); - await pageObjects.common.closeToast(); - await pageObjects.triggersActionsUI.searchAlerts(alertName); - const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResultsBeforeEdit.length).to.eql(1); + const createdAlert = await createAlert('.index-threshold', 'new alert'); + await pageObjects.common.navigateToApp('triggersActions'); + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); - await find.clickByCssSelector(`[data-test-subj="alertsTableCell-editLink"]`); + const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResults).to.eql([ + { + name: createdAlert.name, + tagsText: 'foo, bar', + alertType: 'Index Threshold', + interval: '1m', + }, + ]); + const editLink = await testSubjects.findAll('alertsTableCell-editLink'); + await editLink[0].click(); const updatedAlertName = 'Changed Alert Name'; const nameInputToUpdate = await testSubjects.find('alertNameInput'); From ed4f8365caeeaf389334a133f909dcac41c49873 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 3 Mar 2020 15:30:08 -0800 Subject: [PATCH 09/13] Fixed add alert --- .../sections/alert_form/alert_add.tsx | 2 +- .../sections/alert_form/alert_form.tsx | 2 +- .../public/application/type_registry.ts | 7 +++---- .../apps/triggers_actions_ui/alerts.ts | 16 +++++++++++++--- 4 files changed, 18 insertions(+), 9 deletions(-) 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 a26b01205a21c..2cb7435c1b599 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 @@ -80,7 +80,7 @@ export const AlertAdd = ({ return null; } - const alertType = alertTypeRegistry.get(alert.alertTypeId); + const alertType = alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null; const errors = { ...(alertType ? alertType.validate(alert.params).errors : []), ...validateBaseProperties(alert).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 d615e0172092a..1d1bf69d7035c 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 @@ -105,7 +105,7 @@ export const AlertForm = ({ const { http, toastNotifications, alertTypeRegistry, actionTypeRegistry } = alertsContext; const [alertTypeModel, setAlertTypeModel] = useState( - alertTypeRegistry.get(alert.alertTypeId) + alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null ); const [addModalVisible, setAddModalVisibility] = useState(false); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts index d3d052663dc71..9a5285876d284 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts @@ -45,14 +45,13 @@ export class TypeRegistry { */ public get(id: string): T { if (!this.has(id)) { - throw i18n.translate( - 'xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage', - { + throw new Error( + i18n.translate('xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage', { defaultMessage: 'Action type "{id}" is not registered.', values: { id, }, - } + }) ); } return this.objectTypes.get(id)!; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 6b3103dce75c2..281b11bcb271c 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -18,7 +18,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const supertest = getService('supertest'); const find = getService('find'); - async function createAlert(alertTypeId?: string, name?: string) { + async function createAlert(alertTypeId?: string, name?: string, params?: any) { const { body: createdAlert } = await supertest .post(`/api/alert`) .set('kbn-xsrf', 'foo') @@ -31,7 +31,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { schedule: { interval: '1m' }, throttle: '1m', actions: [], - params: {}, + params: params ?? {}, }) .expect(200); return createdAlert; @@ -113,7 +113,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should edit an alert', async () => { - const createdAlert = await createAlert('.index-threshold', 'new alert'); + const createdAlert = await createAlert('.index-threshold', 'new alert', { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000, 5000], + index: ['.kibana_1'], + timeField: 'alert', + }); await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); From 129af25436df6a522ee6aa6683a6f7336eada06f Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 3 Mar 2020 15:37:20 -0800 Subject: [PATCH 10/13] Small type fix --- .../functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 281b11bcb271c..25ebc6d610f86 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -156,7 +156,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(searchResultsAfterEdit).to.eql([ { name: updatedAlertName, - tagsText: '', + tagsText: 'foo, bar', alertType: 'Index Threshold', interval: '1m', }, From 6fddf147ab718f84e8819194d92ba04c3003427e Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 3 Mar 2020 17:18:05 -0800 Subject: [PATCH 11/13] Fixed jest test --- .../public/application/type_registry.test.ts | 8 ++++++-- .../public/application/type_registry.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts index efe58aedb8353..93e61cf5b4f43 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts @@ -78,9 +78,13 @@ describe('get()', () => { `); }); - test(`return null when action type doesn't exist`, () => { + test(`throw error when action type doesn't exist`, () => { const actionTypeRegistry = new TypeRegistry(); - expect(actionTypeRegistry.get('not-exist-action-type')).toBeNull(); + expect(() => + actionTypeRegistry.get('not-exist-action-type') + ).toThrowErrorMatchingInlineSnapshot( + `"Object type \\"not-exist-action-type\\" is not registered."` + ); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts index 9a5285876d284..8eaa9638d0806 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts @@ -47,7 +47,7 @@ export class TypeRegistry { if (!this.has(id)) { throw new Error( i18n.translate('xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage', { - defaultMessage: 'Action type "{id}" is not registered.', + defaultMessage: 'Object type "{id}" is not registered.', values: { id, }, From 5a7f9ee62301e3e0528eaf91b4557dfefaf7e0da Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Wed, 4 Mar 2020 09:50:02 -0800 Subject: [PATCH 12/13] Fixed type check --- .../application/sections/alert_form/alert_add.test.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index d52ca19f58022..7bc44eafe7543 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -69,8 +69,6 @@ describe('alert_add', () => { wrapper = mountWithIntl( {}, reloadAlerts: () => { return new Promise(() => {}); }, @@ -81,7 +79,11 @@ describe('alert_add', () => { uiSettings: deps.uiSettings, }} > - + {}} + /> ); // Wait for active space to resolve before requesting the component to update From c2c10ac392f10bf54afd3d855e25abe636214cfb Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Wed, 4 Mar 2020 11:03:33 -0800 Subject: [PATCH 13/13] Fixed bugs with interval and throttle + index threshold expression --- .../threshold/expression.tsx | 21 +++++++++++++-- .../sections/alert_form/alert_edit.test.tsx | 2 +- .../sections/alert_form/alert_form.tsx | 26 +++++++++++++------ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index 654c69939da6d..a2ef67be7bca2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -132,7 +132,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { + const setDefaultExpressionValues = async () => { setAlertProperty('params', { ...alertParams, aggType: aggType ?? DEFAULT_VALUES.AGGREGATION_TYPE, @@ -143,6 +143,13 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent 0) { + const currentEsFields = await getFields(index); + const timeFields = getTimeFieldOptions(currentEsFields as any); + + setEsFields(currentEsFields); + setTimeFieldOptions([firstFieldOption, ...timeFields]); + } }; const getFields = async (indexes: string[]) => { @@ -259,7 +266,17 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { }, ], tags: [], - name: 'Serhii', + name: 'test alert', throttle: null, apiKeyOwner: null, createdBy: '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 1d1bf69d7035c..b875fae75c7df 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 @@ -112,10 +112,18 @@ export const AlertForm = ({ const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); const [actionTypesIndex, setActionTypesIndex] = useState(undefined); const [alertTypesIndex, setAlertTypesIndex] = useState(undefined); - const [alertInterval, setAlertInterval] = useState(null); - const [alertIntervalUnit, setAlertIntervalUnit] = useState('m'); - const [alertThrottle, setAlertThrottle] = useState(null); - const [alertThrottleUnit, setAlertThrottleUnit] = useState('m'); + const [alertInterval, setAlertInterval] = useState( + alert.schedule.interval ? parseInt(alert.schedule.interval.replace(/^[A-Za-z]+$/, ''), 0) : 1 + ); + const [alertIntervalUnit, setAlertIntervalUnit] = useState( + alert.schedule.interval ? alert.schedule.interval.replace(alertInterval.toString(), '') : 'm' + ); + const [alertThrottle, setAlertThrottle] = useState( + alert.throttle ? parseInt(alert.throttle.replace(/^[A-Za-z]+$/, ''), 0) : null + ); + const [alertThrottleUnit, setAlertThrottleUnit] = useState( + alert.throttle ? alert.throttle.replace((alertThrottle ?? '').toString(), '') : 'm' + ); const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(true); const [connectors, setConnectors] = useState([]); const [defaultActionGroupId, setDefaultActionGroupId] = useState(undefined); @@ -774,12 +782,12 @@ export const AlertForm = ({ fullWidth min={1} compressed - value={alertInterval || 1} + value={alertInterval} name="interval" data-test-subj="intervalInput" onChange={e => { const interval = e.target.value !== '' ? parseInt(e.target.value, 10) : null; - setAlertInterval(interval); + setAlertInterval(interval ?? 1); setScheduleProperty('interval', `${e.target.value}${alertIntervalUnit}`); }} /> @@ -789,7 +797,7 @@ export const AlertForm = ({ fullWidth compressed value={alertIntervalUnit} - options={getTimeOptions(alertInterval ?? 1)} + options={getTimeOptions(alertInterval)} onChange={e => { setAlertIntervalUnit(e.target.value); setScheduleProperty('interval', `${alertInterval}${e.target.value}`); @@ -824,7 +832,9 @@ export const AlertForm = ({ options={getTimeOptions(alertThrottle ?? 1)} onChange={e => { setAlertThrottleUnit(e.target.value); - setAlertProperty('throttle', `${alertThrottle}${e.target.value}`); + if (alertThrottle) { + setAlertProperty('throttle', `${alertThrottle}${e.target.value}`); + } }} />