From b107924decf3c29e8de6a2cc4ba7455a6b38e247 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Tue, 16 Apr 2019 13:59:54 -0400 Subject: [PATCH 01/13] initial support for adding actions via threshold watch --- .../watcher/common/constants/action_types.ts | 2 - .../__tests__/get_action_type.js | 11 -- .../watcher/common/types/watch_types.ts | 12 ++ x-pack/plugins/watcher/public/lib/api.ts | 5 + .../watcher/public/models/action/action.js | 2 - .../public/models/action/email_action.js | 6 +- .../public/models/action/hipchat.action.js | 69 -------- .../public/models/action/index.action.js | 7 + .../public/models/action/jira.action.js | 7 + .../public/models/action/logging_action.js | 7 +- .../public/models/action/pagerduty.action.js | 9 +- .../public/models/action/webhook.action.js | 7 + .../plugins/watcher/public/models/index.d.ts | 8 + .../components/logging_action_fields.tsx | 40 +++++ .../threshold_watch_action_accordion.tsx | 156 ++++++++++++++++++ .../threshold_watch_action_dropdown.tsx | 90 ++++++++++ .../threshold_watch_action_panel.tsx | 39 +++++ .../threshold_watch_edit_component.tsx | 5 + .../watch_edit/components/watch_edit.tsx | 3 +- .../watcher/server/models/action/action.js | 1 + .../models/settings/__tests__/settings.js | 2 - .../server/models/settings/settings.js | 1 - 22 files changed, 396 insertions(+), 93 deletions(-) delete mode 100644 x-pack/plugins/watcher/public/models/action/hipchat.action.js create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_panel.tsx diff --git a/x-pack/plugins/watcher/common/constants/action_types.ts b/x-pack/plugins/watcher/common/constants/action_types.ts index 3bb0d5748347c..72f1c9d4bf102 100644 --- a/x-pack/plugins/watcher/common/constants/action_types.ts +++ b/x-pack/plugins/watcher/common/constants/action_types.ts @@ -13,8 +13,6 @@ export const ACTION_TYPES: { [key: string]: string } = { LOGGING: 'logging', - HIPCHAT: 'hipchat', - SLACK: 'slack', JIRA: 'jira', diff --git a/x-pack/plugins/watcher/common/lib/get_action_type/__tests__/get_action_type.js b/x-pack/plugins/watcher/common/lib/get_action_type/__tests__/get_action_type.js index 7b2fd7bd84062..c342c45b0c6c2 100644 --- a/x-pack/plugins/watcher/common/lib/get_action_type/__tests__/get_action_type.js +++ b/x-pack/plugins/watcher/common/lib/get_action_type/__tests__/get_action_type.js @@ -56,17 +56,6 @@ describe('get_action_type', () => { expect(type).to.be(ACTION_TYPES.LOGGING); }); - it(`correctly calculates ACTION_TYPES.HIPCHAT`, () => { - const actionJson = { - hipchat: { - 'foo': 'bar' - } - }; - const type = getActionType(actionJson); - - expect(type).to.be(ACTION_TYPES.HIPCHAT); - }); - it(`correctly calculates ACTION_TYPES.SLACK`, () => { const actionJson = { slack: { diff --git a/x-pack/plugins/watcher/common/types/watch_types.ts b/x-pack/plugins/watcher/common/types/watch_types.ts index 0850911cdab6c..3cb3e21d29a40 100644 --- a/x-pack/plugins/watcher/common/types/watch_types.ts +++ b/x-pack/plugins/watcher/common/types/watch_types.ts @@ -55,3 +55,15 @@ export interface BaseWatch { }; }; } + +export interface WatchAction { + id: string; + type: 'email' | 'webhook' | 'index' | 'logging' | 'slack' | 'jira' | 'pagerduty'; + typeName: string; + iconClass: 'document' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email'; + simulateMessage: string; + simulateFailMessage: string; + simulatePrompt: string; + selectMessage: string; + text?: string; +} diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts index 6aeede8136122..ea34e366d9b55 100644 --- a/x-pack/plugins/watcher/public/lib/api.ts +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -3,6 +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 { Settings } from 'plugins/watcher/models/settings'; import { Watch } from 'plugins/watcher/models/watch'; import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; import { WatchStatus } from 'plugins/watcher/models/watch_status'; @@ -123,3 +124,7 @@ export const executeWatch = async (executeWatchDetails: ExecutedWatchDetails, wa }); return data; }; +export const fetchSettings = async () => { + const { data } = await getHttpClient().get(`${basePath}/settings`); + return Settings.fromUpstreamJson(data); +}; diff --git a/x-pack/plugins/watcher/public/models/action/action.js b/x-pack/plugins/watcher/public/models/action/action.js index f3cdfeef7fab7..8d2de2bb8d53d 100644 --- a/x-pack/plugins/watcher/public/models/action/action.js +++ b/x-pack/plugins/watcher/public/models/action/action.js @@ -11,7 +11,6 @@ import { LoggingAction } from './logging_action'; import { SlackAction } from './slack_action'; import { WebhookAction } from './webhook.action'; import { IndexAction } from './index.action'; -import { HipchatAction } from './hipchat.action'; import { PagerDutyAction } from './pagerduty.action'; import { JiraAction } from './jira.action'; import { UnknownAction } from './unknown_action'; @@ -22,7 +21,6 @@ set(ActionTypes, ACTION_TYPES.LOGGING, LoggingAction); set(ActionTypes, ACTION_TYPES.SLACK, SlackAction); set(ActionTypes, ACTION_TYPES.WEBHOOK, WebhookAction); set(ActionTypes, ACTION_TYPES.INDEX, IndexAction); -set(ActionTypes, ACTION_TYPES.HIPCHAT, HipchatAction); set(ActionTypes, ACTION_TYPES.PAGERDUTY, PagerDutyAction); set(ActionTypes, ACTION_TYPES.JIRA, JiraAction); diff --git a/x-pack/plugins/watcher/public/models/action/email_action.js b/x-pack/plugins/watcher/public/models/action/email_action.js index 31fe6a9405e0b..9ad3a8ba13e2a 100644 --- a/x-pack/plugins/watcher/public/models/action/email_action.js +++ b/x-pack/plugins/watcher/public/models/action/email_action.js @@ -70,14 +70,14 @@ export class EmailAction extends BaseAction { } static typeName = i18n.translate('xpack.watcher.models.emailAction.typeName', { - defaultMessage: 'E-mail', + defaultMessage: 'Email', }); static iconClass = 'kuiIcon fa-envelope-o'; static template = ''; static selectMessage = i18n.translate('xpack.watcher.models.emailAction.selectMessageText', { - defaultMessage: 'Send out an e-mail from your server.', + defaultMessage: 'Send out an email from your server.', }); static simulatePrompt = i18n.translate('xpack.watcher.models.emailAction.simulateButtonLabel', { - defaultMessage: 'Test fire an e-mail now' + defaultMessage: 'Test fire an email now' }); } diff --git a/x-pack/plugins/watcher/public/models/action/hipchat.action.js b/x-pack/plugins/watcher/public/models/action/hipchat.action.js deleted file mode 100644 index bf4eb0d039342..0000000000000 --- a/x-pack/plugins/watcher/public/models/action/hipchat.action.js +++ /dev/null @@ -1,69 +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 { get } from 'lodash'; -import { BaseAction } from './base_action'; -import { i18n } from '@kbn/i18n'; - -const requiredFields = ['message']; -const optionalFields = ['account', 'proxy']; - -const allFields = [...requiredFields, ...optionalFields]; - -export class HipchatAction extends BaseAction { - constructor(props = {}) { - super(props); - - this.fields = {}; - allFields.forEach((field) => { - this.fields[field] = get(props, field); - }); - } - - get upstreamJson() { - // Add all required fields to the request body - let result = requiredFields.reduce((acc, field) => { - acc[field] = this.fields[field]; - return acc; - }, super.upstreamJson); - - // If optional fields have been set, add them to the body - result = optionalFields.reduce((acc, field) => { - if (this[field]) { - acc[field] = this.fields[field]; - } - return acc; - }, result); - - return result; - } - - get description() { - return i18n.translate('xpack.watcher.models.hipchatAction.description', { - defaultMessage: '{body} will be sent through Hipchat', - values: { - body: this.fields.message && this.fields.message.body || '' - } - }); - } - - get simulateMessage() { - return i18n.translate('xpack.watcher.models.hipchatAction.simulateMessage', { - defaultMessage: 'Hipchat message has been sent.', - }); - } - - get simulateFailMessage() { - return i18n.translate('xpack.watcher.models.hipchatAction.simulateFailMessage', { - defaultMessage: 'Failed to send Hipchat message.', - }); - } - - static fromUpstreamJson(upstreamAction) { - return new HipchatAction(upstreamAction); - } -} diff --git a/x-pack/plugins/watcher/public/models/action/index.action.js b/x-pack/plugins/watcher/public/models/action/index.action.js index b4a311501df8c..75d236e69943f 100644 --- a/x-pack/plugins/watcher/public/models/action/index.action.js +++ b/x-pack/plugins/watcher/public/models/action/index.action.js @@ -85,4 +85,11 @@ export class IndexAction extends BaseAction { static fromUpstreamJson(upstreamAction) { return new IndexAction(upstreamAction); } + + static typeName = i18n.translate('xpack.watcher.models.indexAction.typeName', { + defaultMessage: 'Index', + }); + static selectMessage = i18n.translate('xpack.watcher.models.emailAction.selectMessageText', { + defaultMessage: 'Index data into Elasticsearch.', + }); } diff --git a/x-pack/plugins/watcher/public/models/action/jira.action.js b/x-pack/plugins/watcher/public/models/action/jira.action.js index e5b8c1a2ab7d9..0b627c03a7ceb 100644 --- a/x-pack/plugins/watcher/public/models/action/jira.action.js +++ b/x-pack/plugins/watcher/public/models/action/jira.action.js @@ -66,5 +66,12 @@ export class JiraAction extends BaseAction { static fromUpstreamJson(upstreamAction) { return new JiraAction(upstreamAction); } + + static typeName = i18n.translate('xpack.watcher.models.emailAction.typeName', { + defaultMessage: 'Jira', + }); + static selectMessage = i18n.translate('xpack.watcher.models.jiraAction.selectMessageText', { + defaultMessage: 'Create issues in Atlassian’s Jira Software.', + }); } diff --git a/x-pack/plugins/watcher/public/models/action/logging_action.js b/x-pack/plugins/watcher/public/models/action/logging_action.js index 603784053e717..999cf5af8e4b4 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -51,6 +51,10 @@ export class LoggingAction extends BaseAction { }); } + static defaults = { + text: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' + }; + static fromUpstreamJson(upstreamAction) { return new LoggingAction(upstreamAction); } @@ -58,7 +62,7 @@ export class LoggingAction extends BaseAction { static typeName = i18n.translate('xpack.watcher.models.loggingAction.typeName', { defaultMessage: 'Logging', }); - static iconClass = 'kuiIcon fa-file-text-o'; + static iconClass = 'document'; static selectMessage = i18n.translate('xpack.watcher.models.loggingAction.selectMessageText', { defaultMessage: 'Add a new item to the logs.', }); @@ -66,4 +70,5 @@ export class LoggingAction extends BaseAction { static simulatePrompt = i18n.translate('xpack.watcher.models.loggingAction.simulateButtonLabel', { defaultMessage: 'Log a sample message now', }); + } diff --git a/x-pack/plugins/watcher/public/models/action/pagerduty.action.js b/x-pack/plugins/watcher/public/models/action/pagerduty.action.js index d8a497cb3236f..fa36fa9ba384f 100644 --- a/x-pack/plugins/watcher/public/models/action/pagerduty.action.js +++ b/x-pack/plugins/watcher/public/models/action/pagerduty.action.js @@ -69,12 +69,19 @@ export class PagerDutyAction extends BaseAction { get simulateFailMessage() { return i18n.translate('xpack.watcher.models.pagerDutyAction.simulateFailMessage', { - defaultMessage: 'Failed to send Hipchat event.', + defaultMessage: 'Failed to send PagerDuty event.', }); } static fromUpstreamJson(upstreamAction) { return new PagerDutyAction(upstreamAction); } + + static typeName = i18n.translate('xpack.watcher.models.pagerDutyAction.typeName', { + defaultMessage: 'PagerDuty', + }); + static selectMessage = i18n.translate('xpack.watcher.models.pagerDutyAction.selectMessageText', { + defaultMessage: 'Create events in PagerDuty.', + }); } diff --git a/x-pack/plugins/watcher/public/models/action/webhook.action.js b/x-pack/plugins/watcher/public/models/action/webhook.action.js index 8c530c9913538..10f293682b478 100644 --- a/x-pack/plugins/watcher/public/models/action/webhook.action.js +++ b/x-pack/plugins/watcher/public/models/action/webhook.action.js @@ -88,4 +88,11 @@ export class WebhookAction extends BaseAction { static fromUpstreamJson(upstreamAction) { return new WebhookAction(upstreamAction); } + + static typeName = i18n.translate('xpack.watcher.models.webhookAction.typeName', { + defaultMessage: 'Webhook', + }); + static selectMessage = i18n.translate('xpack.watcher.models.webhookAction.selectMessageText', { + defaultMessage: 'Send a request to any web service.', + }); } diff --git a/x-pack/plugins/watcher/public/models/index.d.ts b/x-pack/plugins/watcher/public/models/index.d.ts index 10ffa05ec7ff9..c394053a8c068 100644 --- a/x-pack/plugins/watcher/public/models/index.d.ts +++ b/x-pack/plugins/watcher/public/models/index.d.ts @@ -24,8 +24,16 @@ declare module 'plugins/watcher/models/watch_history_item' { declare module 'plugins/watcher/models/watch_status' { export const WatchStatus: any; } + +declare module 'plugins/watcher/models/settings' { + export const Settings: any; +} +declare module 'plugins/watcher/models/action' { + export const Action: any; +} // TODO: Remove once typescript definitions are in EUI declare module '@elastic/eui' { export const EuiCodeEditor: React.SFC; export const EuiDescribedFormGroup: React.SFC; + export const EuiSuperSelect: React.SFC; } diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx new file mode 100644 index 0000000000000..8a4a7674790b5 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx @@ -0,0 +1,40 @@ +/* + * 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 { EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const LoggingActionFields = ({ + action, + editAction, +}: { + action: { text?: string }; + editAction: (changedProperty: { key: string; value: string }) => void; +}) => { + const { text } = action; + return ( + + + { + editAction({ key: 'text', value: e.target.value }); + }} + /> + + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx new file mode 100644 index 0000000000000..dbcfbdc2bac6b --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.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, useContext } from 'react'; + +import { + EuiAccordion, + EuiButton, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiTitle, + EuiForm, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; +import { Action } from 'plugins/watcher/models/action'; +import { toastNotifications } from 'ui/notify'; +import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; +import { WatchAction } from '../../../../common/types/watch_types'; +import { ACTION_TYPES, ACTION_MODES } from '../../../../common/constants'; +import { WatchContext } from './watch_context'; +import { LoggingActionFields } from './logging_action_fields'; +import { executeWatch } from '../../../lib/api'; + +const ActionFieldsComponent = { + [ACTION_TYPES.LOGGING]: LoggingActionFields, + // TODO add support for additional action types +}; + +export const WatchActionsAccordion: React.FunctionComponent = () => { + const { watch, setWatchProperty } = useContext(WatchContext); + const { actions } = watch; + + const ButtonContent = ({ + name, + iconClass, + }: { + name: string; + iconClass: 'document' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email'; + }) => ( + + + + + + + +
{name}
+
+
+
+
+ ); + + const DeleteIcon = ({ onDelete }: { onDelete: () => void }) => ( + onDelete()} + /> + ); + + if (actions && actions.length >= 1) { + return actions.map((action: WatchAction) => { + const FieldsComponent = ActionFieldsComponent[action.type]; + return ( + } + extraAction={ + { + const updatedActions = actions.filter( + (actionItem: WatchAction) => actionItem.id !== action.id + ); + setWatchProperty('actions', updatedActions); + }} + /> + } + paddingSize="l" + > + + { + const updatedActions = actions.map((actionItem: WatchAction) => { + if (actionItem.id === action.id) { + const ActionTypes = Action.getActionTypes(); + const ActionType = ActionTypes[action.type]; + const { key, value } = changedProperty; + return new ActionType({ ...action, [key]: value }); + } + return actionItem; + }); + setWatchProperty('actions', updatedActions); + }} + /> + { + const actionModes = watch.actions.reduce((acc: any, actionItem: WatchAction) => { + acc[action.id] = + action === actionItem ? ACTION_MODES.FORCE_EXECUTE : ACTION_MODES.SKIP; + return acc; + }, {}); + const executeDetails = new ExecuteDetails({ + ignoreCondition: true, + actionModes, + recordExecution: false, + }); + try { + const executedWatch = await executeWatch(executeDetails, watch); + const executeResults = WatchHistoryItem.fromUpstreamJson( + executedWatch.watchHistoryItem + ); + const actionStatuses = executeResults.watchStatus.actionStatuses; + const actionStatus = actionStatuses.find( + (actionItem: WatchAction) => actionItem.id === action.id + ); + + if (actionStatus.lastExecutionSuccessful === false) { + const message = actionStatus.lastExecutionReason || action.simulateFailMessage; + return toastNotifications.addDanger(message); + } + toastNotifications.addSuccess(action.simulateMessage); + } catch (e) { + toastNotifications.addDanger(e.data.message); + } + }} + > + {action.simulatePrompt} + + + + ); + }); + } + return null; +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx new file mode 100644 index 0000000000000..7f14c0d7caae8 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx @@ -0,0 +1,90 @@ +/* + * 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 { EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { Fragment, useContext, useEffect, useState } from 'react'; +import { Action } from 'plugins/watcher/models/action'; +import { WatchAction } from '../../../../common/types/watch_types'; +import { fetchSettings } from '../../../lib/api'; +import { WatchContext } from './watch_context'; + +interface ActionOption extends WatchAction { + isEnabled: boolean; +} + +export const WatchActionsDropdown: React.FunctionComponent = () => { + const EMPTY_FIRST_OPTION_VALUE = 'empty-first-option'; + + const disabledMessage = i18n.translate( + 'xpack.watcher.sections.watchEdit.actions.disabledOptionLabel', + { + defaultMessage: 'Disabled. Configure elasticsearch.yml.', + } + ); + + const firstActionOption = { + inputDisplay: i18n.translate('xpack.watcher.sections.watchEdit.actions.emptyFirstOptionLabel', { + defaultMessage: 'Add an action', + }), + value: EMPTY_FIRST_OPTION_VALUE, + }; + + const allActionTypes = Action.getActionTypes(); + const { addAction } = useContext(WatchContext); + const [actions, setActions] = useState(null); + const getSettings = async () => { + const settings = await fetchSettings(); + const newActions = Object.keys(allActionTypes).map(actionKey => { + const { typeName, iconClass, selectMessage } = allActionTypes[actionKey]; + return { + type: actionKey, + typeName, + iconClass, + selectMessage, + isEnabled: settings.actionTypes[actionKey].enabled, + }; + }); + setActions(newActions); + }; + useEffect(() => { + getSettings(); + }, []); + const actionOptions = + actions && + actions.map((action: ActionOption) => { + const description = action.isEnabled ? action.selectMessage : disabledMessage; + return { + value: action.type, + inputDisplay: action.typeName, + disabled: !action.isEnabled, + dropdownDisplay: ( + + {action.typeName} + + +

{description}

+
+
+ ), + }; + }); + const actionOptionsWithEmptyValue = actionOptions + ? [firstActionOption, ...actionOptions] + : [firstActionOption]; + return ( + { + const defaults = allActionTypes[value].defaults; + addAction({ defaults, type: value }); + }} + itemLayoutAlign="top" + hasDividers + isLoading={!actions} + /> + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_panel.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_panel.tsx new file mode 100644 index 0000000000000..e6c5ccf538d00 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_panel.tsx @@ -0,0 +1,39 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { Fragment, useContext } from 'react'; +import { WatchActionsDropdown } from './threshold_watch_action_dropdown'; +import { WatchActionsAccordion } from './threshold_watch_action_accordion'; +import { WatchContext } from './watch_context'; + +export const WatchActionsPanel = () => { + const { watch } = useContext(WatchContext); + return ( + + + + +

+ {i18n.translate('xpack.watcher.sections.watchEdit.actions.title', { + defaultMessage: + 'Will perform {watchActionsCount, plural, one{# action} other {# actions}} once met', + values: { + watchActionsCount: watch.actions.length, + }, + })} +

+
+
+ + + +
+ + +
+ ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx index e415f89cc3a65..9828bf4391bdb 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx @@ -35,7 +35,9 @@ import { groupByTypes } from '../../../models/watch/group_by_types'; import { comparators } from '../comparators'; import { timeUnits } from '../time_units'; import { onWatchSave, saveWatch } from '../watch_edit_actions'; +import { WatchActionsPanel } from './threshold_watch_action_panel'; import { WatchContext } from './watch_context'; + const firstFieldOption = { text: i18n.translate('xpack.watcher.sections.watchEdit.titlePanel.timeFieldOptionLabel', { defaultMessage: 'Select a field', @@ -763,8 +765,11 @@ const ThresholdWatchEditUi = ({ + + ) : null} + { return new (Watch.getWatchTypes())[state.type]({ ...state, [property]: value }); } case 'addAction': + const { type, defaults } = payload; const newWatch = new (Watch.getWatchTypes())[state.type](state); - newWatch.addAction(payload); + newWatch.createAction(type, defaults); return newWatch; } }; diff --git a/x-pack/plugins/watcher/server/models/action/action.js b/x-pack/plugins/watcher/server/models/action/action.js index 65c3f2b44c26c..09f5dffca23d2 100644 --- a/x-pack/plugins/watcher/server/models/action/action.js +++ b/x-pack/plugins/watcher/server/models/action/action.js @@ -19,6 +19,7 @@ set(ActionTypes, ACTION_TYPES.LOGGING, LoggingAction); set(ActionTypes, ACTION_TYPES.EMAIL, EmailAction); set(ActionTypes, ACTION_TYPES.SLACK, SlackAction); set(ActionTypes, ACTION_TYPES.UNKNOWN, UnknownAction); +// TODO add additional actions export class Action { static getActionTypes = () => { diff --git a/x-pack/plugins/watcher/server/models/settings/__tests__/settings.js b/x-pack/plugins/watcher/server/models/settings/__tests__/settings.js index 7add7a0e0564e..9c03e4d58d721 100644 --- a/x-pack/plugins/watcher/server/models/settings/__tests__/settings.js +++ b/x-pack/plugins/watcher/server/models/settings/__tests__/settings.js @@ -19,7 +19,6 @@ describe('settings module', () => { expect(actionTypes.webhook.enabled).to.be(true); expect(actionTypes.index.enabled).to.be(true); expect(actionTypes.logging.enabled).to.be(true); - expect(actionTypes.hipchat.enabled).to.be(false); expect(actionTypes.slack.enabled).to.be(true); expect(actionTypes.jira.enabled).to.be(false); expect(actionTypes.pagerduty.enabled).to.be(false); @@ -94,7 +93,6 @@ describe('settings module', () => { expect(json.action_types.webhook.enabled).to.be(true); expect(json.action_types.index.enabled).to.be(true); expect(json.action_types.logging.enabled).to.be(true); - expect(json.action_types.hipchat.enabled).to.be(false); expect(json.action_types.slack.enabled).to.be(true); expect(json.action_types.jira.enabled).to.be(false); expect(json.action_types.pagerduty.enabled).to.be(false); diff --git a/x-pack/plugins/watcher/server/models/settings/settings.js b/x-pack/plugins/watcher/server/models/settings/settings.js index 91970573f795f..0d6a9244c6c3d 100644 --- a/x-pack/plugins/watcher/server/models/settings/settings.js +++ b/x-pack/plugins/watcher/server/models/settings/settings.js @@ -22,7 +22,6 @@ function isEnabledByDefault(actionType) { function requiresAccountInfo(actionType) { switch (actionType) { case ACTION_TYPES.EMAIL: - case ACTION_TYPES.HIPCHAT: // case ACTION_TYPES.SLACK: // https://github.com/elastic/x-pack-elasticsearch/issues/1573 case ACTION_TYPES.JIRA: case ACTION_TYPES.PAGERDUTY: From 38e3d5540d4650add6e79b8bf9c6b7b7d3f8f0bc Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Wed, 17 Apr 2019 15:15:25 -0400 Subject: [PATCH 02/13] added validation; support for email and slack action types --- .../public/models/action/email_action.js | 47 ++++++-- .../public/models/action/index.action.js | 2 +- .../public/models/action/jira.action.js | 2 +- .../public/models/action/logging_action.js | 16 ++- .../public/models/action/slack_action.js | 35 +++++- .../components/email_action_fields.tsx | 109 ++++++++++++++++++ .../components/logging_action_fields.tsx | 24 +++- .../components/slack_action_fields.tsx | 80 +++++++++++++ .../threshold_watch_action_accordion.tsx | 4 + 9 files changed, 298 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/email_action_fields.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/slack_action_fields.tsx diff --git a/x-pack/plugins/watcher/public/models/action/email_action.js b/x-pack/plugins/watcher/public/models/action/email_action.js index 9ad3a8ba13e2a..dc7b4113c4bb6 100644 --- a/x-pack/plugins/watcher/public/models/action/email_action.js +++ b/x-pack/plugins/watcher/public/models/action/email_action.js @@ -12,10 +12,40 @@ export class EmailAction extends BaseAction { constructor(props = {}) { super(props); - const toArray = get(props, 'to', []); - this.to = isArray(toArray) ? toArray : [ toArray ]; - this.subject = get(props, 'subject', ''); - this.body = get(props, 'body', ''); + const toArray = get(props, 'to'); + this.to = isArray(toArray) ? toArray : toArray && [ toArray ]; + this.subject = get(props, 'subject'); + this.body = get(props, 'body'); + } + + validateAction() { + const errors = { + to: [], + subject: [], + body: [], + }; + if (!this.to || this.to.length === 0) { + errors.to.push( + i18n.translate('xpack.watcher.watchActions.email.emailRecipientIsRequiredValidationMessage', { + defaultMessage: 'To email address is required.', + }) + ); + } + if (!this.subject) { + errors.subject.push( + i18n.translate('xpack.watcher.watchActions.email.emailSubhectIsRequiredValidationMessage', { + defaultMessage: 'Subject is required.', + }) + ); + } + if (!this.body) { + errors.body.push( + i18n.translate('xpack.watcher.watchActions.email.emailBodyIsRequiredValidationMessage', { + defaultMessage: 'Body is required.', + }) + ); + } + return errors; } get upstreamJson() { @@ -48,7 +78,7 @@ export class EmailAction extends BaseAction { get simulateMessage() { const toList = this.to.join(', '); return i18n.translate('xpack.watcher.models.emailAction.simulateMessage', { - defaultMessage: 'Sample e-mail sent to {toList}', + defaultMessage: 'Sample email sent to {toList}', values: { toList } @@ -58,7 +88,7 @@ export class EmailAction extends BaseAction { get simulateFailMessage() { const toList = this.to.join(', '); return i18n.translate('xpack.watcher.models.emailAction.simulateFailMessage', { - defaultMessage: 'Failed to send e-mail to {toList}.', + defaultMessage: 'Failed to send email to {toList}.', values: { toList } @@ -72,7 +102,7 @@ export class EmailAction extends BaseAction { static typeName = i18n.translate('xpack.watcher.models.emailAction.typeName', { defaultMessage: 'Email', }); - static iconClass = 'kuiIcon fa-envelope-o'; + static iconClass = 'email'; static template = ''; static selectMessage = i18n.translate('xpack.watcher.models.emailAction.selectMessageText', { defaultMessage: 'Send out an email from your server.', @@ -80,4 +110,7 @@ export class EmailAction extends BaseAction { static simulatePrompt = i18n.translate('xpack.watcher.models.emailAction.simulateButtonLabel', { defaultMessage: 'Test fire an email now' }); + static defaults = { + subject: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' + }; } diff --git a/x-pack/plugins/watcher/public/models/action/index.action.js b/x-pack/plugins/watcher/public/models/action/index.action.js index 75d236e69943f..5cab83e4ec890 100644 --- a/x-pack/plugins/watcher/public/models/action/index.action.js +++ b/x-pack/plugins/watcher/public/models/action/index.action.js @@ -89,7 +89,7 @@ export class IndexAction extends BaseAction { static typeName = i18n.translate('xpack.watcher.models.indexAction.typeName', { defaultMessage: 'Index', }); - static selectMessage = i18n.translate('xpack.watcher.models.emailAction.selectMessageText', { + static selectMessage = i18n.translate('xpack.watcher.models.indexAction.selectMessageText', { defaultMessage: 'Index data into Elasticsearch.', }); } diff --git a/x-pack/plugins/watcher/public/models/action/jira.action.js b/x-pack/plugins/watcher/public/models/action/jira.action.js index 0b627c03a7ceb..d39245a985254 100644 --- a/x-pack/plugins/watcher/public/models/action/jira.action.js +++ b/x-pack/plugins/watcher/public/models/action/jira.action.js @@ -67,7 +67,7 @@ export class JiraAction extends BaseAction { return new JiraAction(upstreamAction); } - static typeName = i18n.translate('xpack.watcher.models.emailAction.typeName', { + static typeName = i18n.translate('xpack.watcher.models.jiraAction.typeName', { defaultMessage: 'Jira', }); static selectMessage = i18n.translate('xpack.watcher.models.jiraAction.selectMessageText', { diff --git a/x-pack/plugins/watcher/public/models/action/logging_action.js b/x-pack/plugins/watcher/public/models/action/logging_action.js index 999cf5af8e4b4..7afbd3461198e 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -12,7 +12,21 @@ export class LoggingAction extends BaseAction { constructor(props = {}) { super(props); - this.text = get(props, 'text', ''); + this.text = get(props, 'text'); + } + + validateAction() { + const errors = { + text: [], + }; + if (!this.text) { + errors.text.push( + i18n.translate('xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage', { + defaultMessage: 'Log text is required.', + }) + ); + } + return errors; } get upstreamJson() { diff --git a/x-pack/plugins/watcher/public/models/action/slack_action.js b/x-pack/plugins/watcher/public/models/action/slack_action.js index 2cbfa27b2ca7a..69af275f6de48 100644 --- a/x-pack/plugins/watcher/public/models/action/slack_action.js +++ b/x-pack/plugins/watcher/public/models/action/slack_action.js @@ -16,15 +16,36 @@ export class SlackAction extends BaseAction { constructor(props = {}) { super(props); - const toArray = get(props, 'to', []); - this.to = isArray(toArray) ? toArray : [ toArray ]; + const toArray = get(props, 'to'); + this.to = isArray(toArray) ? toArray : toArray && [ toArray ]; this.text = props.text; } + validateAction() { + const errors = { + to: [], + text: [] + }; + if (!this.to || this.to.length === 0) { + errors.to.push( + i18n.translate('xpack.watcher.watchActions.slack.slackRecipientIsRequiredValidationMessage', { + defaultMessage: 'Recipient is required.', + }) + ); + } + if (!this.text) { + errors.text.push( + i18n.translate('xpack.watcher.watchActions.slack.slackMessageIsRequiredValidationMessage', { + defaultMessage: 'Message is required.', + }) + ); + } + return errors; + } validate() { const errors = []; - if (!this.to.length) { + if (!this.to || !this.to.length) { const message = ( void; +} + +export const EmailActionFields: React.FunctionComponent = ({ action, editAction }) => { + const { to, subject, body } = action; + const errors = action.validateAction(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + + return ( + + + { + const toValues = e.target.value; + const toArray = (toValues || '').split(',').map(toVal => toVal.trim()); + editAction({ key: 'to', value: toArray.join(', ') }); + }} + onBlur={() => { + if (!to) { + editAction({ key: 'to', value: [] }); + } + }} + /> + + + { + editAction({ key: 'subject', value: e.target.value }); + }} + onBlur={() => { + if (!subject) { + editAction({ key: 'subject', value: '' }); + } + }} + /> + + + { + editAction({ key: 'body', value: e.target.value }); + }} + onBlur={() => { + if (!body) { + editAction({ key: 'body', value: '' }); + } + }} + /> + + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx index 8a4a7674790b5..cecb44861e54d 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Fragment } from 'react'; - -import { EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { ErrableFormRow } from '../../../components/form_errors'; export const LoggingActionFields = ({ action, @@ -16,10 +16,16 @@ export const LoggingActionFields = ({ editAction: (changedProperty: { key: string; value: string }) => void; }) => { const { text } = action; + const errors = action.validateAction(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); return ( - { + name="text" + value={text || ''} + onChange={(e: React.ChangeEvent) => { editAction({ key: 'text', value: e.target.value }); }} + onBlur={() => { + if (!text) { + editAction({ key: 'text', value: '' }); + } + }} /> - + ); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/slack_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/slack_action_fields.tsx new file mode 100644 index 0000000000000..5f10b99a9625c --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/slack_action_fields.tsx @@ -0,0 +1,80 @@ +/* + * 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 { EuiFieldText, EuiTextArea } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ErrableFormRow } from '../../../components/form_errors'; + +interface Props { + action: {}; + editAction: (changedProperty: { key: string; value: any }) => void; +} + +export const SlackActionFields: React.FunctionComponent = ({ action, editAction }) => { + const { text, to } = action; + const errors = action.validateAction(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + return ( + + + { + const toValues = e.target.value; + const toArray = (toValues || '').split(',').map(toVal => toVal.trim()); + editAction({ key: 'to', value: toArray.join(', ') }); + }} + onBlur={() => { + if (!to) { + editAction({ key: 'to', value: [] }); + } + }} + /> + + + { + editAction({ key: 'text', value: e.target.value }); + }} + onBlur={() => { + if (!text) { + editAction({ key: 'text', value: [] }); + } + }} + /> + + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx index dbcfbdc2bac6b..9192ce67ee15c 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx @@ -23,11 +23,15 @@ import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; import { WatchAction } from '../../../../common/types/watch_types'; import { ACTION_TYPES, ACTION_MODES } from '../../../../common/constants'; import { WatchContext } from './watch_context'; +import { EmailActionFields } from './email_action_fields'; import { LoggingActionFields } from './logging_action_fields'; +import { SlackActionFields } from './slack_action_fields'; import { executeWatch } from '../../../lib/api'; const ActionFieldsComponent = { [ACTION_TYPES.LOGGING]: LoggingActionFields, + [ACTION_TYPES.SLACK]: SlackActionFields, + [ACTION_TYPES.EMAIL]: EmailActionFields, // TODO add support for additional action types }; From ab7f44e070c83511dcebeaa3931bc8562acab18d Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Thu, 18 Apr 2019 10:05:40 -0400 Subject: [PATCH 03/13] added support for index action --- .../watcher/common/types/watch_types.ts | 2 +- .../watcher/public/models/action/action.js | 2 +- .../{index.action.js => index_action.js} | 70 +++++++------ .../public/models/action/logging_action.js | 2 +- .../components/index_action_fields.tsx | 51 ++++++++++ .../components/logging_action_fields.tsx | 9 +- .../threshold_watch_action_accordion.tsx | 4 +- .../watcher/server/models/action/action.js | 2 + .../server/models/action/index_action.js | 97 +++++++++++++++++++ 9 files changed, 193 insertions(+), 46 deletions(-) rename x-pack/plugins/watcher/public/models/action/{index.action.js => index_action.js} (60%) create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/index_action_fields.tsx create mode 100644 x-pack/plugins/watcher/server/models/action/index_action.js diff --git a/x-pack/plugins/watcher/common/types/watch_types.ts b/x-pack/plugins/watcher/common/types/watch_types.ts index 3cb3e21d29a40..0367f92ccda16 100644 --- a/x-pack/plugins/watcher/common/types/watch_types.ts +++ b/x-pack/plugins/watcher/common/types/watch_types.ts @@ -60,7 +60,7 @@ export interface WatchAction { id: string; type: 'email' | 'webhook' | 'index' | 'logging' | 'slack' | 'jira' | 'pagerduty'; typeName: string; - iconClass: 'document' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email'; + iconClass: 'loggingApp' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email'; simulateMessage: string; simulateFailMessage: string; simulatePrompt: string; diff --git a/x-pack/plugins/watcher/public/models/action/action.js b/x-pack/plugins/watcher/public/models/action/action.js index 8d2de2bb8d53d..61a0b2eb0f45c 100644 --- a/x-pack/plugins/watcher/public/models/action/action.js +++ b/x-pack/plugins/watcher/public/models/action/action.js @@ -10,7 +10,7 @@ import { EmailAction } from './email_action'; import { LoggingAction } from './logging_action'; import { SlackAction } from './slack_action'; import { WebhookAction } from './webhook.action'; -import { IndexAction } from './index.action'; +import { IndexAction } from './index_action'; import { PagerDutyAction } from './pagerduty.action'; import { JiraAction } from './jira.action'; import { UnknownAction } from './unknown_action'; diff --git a/x-pack/plugins/watcher/public/models/action/index.action.js b/x-pack/plugins/watcher/public/models/action/index_action.js similarity index 60% rename from x-pack/plugins/watcher/public/models/action/index.action.js rename to x-pack/plugins/watcher/public/models/action/index_action.js index 5cab83e4ec890..148a18f7fe245 100644 --- a/x-pack/plugins/watcher/public/models/action/index.action.js +++ b/x-pack/plugins/watcher/public/models/action/index_action.js @@ -9,75 +9,66 @@ import { get } from 'lodash'; import { BaseAction } from './base_action'; import { i18n } from '@kbn/i18n'; -const requiredFields = ['host', 'port']; -const optionalFields = [ - 'scheme', - 'path', - 'method', - 'headers', - 'params', - 'auth', - 'body', - 'proxy', - 'connection_timeout', - 'read_timeout', - 'url' -]; - -const allFields = [...requiredFields, ...optionalFields]; - export class IndexAction extends BaseAction { constructor(props = {}) { super(props); - this.fields = {}; - allFields.forEach((field) => { - this.fields[field] = get(props, field); - }); + this.index = get(props, 'index'); + } + + validateAction() { + const errors = { + index: [], + }; + if (!this.index) { + errors.index.push( + i18n.translate('xpack.watcher.watchActions.index.indexIsRequiredValidationMessage', { + defaultMessage: 'Index is required.', + }) + ); + } + return errors; } + get upstreamJson() { - // Add all required fields to the request body - let result = requiredFields.reduce((acc, field) => { - acc[field] = this.fields[field]; - return acc; - }, super.upstreamJson); + const result = super.upstreamJson; - // If optional fields have been set, add them to the body - result = optionalFields.reduce((acc, field) => { - if (this[field]) { - acc[field] = this.fields[field]; + Object.assign(result, { + index: { + index: this.index, } - return acc; - }, result); + }); return result; } get description() { + const index = this.index || ''; return i18n.translate('xpack.watcher.models.indexAction.description', { - defaultMessage: 'The {index} will be indexed as {docType}', + defaultMessage: 'The {index} will be indexed', values: { - index: this.fields.index, - docType: this.fields.doc_type, + index, } }); } get simulateMessage() { + const index = this.index || ''; return i18n.translate('xpack.watcher.models.indexAction.simulateMessage', { defaultMessage: 'Index {index} has been indexed.', values: { - index: this.index, + index, } }); } get simulateFailMessage() { + const index = this.index || ''; return i18n.translate('xpack.watcher.models.indexAction.simulateFailMessage', { defaultMessage: 'Failed to index {index}.', values: { - index: this.index + index, } }); } @@ -86,10 +77,15 @@ export class IndexAction extends BaseAction { return new IndexAction(upstreamAction); } + static defaults = {}; static typeName = i18n.translate('xpack.watcher.models.indexAction.typeName', { defaultMessage: 'Index', }); + static iconClass = 'apps'; static selectMessage = i18n.translate('xpack.watcher.models.indexAction.selectMessageText', { defaultMessage: 'Index data into Elasticsearch.', }); + static simulatePrompt = i18n.translate('xpack.watcher.models.indexAction.simulateButtonLabel', { + defaultMessage: 'Index data now', + }); } diff --git a/x-pack/plugins/watcher/public/models/action/logging_action.js b/x-pack/plugins/watcher/public/models/action/logging_action.js index 7afbd3461198e..123d0f820b04f 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -76,7 +76,7 @@ export class LoggingAction extends BaseAction { static typeName = i18n.translate('xpack.watcher.models.loggingAction.typeName', { defaultMessage: 'Logging', }); - static iconClass = 'document'; + static iconClass = 'loggingApp'; static selectMessage = i18n.translate('xpack.watcher.models.loggingAction.selectMessageText', { defaultMessage: 'Add a new item to the logs.', }); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/index_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/index_action_fields.tsx new file mode 100644 index 0000000000000..249d14c5fe014 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/index_action_fields.tsx @@ -0,0 +1,51 @@ +/* + * 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 { EuiFieldText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ErrableFormRow } from '../../../components/form_errors'; + +interface Props { + action: { text?: string }; + editAction: (changedProperty: { key: string; value: string }) => void; +} + +export const IndexActionFields: React.FunctionComponent = ({ action, editAction }) => { + const { index } = action; + const errors = action.validateAction(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + return ( + + + ) => { + editAction({ key: 'index', value: e.target.value }); + }} + onBlur={() => { + if (!index) { + editAction({ key: 'index', value: '' }); + } + }} + /> + + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx index cecb44861e54d..0ab979c757bdd 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx @@ -8,13 +8,12 @@ import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../components/form_errors'; -export const LoggingActionFields = ({ - action, - editAction, -}: { +interface Props { action: { text?: string }; editAction: (changedProperty: { key: string; value: string }) => void; -}) => { +} + +export const LoggingActionFields: React.FunctionComponent = ({ action, editAction }) => { const { text } = action; const errors = action.validateAction(); const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx index 9192ce67ee15c..55977a9422f6a 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx @@ -26,12 +26,14 @@ import { WatchContext } from './watch_context'; import { EmailActionFields } from './email_action_fields'; import { LoggingActionFields } from './logging_action_fields'; import { SlackActionFields } from './slack_action_fields'; +import { IndexActionFields } from './index_action_fields'; import { executeWatch } from '../../../lib/api'; const ActionFieldsComponent = { [ACTION_TYPES.LOGGING]: LoggingActionFields, [ACTION_TYPES.SLACK]: SlackActionFields, [ACTION_TYPES.EMAIL]: EmailActionFields, + [ACTION_TYPES.INDEX]: IndexActionFields, // TODO add support for additional action types }; @@ -44,7 +46,7 @@ export const WatchActionsAccordion: React.FunctionComponent = () => { iconClass, }: { name: string; - iconClass: 'document' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email'; + iconClass: 'loggingApp' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email'; }) => ( diff --git a/x-pack/plugins/watcher/server/models/action/action.js b/x-pack/plugins/watcher/server/models/action/action.js index 09f5dffca23d2..37153120003c6 100644 --- a/x-pack/plugins/watcher/server/models/action/action.js +++ b/x-pack/plugins/watcher/server/models/action/action.js @@ -11,6 +11,7 @@ import { ACTION_TYPES } from '../../../common/constants'; import { LoggingAction } from './logging_action'; import { EmailAction } from './email_action'; import { SlackAction } from './slack_action'; +import { IndexAction } from './index_action'; import { UnknownAction } from './unknown_action'; import { i18n } from '@kbn/i18n'; @@ -18,6 +19,7 @@ const ActionTypes = {}; set(ActionTypes, ACTION_TYPES.LOGGING, LoggingAction); set(ActionTypes, ACTION_TYPES.EMAIL, EmailAction); set(ActionTypes, ACTION_TYPES.SLACK, SlackAction); +set(ActionTypes, ACTION_TYPES.INDEX, IndexAction); set(ActionTypes, ACTION_TYPES.UNKNOWN, UnknownAction); // TODO add additional actions diff --git a/x-pack/plugins/watcher/server/models/action/index_action.js b/x-pack/plugins/watcher/server/models/action/index_action.js new file mode 100644 index 0000000000000..692737456faa1 --- /dev/null +++ b/x-pack/plugins/watcher/server/models/action/index_action.js @@ -0,0 +1,97 @@ +/* + * 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 { BaseAction } from './base_action'; +import { ACTION_TYPES, ERROR_CODES } from '../../../common/constants'; +import { i18n } from '@kbn/i18n'; + +export class IndexAction extends BaseAction { + constructor(props, errors) { + props.type = ACTION_TYPES.INDEX; + super(props, errors); + + this.index = props.index; + } + + // To Kibana + get downstreamJson() { + const result = super.downstreamJson; + Object.assign(result, { + index: this.index + }); + + return result; + } + + // From Kibana + static fromDownstreamJson(json) { + const props = super.getPropsFromDownstreamJson(json); + const { errors } = this.validateJson(json); + + Object.assign(props, { + index: json.index + }); + + const action = new IndexAction(props, errors); + return { action, errors }; + } + + // To Elasticsearch + get upstreamJson() { + const result = super.upstreamJson; + + result[this.id] = { + index: this.index, + }; + return result; + } + + // From Elasticsearch + static fromUpstreamJson(json) { + const props = super.getPropsFromUpstreamJson(json); + const { errors } = this.validateJson(json.actionJson); + + Object.assign(props, { + index: json.actionJson.index.index + }); + + const action = new IndexAction(props, errors); + return { action, errors }; + } + + static validateJson(json) { + const errors = []; + + if (!json.index) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.indexAction.actionJsonIndexPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonIndex} property', + values: { + actionJsonIndex: 'actionJson.index' + } + }), + }); + + json.index = {}; + } + + if (!json.index.index) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonIndexNamePropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonIndexName} property', + values: { + actionJsonIndexName: 'actionJson.index.index' + } + }), + }); + } + + return { errors: errors.length ? errors : null }; + } + +} From ac0b56906e3ee686cb0a3950202d67d3af9400b5 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Fri, 19 Apr 2019 10:48:58 -0400 Subject: [PATCH 04/13] added support for webhook action --- .../watcher/public/models/action/action.js | 2 +- .../public/models/action/logging_action.js | 1 - .../public/models/action/webhook.action.js | 98 ---------- .../public/models/action/webhook_action.js | 126 +++++++++++++ .../threshold_actions_form_builder.tsx | 81 ++++++++ .../threshold_watch_action_accordion.tsx | 6 +- .../threshold_watch_action_dropdown.tsx | 3 +- .../components/webhook_action_fields.tsx | 175 ++++++++++++++++++ .../watcher/server/models/action/action.js | 2 + .../server/models/action/webhook_action.js | 139 ++++++++++++++ 10 files changed, 530 insertions(+), 103 deletions(-) delete mode 100644 x-pack/plugins/watcher/public/models/action/webhook.action.js create mode 100644 x-pack/plugins/watcher/public/models/action/webhook_action.js create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_actions_form_builder.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx create mode 100644 x-pack/plugins/watcher/server/models/action/webhook_action.js diff --git a/x-pack/plugins/watcher/public/models/action/action.js b/x-pack/plugins/watcher/public/models/action/action.js index 61a0b2eb0f45c..9d10d26683dbd 100644 --- a/x-pack/plugins/watcher/public/models/action/action.js +++ b/x-pack/plugins/watcher/public/models/action/action.js @@ -9,7 +9,7 @@ import { ACTION_TYPES } from 'plugins/watcher/../common/constants'; import { EmailAction } from './email_action'; import { LoggingAction } from './logging_action'; import { SlackAction } from './slack_action'; -import { WebhookAction } from './webhook.action'; +import { WebhookAction } from './webhook_action'; import { IndexAction } from './index_action'; import { PagerDutyAction } from './pagerduty.action'; import { JiraAction } from './jira.action'; diff --git a/x-pack/plugins/watcher/public/models/action/logging_action.js b/x-pack/plugins/watcher/public/models/action/logging_action.js index 123d0f820b04f..509ed71f7dbe3 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -84,5 +84,4 @@ export class LoggingAction extends BaseAction { static simulatePrompt = i18n.translate('xpack.watcher.models.loggingAction.simulateButtonLabel', { defaultMessage: 'Log a sample message now', }); - } diff --git a/x-pack/plugins/watcher/public/models/action/webhook.action.js b/x-pack/plugins/watcher/public/models/action/webhook.action.js deleted file mode 100644 index 10f293682b478..0000000000000 --- a/x-pack/plugins/watcher/public/models/action/webhook.action.js +++ /dev/null @@ -1,98 +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 { get } from 'lodash'; -import { BaseAction } from './base_action'; -import { i18n } from '@kbn/i18n'; - -const requiredFields = ['host', 'port']; -const optionalFields = [ - 'scheme', - 'path', - 'method', - 'headers', - 'params', - 'auth', - 'body', - 'proxy', - 'connection_timeout', - 'read_timeout', - 'url' -]; - -const allFields = [...requiredFields, ...optionalFields]; - -export class WebhookAction extends BaseAction { - constructor(props = {}) { - super(props); - - this.fields = {}; - allFields.forEach((field) => { - this.fields[field] = get(props, field); - }); - - const { url, host, port, path } = this.fields; - this.fullPath = url ? url : host + port + path; - } - - get upstreamJson() { - // Add all required fields to the request body - let result = requiredFields.reduce((acc, field) => { - acc[field] = this.fields[field]; - return acc; - }, super.upstreamJson); - - // If optional fields have been set, add them to the body - result = optionalFields.reduce((acc, field) => { - if (this[field]) { - acc[field] = this.fields[field]; - } - return acc; - }, result); - - return result; - } - - get description() { - return i18n.translate('xpack.watcher.models.webhookAction.description', { - defaultMessage: 'Webhook will trigger a {method} request on {fullPath}', - values: { - method: this.method, - fullPath: this.fullPath - } - }); - } - - get simulateMessage() { - return i18n.translate('xpack.watcher.models.webhookAction.simulateMessage', { - defaultMessage: 'Sample request sent to {fullPath}', - values: { - fullPath: this.fullPath - } - }); - } - - get simulateFailMessage() { - return i18n.translate('xpack.watcher.models.webhookAction.simulateFailMessage', { - defaultMessage: 'Failed to send request to {fullPath}.', - values: { - fullPath: this.fullPath - } - }); - } - - static fromUpstreamJson(upstreamAction) { - return new WebhookAction(upstreamAction); - } - - static typeName = i18n.translate('xpack.watcher.models.webhookAction.typeName', { - defaultMessage: 'Webhook', - }); - static selectMessage = i18n.translate('xpack.watcher.models.webhookAction.selectMessageText', { - defaultMessage: 'Send a request to any web service.', - }); -} diff --git a/x-pack/plugins/watcher/public/models/action/webhook_action.js b/x-pack/plugins/watcher/public/models/action/webhook_action.js new file mode 100644 index 0000000000000..8cd6fca745027 --- /dev/null +++ b/x-pack/plugins/watcher/public/models/action/webhook_action.js @@ -0,0 +1,126 @@ +/* + * 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 { get } from 'lodash'; +import { BaseAction } from './base_action'; +import { i18n } from '@kbn/i18n'; + +export class WebhookAction extends BaseAction { + constructor(props = {}) { + super(props); + + this.method = get(props, 'method'); + this.host = get(props, 'host'); + this.port = get(props, 'port'); + this.path = get(props, 'path'); + this.body = get(props, 'body'); + this.fullPath = `${this.host}${this.port}${this.path}`; + } + + + validateAction() { + const errors = { + host: [], + port: [], + body: [], + }; + + if (!this.host) { + errors.host.push( + i18n.translate('xpack.watcher.watchActions.webhook.hostIsRequiredValidationMessage', { + defaultMessage: 'Host is required.', + }) + ); + } + if (!this.port) { + errors.port.push( + i18n.translate('xpack.watcher.watchActions.webhook.portIsRequiredValidationMessage', { + defaultMessage: 'Port is required.', + }) + ); + } + if (this.body || this.body !== '') { + try { + const parsedJson = JSON.parse(this.body); + if (parsedJson && typeof parsedJson !== 'object') { + errors.body.push(i18n.translate('xpack.watcher.watchActions.webhook.bodyParseValidationMessage', { + defaultMessage: 'Invalid JSON', + })); + } + } catch (e) { + errors.body.push(i18n.translate('xpack.watcher.watchActions.webhook.bodyParseValidationMessage', { + defaultMessage: 'Invalid JSON', + })); + } + } + return errors; + } + + get upstreamJson() { + const result = super.upstreamJson; + + Object.assign(result, { + method: this.method, + host: this.host, + port: this.port, + path: this.path, + body: this.body, + webhook: { + host: this.host, + port: this.port, + } + }); + + return result; + } + + get description() { + return i18n.translate('xpack.watcher.models.webhookAction.description', { + defaultMessage: 'Webhook will trigger a {method} request on {fullPath}', + values: { + method: this.method, + fullPath: this.fullPath + } + }); + } + + get simulateMessage() { + return i18n.translate('xpack.watcher.models.webhookAction.simulateMessage', { + defaultMessage: 'Sample request sent to {fullPath}', + values: { + fullPath: this.fullPath + } + }); + } + + get simulateFailMessage() { + return i18n.translate('xpack.watcher.models.webhookAction.simulateFailMessage', { + defaultMessage: 'Failed to send request to {fullPath}.', + values: { + fullPath: this.fullPath + } + }); + } + + static fromUpstreamJson(upstreamAction) { + return new WebhookAction(upstreamAction); + } + + static defaults = { + body: JSON.stringify({ message: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' }, null, 2) + }; + static typeName = i18n.translate('xpack.watcher.models.webhookAction.typeName', { + defaultMessage: 'Webhook', + }); + static iconClass = 'logoWebhook'; + static selectMessage = i18n.translate('xpack.watcher.models.webhookAction.selectMessageText', { + defaultMessage: 'Send a request to any web service.', + }); + static simulatePrompt = i18n.translate('xpack.watcher.models.webhookAction.simulateButtonLabel', { + defaultMessage: 'Send request now', + }); +} diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_actions_form_builder.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_actions_form_builder.tsx new file mode 100644 index 0000000000000..f0209da3352c0 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_actions_form_builder.tsx @@ -0,0 +1,81 @@ +/* + * 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 { EuiFieldNumber, EuiFieldText, EuiTextArea } from '@elastic/eui'; +import { ErrableFormRow } from '../../../components/form_errors'; + +interface Props { + action: any; // TODO fix + // fields: Array<{ + // required: boolean; + // fieldType: 'text' | 'textarea' | 'array' | 'number'; + // fieldName: string; + // fieldLabel: string; + // }>; + editAction: (changedProperty: { key: string; value: string }) => void; +} + +const FORM_CONTROLS = { + text: EuiFieldText, + textarea: EuiTextArea, + array: EuiFieldText, // TODO replace with combo box? + number: EuiFieldNumber, +}; + +export const ThresholdActionsFormBuilder: React.FunctionComponent = ({ + action, + editAction, +}) => { + const errors = action.validateAction(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + return ( + + {action.fields.map((field, index) => { + const { required, fieldName, fieldLabel, fieldType } = field; + const fieldValue = action[fieldName]; + if (required) { + const FormControl = FORM_CONTROLS[fieldType]; + const currentValue = Array.isArray(fieldValue) ? fieldValue.join(', ') : fieldValue; // TODO cleanup/rename? + return ( + + ) => { + let newValue = e.target.value; + if (fieldType === 'array') { + const toArray = (newValue || '').split(',').map(val => val.trim()); + newValue = toArray.join(', '); + } + if (fieldType === 'number') { + newValue = parseInt(newValue, 10); + } + editAction({ key: fieldName, value: newValue }); + }} + onBlur={() => { + if (!action[fieldName]) { + editAction({ key: fieldName, value: fieldType === 'array' ? [] : '' }); + } + }} + /> + + ); + } + // TODO implement non-required form row + return null; + })} + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx index 55977a9422f6a..dfca9441a7439 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx @@ -23,10 +23,11 @@ import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; import { WatchAction } from '../../../../common/types/watch_types'; import { ACTION_TYPES, ACTION_MODES } from '../../../../common/constants'; import { WatchContext } from './watch_context'; -import { EmailActionFields } from './email_action_fields'; +import { WebhookActionFields } from './webhook_action_fields'; import { LoggingActionFields } from './logging_action_fields'; -import { SlackActionFields } from './slack_action_fields'; import { IndexActionFields } from './index_action_fields'; +import { SlackActionFields } from './slack_action_fields'; +import { EmailActionFields } from './email_action_fields'; import { executeWatch } from '../../../lib/api'; const ActionFieldsComponent = { @@ -34,6 +35,7 @@ const ActionFieldsComponent = { [ACTION_TYPES.SLACK]: SlackActionFields, [ACTION_TYPES.EMAIL]: EmailActionFields, [ACTION_TYPES.INDEX]: IndexActionFields, + [ACTION_TYPES.WEBHOOK]: WebhookActionFields, // TODO add support for additional action types }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx index 7f14c0d7caae8..3a06b979083d7 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx @@ -44,7 +44,8 @@ export const WatchActionsDropdown: React.FunctionComponent = () => { typeName, iconClass, selectMessage, - isEnabled: settings.actionTypes[actionKey].enabled, + isEnabled: true, // TODO temp + // isEnabled: settings.actionTypes[actionKey].enabled, }; }); setActions(newActions); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx new file mode 100644 index 0000000000000..602fcc2e2d382 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx @@ -0,0 +1,175 @@ +/* + * 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 { + EuiCodeEditor, + EuiFieldNumber, + EuiFieldText, + EuiFormRow, + EuiSelect, + EuiTextArea, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ErrableFormRow } from '../../../components/form_errors'; + +interface Props { + action: {}; + editAction: (changedProperty: { key: string; value: any }) => void; +} + +export const WebhookActionFields: React.FunctionComponent = ({ action, editAction }) => { + const { method, host, port, path, body } = action; + const errors = action.validateAction(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + + return ( + + + { + editAction({ key: 'method', value: e.target.value }); + }} + /> + + + { + editAction({ key: 'host', value: e.target.value }); + }} + onBlur={() => { + if (!host) { + editAction({ key: 'host', value: '' }); + } + }} + /> + + + { + editAction({ key: 'port', value: parseInt(e.target.value, 10) }); + }} + onBlur={() => { + if (!port) { + editAction({ key: 'port', value: '' }); + } + }} + /> + + + { + editAction({ key: 'path', value: e.target.value }); + }} + /> + + + { + editAction({ key: 'body', value: json }); + }} + /> + + + ); +}; diff --git a/x-pack/plugins/watcher/server/models/action/action.js b/x-pack/plugins/watcher/server/models/action/action.js index 37153120003c6..02dcc673a97a9 100644 --- a/x-pack/plugins/watcher/server/models/action/action.js +++ b/x-pack/plugins/watcher/server/models/action/action.js @@ -12,6 +12,7 @@ import { LoggingAction } from './logging_action'; import { EmailAction } from './email_action'; import { SlackAction } from './slack_action'; import { IndexAction } from './index_action'; +import { WebhookAction } from './webhook_action'; import { UnknownAction } from './unknown_action'; import { i18n } from '@kbn/i18n'; @@ -20,6 +21,7 @@ set(ActionTypes, ACTION_TYPES.LOGGING, LoggingAction); set(ActionTypes, ACTION_TYPES.EMAIL, EmailAction); set(ActionTypes, ACTION_TYPES.SLACK, SlackAction); set(ActionTypes, ACTION_TYPES.INDEX, IndexAction); +set(ActionTypes, ACTION_TYPES.WEBHOOK, WebhookAction); set(ActionTypes, ACTION_TYPES.UNKNOWN, UnknownAction); // TODO add additional actions diff --git a/x-pack/plugins/watcher/server/models/action/webhook_action.js b/x-pack/plugins/watcher/server/models/action/webhook_action.js new file mode 100644 index 0000000000000..42dfbf70d17ba --- /dev/null +++ b/x-pack/plugins/watcher/server/models/action/webhook_action.js @@ -0,0 +1,139 @@ +/* + * 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 { BaseAction } from './base_action'; +import { ACTION_TYPES, ERROR_CODES } from '../../../common/constants'; +import { i18n } from '@kbn/i18n'; + +export class WebhookAction extends BaseAction { + constructor(props, errors) { + props.type = ACTION_TYPES.WEBHOOK; + super(props, errors); + + this.method = props.method; + this.host = props.host; + this.port = props.port; + this.path = props.path; + this.body = props.body; + } + + // To Kibana + get downstreamJson() { + const result = super.downstreamJson; + Object.assign(result, { + method: this.method, + host: this.host, + port: this.port, + path: this.path, + body: this.body, + }); + return result; + } + + // From Kibana + static fromDownstreamJson(json) { + const props = super.getPropsFromDownstreamJson(json); + const { errors } = this.validateJson(json); + + Object.assign(props, { + method: json.method, + host: json.host, + port: json.port, + path: json.path, + body: json.body, + }); + + const action = new WebhookAction(props, errors); + return { action, errors }; + } + + // To Elasticsearch + get upstreamJson() { + const result = super.upstreamJson; + + const optionalFields = {}; + if (this.path) { + optionalFields.path = this.path; + } + if (this.method) { + optionalFields.method = this.method; + } + if (this.body) { + optionalFields.body = this.body; + } + + result[this.id] = { + webhook: { + host: this.host, + port: this.port, + ...optionalFields, + headers: { + 'Content-Type': 'application/json', + }, + } + }; + return result; + } + + // From Elasticsearch + static fromUpstreamJson(json) { + const props = super.getPropsFromUpstreamJson(json); + const { errors } = this.validateJson(json.actionJson); + + const optionalFields = {}; + + if (json.actionJson.webhook.path) { + optionalFields.path = json.actionJson.webhook.path; + } + if (json.actionJson.webhook.method) { + optionalFields.method = json.actionJson.webhook.method; + } + if (json.actionJson.webhook.body) { + optionalFields.body = json.actionJson.webhook.body; + } + + Object.assign(props, { + host: json.actionJson.webhook.host, + port: json.actionJson.webhook.port, + ...optionalFields, + }); + + const action = new WebhookAction(props, errors); + return { action, errors }; + } + + static validateJson(json) { + const errors = []; + + if (!json.webhook.host) { // TODO this is broken when called from the client + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonWebhookHostPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonWebhookHost} property', + values: { + actionJsonWebhookHost: 'actionJson.webhook.host' + } + }), + }); + json.webhook = {}; + } + + if (!json.webhook.port) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonWebhookPortPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonWebhookPort} property', + values: { + actionJsonWebhookPort: 'actionJson.webhook.port' + } + }), + }); + } + + return { errors: errors.length ? errors : null }; + } + +} From f3ce9ca8813b8a44afe9710b7c909c2d0b15b8c7 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 22 Apr 2019 08:59:38 -0400 Subject: [PATCH 05/13] added support for pagerduty action --- .../watcher/common/types/watch_types.ts | 2 +- .../watcher/public/models/action/action.js | 2 +- .../public/models/action/index_action.js | 2 +- ...agerduty.action.js => pagerduty_action.js} | 63 ++++++------ .../public/models/action/webhook_action.js | 1 - .../components/pagerduty_action_fields.tsx | 51 ++++++++++ .../threshold_watch_action_accordion.tsx | 4 +- .../watcher/server/models/action/action.js | 2 + .../server/models/action/pagerduty_action.js | 99 +++++++++++++++++++ 9 files changed, 187 insertions(+), 39 deletions(-) rename x-pack/plugins/watcher/public/models/action/{pagerduty.action.js => pagerduty_action.js} (60%) create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/pagerduty_action_fields.tsx create mode 100644 x-pack/plugins/watcher/server/models/action/pagerduty_action.js diff --git a/x-pack/plugins/watcher/common/types/watch_types.ts b/x-pack/plugins/watcher/common/types/watch_types.ts index 0367f92ccda16..c463cd8c4812d 100644 --- a/x-pack/plugins/watcher/common/types/watch_types.ts +++ b/x-pack/plugins/watcher/common/types/watch_types.ts @@ -60,7 +60,7 @@ export interface WatchAction { id: string; type: 'email' | 'webhook' | 'index' | 'logging' | 'slack' | 'jira' | 'pagerduty'; typeName: string; - iconClass: 'loggingApp' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email'; + iconClass: 'loggingApp' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email' | 'indexOpen'; simulateMessage: string; simulateFailMessage: string; simulatePrompt: string; diff --git a/x-pack/plugins/watcher/public/models/action/action.js b/x-pack/plugins/watcher/public/models/action/action.js index 9d10d26683dbd..b5d5aa7112893 100644 --- a/x-pack/plugins/watcher/public/models/action/action.js +++ b/x-pack/plugins/watcher/public/models/action/action.js @@ -11,7 +11,7 @@ import { LoggingAction } from './logging_action'; import { SlackAction } from './slack_action'; import { WebhookAction } from './webhook_action'; import { IndexAction } from './index_action'; -import { PagerDutyAction } from './pagerduty.action'; +import { PagerDutyAction } from './pagerduty_action'; import { JiraAction } from './jira.action'; import { UnknownAction } from './unknown_action'; diff --git a/x-pack/plugins/watcher/public/models/action/index_action.js b/x-pack/plugins/watcher/public/models/action/index_action.js index 148a18f7fe245..6f00588cb9e00 100644 --- a/x-pack/plugins/watcher/public/models/action/index_action.js +++ b/x-pack/plugins/watcher/public/models/action/index_action.js @@ -81,7 +81,7 @@ export class IndexAction extends BaseAction { static typeName = i18n.translate('xpack.watcher.models.indexAction.typeName', { defaultMessage: 'Index', }); - static iconClass = 'apps'; + static iconClass = 'indexOpen'; static selectMessage = i18n.translate('xpack.watcher.models.indexAction.selectMessageText', { defaultMessage: 'Index data into Elasticsearch.', }); diff --git a/x-pack/plugins/watcher/public/models/action/pagerduty.action.js b/x-pack/plugins/watcher/public/models/action/pagerduty_action.js similarity index 60% rename from x-pack/plugins/watcher/public/models/action/pagerduty.action.js rename to x-pack/plugins/watcher/public/models/action/pagerduty_action.js index fa36fa9ba384f..aa4ff44f49fd0 100644 --- a/x-pack/plugins/watcher/public/models/action/pagerduty.action.js +++ b/x-pack/plugins/watcher/public/models/action/pagerduty_action.js @@ -9,55 +9,43 @@ import { get } from 'lodash'; import { BaseAction } from './base_action'; import { i18n } from '@kbn/i18n'; -const requiredFields = ['description', 'type']; -const optionalFields = [ - 'event_type', - 'incident_key', - 'client', - 'client_url', - 'attach_payload', - 'contexts', - 'proxy', - 'href', - 'src', -]; - -const allFields = [...requiredFields, ...optionalFields]; - export class PagerDutyAction extends BaseAction { constructor(props = {}) { super(props); + this.message = get(props, 'message'); + } - this.fields = {}; - allFields.forEach((field) => { - this.fields[field] = get(props, field); - }); + validateAction() { + const errors = { + message: [], + }; + + if (!this.message) { + errors.message.push( + i18n.translate('xpack.watcher.watchActions.pagerduty.descriptionIsRequiredValidationMessage', { + defaultMessage: 'Description is required.', + }) + ); + } + return errors; } get upstreamJson() { - // Add all required fields to the request body - let result = requiredFields.reduce((acc, field) => { - acc[field] = this.fields[field]; - return acc; - }, super.upstreamJson); + const result = super.upstreamJson; - // If optional fields have been set, add them to the body - result = optionalFields.reduce((acc, field) => { - if (this.fields[field]) { - acc[field] = this.fields[field]; + Object.assign(result, { + description: this.message, + pagerduty: { + description: this.message, } - return acc; - }, result); + }); return result; } get description() { return i18n.translate('xpack.watcher.models.pagerDutyAction.description', { - defaultMessage: '{description} will be sent to PagerDuty', - values: { - description: this.fields.description, - } + defaultMessage: 'A message will be sent to PagerDuty', }); } @@ -80,8 +68,15 @@ export class PagerDutyAction extends BaseAction { static typeName = i18n.translate('xpack.watcher.models.pagerDutyAction.typeName', { defaultMessage: 'PagerDuty', }); + static iconClass = 'apps'; static selectMessage = i18n.translate('xpack.watcher.models.pagerDutyAction.selectMessageText', { defaultMessage: 'Create events in PagerDuty.', }); + static simulatePrompt = i18n.translate('xpack.watcher.models.pagerDutyAction.simulateButtonLabel', { + defaultMessage: 'Test fire a PagerDuty event' + }); + static defaults = { + message: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' + }; } diff --git a/x-pack/plugins/watcher/public/models/action/webhook_action.js b/x-pack/plugins/watcher/public/models/action/webhook_action.js index 8cd6fca745027..a960b57b8fe10 100644 --- a/x-pack/plugins/watcher/public/models/action/webhook_action.js +++ b/x-pack/plugins/watcher/public/models/action/webhook_action.js @@ -21,7 +21,6 @@ export class WebhookAction extends BaseAction { this.fullPath = `${this.host}${this.port}${this.path}`; } - validateAction() { const errors = { host: [], diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/pagerduty_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/pagerduty_action_fields.tsx new file mode 100644 index 0000000000000..00e8cc9fab1d8 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/pagerduty_action_fields.tsx @@ -0,0 +1,51 @@ +/* + * 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 { EuiFieldText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ErrableFormRow } from '../../../components/form_errors'; + +interface Props { + action: { text?: string }; + editAction: (changedProperty: { key: string; value: string }) => void; +} + +export const PagerDutyActionFields: React.FunctionComponent = ({ action, editAction }) => { + const { message } = action; + const errors = action.validateAction(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + return ( + + + ) => { + editAction({ key: 'message', value: e.target.value }); + }} + onBlur={() => { + if (!message) { + editAction({ key: 'message', value: '' }); + } + }} + /> + + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx index dfca9441a7439..af32b56c46591 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx @@ -28,6 +28,7 @@ import { LoggingActionFields } from './logging_action_fields'; import { IndexActionFields } from './index_action_fields'; import { SlackActionFields } from './slack_action_fields'; import { EmailActionFields } from './email_action_fields'; +import { PagerDutyActionFields } from './pagerduty_action_fields'; import { executeWatch } from '../../../lib/api'; const ActionFieldsComponent = { @@ -36,6 +37,7 @@ const ActionFieldsComponent = { [ACTION_TYPES.EMAIL]: EmailActionFields, [ACTION_TYPES.INDEX]: IndexActionFields, [ACTION_TYPES.WEBHOOK]: WebhookActionFields, + [ACTION_TYPES.PAGERDUTY]: PagerDutyActionFields, // TODO add support for additional action types }; @@ -48,7 +50,7 @@ export const WatchActionsAccordion: React.FunctionComponent = () => { iconClass, }: { name: string; - iconClass: 'loggingApp' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email'; + iconClass: 'loggingApp' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email' | 'indexOpen'; }) => ( diff --git a/x-pack/plugins/watcher/server/models/action/action.js b/x-pack/plugins/watcher/server/models/action/action.js index 02dcc673a97a9..c1ee5aa5592d7 100644 --- a/x-pack/plugins/watcher/server/models/action/action.js +++ b/x-pack/plugins/watcher/server/models/action/action.js @@ -13,6 +13,7 @@ import { EmailAction } from './email_action'; import { SlackAction } from './slack_action'; import { IndexAction } from './index_action'; import { WebhookAction } from './webhook_action'; +import { PagerDutyAction } from './pagerduty_action'; import { UnknownAction } from './unknown_action'; import { i18n } from '@kbn/i18n'; @@ -22,6 +23,7 @@ set(ActionTypes, ACTION_TYPES.EMAIL, EmailAction); set(ActionTypes, ACTION_TYPES.SLACK, SlackAction); set(ActionTypes, ACTION_TYPES.INDEX, IndexAction); set(ActionTypes, ACTION_TYPES.WEBHOOK, WebhookAction); +set(ActionTypes, ACTION_TYPES.PAGERDUTY, PagerDutyAction); set(ActionTypes, ACTION_TYPES.UNKNOWN, UnknownAction); // TODO add additional actions diff --git a/x-pack/plugins/watcher/server/models/action/pagerduty_action.js b/x-pack/plugins/watcher/server/models/action/pagerduty_action.js new file mode 100644 index 0000000000000..4d7cd79bbf878 --- /dev/null +++ b/x-pack/plugins/watcher/server/models/action/pagerduty_action.js @@ -0,0 +1,99 @@ +/* + * 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 { BaseAction } from './base_action'; +import { ACTION_TYPES, ERROR_CODES } from '../../../common/constants'; +import { i18n } from '@kbn/i18n'; + +export class PagerDutyAction extends BaseAction { + constructor(props, errors) { + props.type = ACTION_TYPES.PAGERDUTY; + super(props, errors); + + this.description = props.description; + } + + // To Kibana + get downstreamJson() { + const result = super.downstreamJson; + Object.assign(result, { + description: this.description + }); + + return result; + } + + // From Kibana + static fromDownstreamJson(json) { + const props = super.getPropsFromDownstreamJson(json); + const { errors } = this.validateJson(json); + + Object.assign(props, { + description: json.description + }); + + const action = new PagerDutyAction(props, errors); + return { action, errors }; + } + + // To Elasticsearch + get upstreamJson() { + const result = super.upstreamJson; + + result[this.id] = { + pagerduty: { + description: this.description + } + }; + + return result; + } + + // From Elasticsearch + static fromUpstreamJson(json) { + const props = super.getPropsFromUpstreamJson(json); + const { errors } = this.validateJson(json.actionJson); + + Object.assign(props, { + description: json.actionJson.pagerduty.description + }); + + const action = new PagerDutyAction(props, errors); + return { action, errors }; + } + + static validateJson(json) { + const errors = []; + + if (!json.pagerduty) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonPagerDutyPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonLogging} property', + values: { + actionJsonLogging: 'actionJson.pagerduty' + } + }), + }); + + json.pagerduty = {}; + } + + if (!json.pagerduty.description) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonPagerDutyDescriptionPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonLoggingText} property', + values: { + actionJsonLoggingText: 'actionJson.pagerduty.description' + } + }), + }); + } + + return { errors: errors.length ? errors : null }; + } +} From b25f6eefeef7a049661cb3a15094c882f34d9ebd Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 22 Apr 2019 13:37:17 -0400 Subject: [PATCH 06/13] added support for jira action --- .../watcher/public/models/action/action.js | 2 +- .../public/models/action/base_action.js | 7 +- .../public/models/action/email_action.js | 3 +- .../public/models/action/jira.action.js | 77 --------- .../public/models/action/jira_action.js | 113 +++++++++++++ .../public/models/action/logging_action.js | 1 - .../public/models/action/slack_action.js | 1 - .../components/jira_action_fields.tsx | 125 ++++++++++++++ .../threshold_watch_action_accordion.tsx | 3 +- .../watcher/server/models/action/action.js | 3 +- .../server/models/action/jira_action.js | 156 ++++++++++++++++++ .../server/models/action/pagerduty_action.js | 12 +- 12 files changed, 407 insertions(+), 96 deletions(-) delete mode 100644 x-pack/plugins/watcher/public/models/action/jira.action.js create mode 100644 x-pack/plugins/watcher/public/models/action/jira_action.js create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/jira_action_fields.tsx create mode 100644 x-pack/plugins/watcher/server/models/action/jira_action.js diff --git a/x-pack/plugins/watcher/public/models/action/action.js b/x-pack/plugins/watcher/public/models/action/action.js index b5d5aa7112893..051c2fc6868aa 100644 --- a/x-pack/plugins/watcher/public/models/action/action.js +++ b/x-pack/plugins/watcher/public/models/action/action.js @@ -12,7 +12,7 @@ import { SlackAction } from './slack_action'; import { WebhookAction } from './webhook_action'; import { IndexAction } from './index_action'; import { PagerDutyAction } from './pagerduty_action'; -import { JiraAction } from './jira.action'; +import { JiraAction } from './jira_action'; import { UnknownAction } from './unknown_action'; const ActionTypes = {}; diff --git a/x-pack/plugins/watcher/public/models/action/base_action.js b/x-pack/plugins/watcher/public/models/action/base_action.js index 50d37dcde2edc..4375da3ee2673 100644 --- a/x-pack/plugins/watcher/public/models/action/base_action.js +++ b/x-pack/plugins/watcher/public/models/action/base_action.js @@ -47,15 +47,10 @@ export class BaseAction { return this.constructor.simulatePrompt; } - get template() { - return this.constructor.template; - } - static typeName = i18n.translate('xpack.watcher.models.baseAction.typeName', { defaultMessage: 'Action', }); - static iconClass = 'fa-cog'; - static template = ''; + static iconClass = 'apps'; static selectMessage = i18n.translate('xpack.watcher.models.baseAction.selectMessageText', { defaultMessage: 'Perform an action.', }); diff --git a/x-pack/plugins/watcher/public/models/action/email_action.js b/x-pack/plugins/watcher/public/models/action/email_action.js index dc7b4113c4bb6..910ce599833af 100644 --- a/x-pack/plugins/watcher/public/models/action/email_action.js +++ b/x-pack/plugins/watcher/public/models/action/email_action.js @@ -33,7 +33,7 @@ export class EmailAction extends BaseAction { } if (!this.subject) { errors.subject.push( - i18n.translate('xpack.watcher.watchActions.email.emailSubhectIsRequiredValidationMessage', { + i18n.translate('xpack.watcher.watchActions.email.emailSubjectIsRequiredValidationMessage', { defaultMessage: 'Subject is required.', }) ); @@ -103,7 +103,6 @@ export class EmailAction extends BaseAction { defaultMessage: 'Email', }); static iconClass = 'email'; - static template = ''; static selectMessage = i18n.translate('xpack.watcher.models.emailAction.selectMessageText', { defaultMessage: 'Send out an email from your server.', }); diff --git a/x-pack/plugins/watcher/public/models/action/jira.action.js b/x-pack/plugins/watcher/public/models/action/jira.action.js deleted file mode 100644 index d39245a985254..0000000000000 --- a/x-pack/plugins/watcher/public/models/action/jira.action.js +++ /dev/null @@ -1,77 +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 { get } from 'lodash'; -import { BaseAction } from './base_action'; -import { i18n } from '@kbn/i18n'; - -const requiredFields = ['fields']; -const optionalFields = ['account', 'proxy']; - -const allFields = [...requiredFields, ...optionalFields]; - -export class JiraAction extends BaseAction { - constructor(props = {}) { - super(props); - - this.fields = {}; - allFields.forEach((field) => { - this.fields[field] = get(props, field); - }); - } - - get upstreamJson() { - // Add all required fields to the request body - let result = requiredFields.reduce((acc, field) => { - acc[field] = this.fields[field]; - return acc; - }, super.upstreamJson); - - // If optional fields have been set, add them to the body - result = optionalFields.reduce((acc, field) => { - if (this[field]) { - acc[field] = this.fields[field]; - } - return acc; - }, result); - - return result; - } - - get description() { - return i18n.translate('xpack.watcher.models.jiraAction.description', { - defaultMessage: '{issueName} will be created in Jira', - values: { - issueName: get(this.fields, 'fields.issue.issuetype.name', ''), - } - }); - } - - get simulateMessage() { - return i18n.translate('xpack.watcher.models.jiraAction.simulateMessage', { - defaultMessage: 'Jira issue has been created.', - }); - } - - get simulateFailMessage() { - return i18n.translate('xpack.watcher.models.jiraAction.simulateFailMessage', { - defaultMessage: 'Failed to create Jira issue.', - }); - } - - static fromUpstreamJson(upstreamAction) { - return new JiraAction(upstreamAction); - } - - static typeName = i18n.translate('xpack.watcher.models.jiraAction.typeName', { - defaultMessage: 'Jira', - }); - static selectMessage = i18n.translate('xpack.watcher.models.jiraAction.selectMessageText', { - defaultMessage: 'Create issues in Atlassian’s Jira Software.', - }); -} - diff --git a/x-pack/plugins/watcher/public/models/action/jira_action.js b/x-pack/plugins/watcher/public/models/action/jira_action.js new file mode 100644 index 0000000000000..f977f42eb9958 --- /dev/null +++ b/x-pack/plugins/watcher/public/models/action/jira_action.js @@ -0,0 +1,113 @@ +/* + * 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 { get } from 'lodash'; +import { BaseAction } from './base_action'; +import { i18n } from '@kbn/i18n'; + +export class JiraAction extends BaseAction { + constructor(props = {}) { + super(props); + this.account = get(props, 'account'); + this.projectKey = get(props, 'projectKey'); + this.issueType = get(props, 'issueType'); + this.summary = get(props, 'summary'); + } + + validateAction() { + const errors = { + projectKey: [], + issueType: [], + summary: [], + }; + if (!this.projectKey) { + errors.projectKey.push( + i18n.translate('xpack.watcher.watchActions.jira.projectKeyIsRequiredValidationMessage', { + defaultMessage: 'Project key is required.', + }) + ); + } + if (!this.issueType) { + errors.issueType.push( + i18n.translate('xpack.watcher.watchActions.jira.issueTypeNameIsRequiredValidationMessage', { + defaultMessage: 'Issue type is required.', + }) + ); + } + if (!this.summary) { + errors.summary.push( + i18n.translate('xpack.watcher.watchActions.jira.summaryIsRequiredValidationMessage', { + defaultMessage: 'Summary is required.', + }) + ); + } + return errors; + } + + get upstreamJson() { + const result = super.upstreamJson; + + Object.assign(result, { + projectKey: this.projectKey, + account: this.account ? this.account : null, + issueType: this.issueType, + summary: this.summary, + jira: { + fields: { + project: { + key: this.projectKey, + }, + issuetype: { + name: this.issueType, + }, + summary: this.summary, + }, + account: this.account ? this.account : null, + } + }); + + return result; + } + + get description() { + return i18n.translate('xpack.watcher.models.jiraAction.description', { + defaultMessage: '{issueType} will be created in Jira', + values: { + issueType: this.issueType, + } + }); + } + + get simulateMessage() { + return i18n.translate('xpack.watcher.models.jiraAction.simulateMessage', { + defaultMessage: 'Jira issue has been created.', + }); + } + + get simulateFailMessage() { + return i18n.translate('xpack.watcher.models.jiraAction.simulateFailMessage', { + defaultMessage: 'Failed to create Jira issue.', + }); + } + + static fromUpstreamJson(upstreamAction) { + return new JiraAction(upstreamAction); + } + + static typeName = i18n.translate('xpack.watcher.models.jiraAction.typeName', { + defaultMessage: 'Jira', + }); + static iconClass = 'apps'; + static selectMessage = i18n.translate('xpack.watcher.models.jiraAction.selectMessageText', { + defaultMessage: 'Create issues in Atlassian’s Jira Software.', + }); + static simulatePrompt = i18n.translate('xpack.watcher.models.jiraAction.simulateButtonLabel', { + defaultMessage: 'Create a sample Jira issue now' + }); + static defaults = { + summary: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' + }; +} diff --git a/x-pack/plugins/watcher/public/models/action/logging_action.js b/x-pack/plugins/watcher/public/models/action/logging_action.js index 509ed71f7dbe3..9f84e59ada533 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -80,7 +80,6 @@ export class LoggingAction extends BaseAction { static selectMessage = i18n.translate('xpack.watcher.models.loggingAction.selectMessageText', { defaultMessage: 'Add a new item to the logs.', }); - static template = ''; static simulatePrompt = i18n.translate('xpack.watcher.models.loggingAction.simulateButtonLabel', { defaultMessage: 'Log a sample message now', }); diff --git a/x-pack/plugins/watcher/public/models/action/slack_action.js b/x-pack/plugins/watcher/public/models/action/slack_action.js index 69af275f6de48..ec3a29025af2e 100644 --- a/x-pack/plugins/watcher/public/models/action/slack_action.js +++ b/x-pack/plugins/watcher/public/models/action/slack_action.js @@ -134,7 +134,6 @@ export class SlackAction extends BaseAction { defaultMessage: 'Slack' }); static iconClass = 'logoSlack'; - static template = ''; static selectMessage = i18n.translate('xpack.watcher.models.slackAction.selectMessageText', { defaultMessage: 'Send a message to a Slack user or channel.' }); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/jira_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/jira_action_fields.tsx new file mode 100644 index 0000000000000..7e0de889013b3 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/jira_action_fields.tsx @@ -0,0 +1,125 @@ +/* + * 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 { EuiFieldText, EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ErrableFormRow } from '../../../components/form_errors'; + +interface Props { + action: {}; + editAction: (changedProperty: { key: string; value: any }) => void; +} + +export const JiraActionFields: React.FunctionComponent = ({ action, editAction }) => { + const { account, projectKey, issueType, summary } = action; + const errors = action.validateAction(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + + return ( + + + { + editAction({ key: 'account', value: e.target.value }); + }} + /> + + + { + editAction({ key: 'projectKey', value: e.target.value }); + }} + onBlur={() => { + if (!projectKey) { + editAction({ key: 'projectKey', value: '' }); + } + }} + /> + + + { + editAction({ key: 'issueType', value: e.target.value }); + }} + onBlur={() => { + if (!issueType) { + editAction({ key: 'issueType', value: '' }); + } + }} + /> + + + { + editAction({ key: 'summary', value: e.target.value }); + }} + onBlur={() => { + if (!summary) { + editAction({ key: 'summary', value: '' }); + } + }} + /> + + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx index af32b56c46591..b607ba41b2705 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx @@ -29,6 +29,7 @@ import { IndexActionFields } from './index_action_fields'; import { SlackActionFields } from './slack_action_fields'; import { EmailActionFields } from './email_action_fields'; import { PagerDutyActionFields } from './pagerduty_action_fields'; +import { JiraActionFields } from './jira_action_fields'; import { executeWatch } from '../../../lib/api'; const ActionFieldsComponent = { @@ -38,7 +39,7 @@ const ActionFieldsComponent = { [ACTION_TYPES.INDEX]: IndexActionFields, [ACTION_TYPES.WEBHOOK]: WebhookActionFields, [ACTION_TYPES.PAGERDUTY]: PagerDutyActionFields, - // TODO add support for additional action types + [ACTION_TYPES.JIRA]: JiraActionFields, }; export const WatchActionsAccordion: React.FunctionComponent = () => { diff --git a/x-pack/plugins/watcher/server/models/action/action.js b/x-pack/plugins/watcher/server/models/action/action.js index c1ee5aa5592d7..c25cbfd0256d8 100644 --- a/x-pack/plugins/watcher/server/models/action/action.js +++ b/x-pack/plugins/watcher/server/models/action/action.js @@ -14,6 +14,7 @@ import { SlackAction } from './slack_action'; import { IndexAction } from './index_action'; import { WebhookAction } from './webhook_action'; import { PagerDutyAction } from './pagerduty_action'; +import { JiraAction } from './jira_action'; import { UnknownAction } from './unknown_action'; import { i18n } from '@kbn/i18n'; @@ -24,8 +25,8 @@ set(ActionTypes, ACTION_TYPES.SLACK, SlackAction); set(ActionTypes, ACTION_TYPES.INDEX, IndexAction); set(ActionTypes, ACTION_TYPES.WEBHOOK, WebhookAction); set(ActionTypes, ACTION_TYPES.PAGERDUTY, PagerDutyAction); +set(ActionTypes, ACTION_TYPES.JIRA, JiraAction); set(ActionTypes, ACTION_TYPES.UNKNOWN, UnknownAction); -// TODO add additional actions export class Action { static getActionTypes = () => { diff --git a/x-pack/plugins/watcher/server/models/action/jira_action.js b/x-pack/plugins/watcher/server/models/action/jira_action.js new file mode 100644 index 0000000000000..ae150242dfa9d --- /dev/null +++ b/x-pack/plugins/watcher/server/models/action/jira_action.js @@ -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 { BaseAction } from './base_action'; +import { ACTION_TYPES, ERROR_CODES } from '../../../common/constants'; +import { i18n } from '@kbn/i18n'; + +export class JiraAction extends BaseAction { + constructor(props, errors) { + props.type = ACTION_TYPES.JIRA; + super(props, errors); + + this.account = props.account; + this.projectKey = props.projectKey; + this.issueType = props.issueType; + this.summary = props.summary; + } + + // To Kibana + get downstreamJson() { + const result = super.downstreamJson; + Object.assign(result, { + account: this.account, + projectKey: this.projectKey, + issueType: this.issueType, + summary: this.summary, + }); + + return result; + } + + // From Kibana + static fromDownstreamJson(json) { + const props = super.getPropsFromDownstreamJson(json); + const { errors } = this.validateJson(json); + + Object.assign(props, { + account: json.account, + projectKey: json.projectKey, + issueType: json.issueType, + summary: json.summary, + }); + + const action = new JiraAction(props, errors); + return { action, errors }; + } + + // To Elasticsearch + get upstreamJson() { + const result = super.upstreamJson; + + const optionalFields = {}; + + if (this.account) { + optionalFields.account = this.account; + } + + result[this.id] = { + jira: { + fields: { + project: { + key: this.projectKey, + }, + issuetype: { + name: this.issueType, + }, + summary: this.summary, + }, + ...optionalFields, + } + }; + + return result; + } + + // From Elasticsearch + static fromUpstreamJson(json) { + const props = super.getPropsFromUpstreamJson(json); + const { errors } = this.validateJson(json.actionJson); + + const optionalFields = {}; + + if (json.actionJson.account) { + optionalFields.account = json.actionJson.account; + } + + Object.assign(props, { + projectKey: json.actionJson.jira.fields.project.key, + issueType: json.actionJson.jira.fields.issuetype.name, + summary: json.actionJson.jira.fields.summary, + ...optionalFields, + }); + + const action = new JiraAction(props, errors); + return { action, errors }; + } + + static validateJson(json) { + const errors = []; + + if (!json.jira) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.jiraAction.actionJsonJiraPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonJira} property', + values: { + actionJsonJira: 'actionJson.jira' + } + }), + }); + + json.jira = {}; + } + + if (!json.jira.fields.project.key) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.jiraAction.actionJsonJiraProjectKeyPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonJiraProjectKey} property', + values: { + actionJsonJiraProjectKey: 'actionJson.jira.fields.project.key' + } + }), + }); + } + + if (!json.jira.fields.issuetype.name) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.jiraAction.actionJsonJiraIssueTypePropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonJiraIssueType} property', + values: { + actionJsonJiraIssueType: 'actionJson.jira.fields.issuetype.name' + } + }), + }); + } + + if (!json.jira.fields.summary) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.jiraAction.actionJsonJiraSummaryPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonJiraSummary} property', + values: { + actionJsonJiraSummary: 'actionJson.jira.fields.summary' + } + }), + }); + } + + return { errors: errors.length ? errors : null }; + } +} diff --git a/x-pack/plugins/watcher/server/models/action/pagerduty_action.js b/x-pack/plugins/watcher/server/models/action/pagerduty_action.js index 4d7cd79bbf878..6ded2e4f0422b 100644 --- a/x-pack/plugins/watcher/server/models/action/pagerduty_action.js +++ b/x-pack/plugins/watcher/server/models/action/pagerduty_action.js @@ -71,10 +71,10 @@ export class PagerDutyAction extends BaseAction { if (!json.pagerduty) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, - message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonPagerDutyPropertyMissingBadRequestMessage', { - defaultMessage: 'json argument must contain an {actionJsonLogging} property', + message: i18n.translate('xpack.watcher.models.pagerDutyAction.actionJsonPagerDutyPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonPagerDuty} property', values: { - actionJsonLogging: 'actionJson.pagerduty' + actionJsonPagerDuty: 'actionJson.pagerduty' } }), }); @@ -85,10 +85,10 @@ export class PagerDutyAction extends BaseAction { if (!json.pagerduty.description) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, - message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonPagerDutyDescriptionPropertyMissingBadRequestMessage', { - defaultMessage: 'json argument must contain an {actionJsonLoggingText} property', + message: i18n.translate('xpack.watcher.models.pagerDutyAction.actionJsonPagerDutyDescriptionPropertyMissingBadRequestMessage', { + defaultMessage: 'json argument must contain an {actionJsonPagerDutyText} property', values: { - actionJsonLoggingText: 'actionJson.pagerduty.description' + actionJsonPagerDutyText: 'actionJson.pagerduty.description' } }), }); From 7d85488dfa7b4553c5a702f3a1051f52990ba716 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Tue, 23 Apr 2019 11:48:15 -0400 Subject: [PATCH 07/13] cleanup --- .../translations/translations/zh-CN.json | 3 - .../watcher/common/types/action_types.ts | 84 +++++++++++++++++++ .../watcher/common/types/watch_types.ts | 12 --- .../public/models/action/email_action.js | 26 +----- .../public/models/action/index_action.js | 14 +--- .../public/models/action/jira_action.js | 17 +--- .../public/models/action/logging_action.js | 12 +-- .../public/models/action/pagerduty_action.js | 24 ++---- .../public/models/action/slack_action.js | 53 +----------- .../public/models/action/webhook_action.js | 18 +--- .../watcher/public/models/watch/base_watch.js | 38 --------- .../watcher/public/models/watch/json_watch.js | 2 +- .../public/models/watch/threshold_watch.js | 2 +- .../components/email_action_fields.tsx | 67 ++++++++------- .../components/index_action_fields.tsx | 14 +++- .../components/jira_action_fields.tsx | 14 +++- .../components/json_watch_edit_form.tsx | 56 +++++++++---- .../components/logging_action_fields.tsx | 14 +++- .../components/pagerduty_action_fields.tsx | 32 ++++--- .../components/slack_action_fields.tsx | 55 ++++++++---- .../threshold_actions_form_builder.tsx | 81 ------------------ .../threshold_watch_action_accordion.tsx | 25 +++--- .../threshold_watch_action_dropdown.tsx | 69 ++++++++------- .../components/threshold_watch_edit.tsx | 4 +- .../components/webhook_action_fields.tsx | 23 +++-- .../sections/watch_edit/watch_edit_actions.ts | 42 ++++++---- .../action_defaults_service.factory.js | 22 ----- .../action_defaults_service.js | 19 ----- .../action_defaults/actions/email_action.js | 33 -------- .../action_defaults/actions/logging_action.js | 30 ------- .../action_defaults/actions/slack_action.js | 30 ------- .../public/services/action_defaults/index.js | 8 -- .../services/action_defaults/registry.js | 11 --- .../watcher/public/services/settings/index.js | 7 -- .../settings/settings_service.factory.js | 14 ---- .../services/settings/settings_service.js | 23 ----- .../server/models/action/webhook_action.js | 2 +- 37 files changed, 371 insertions(+), 629 deletions(-) create mode 100644 x-pack/plugins/watcher/common/types/action_types.ts delete mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_actions_form_builder.tsx delete mode 100644 x-pack/plugins/watcher/public/services/action_defaults/action_defaults_service.factory.js delete mode 100644 x-pack/plugins/watcher/public/services/action_defaults/action_defaults_service.js delete mode 100644 x-pack/plugins/watcher/public/services/action_defaults/actions/email_action.js delete mode 100644 x-pack/plugins/watcher/public/services/action_defaults/actions/logging_action.js delete mode 100644 x-pack/plugins/watcher/public/services/action_defaults/actions/slack_action.js delete mode 100644 x-pack/plugins/watcher/public/services/action_defaults/index.js delete mode 100644 x-pack/plugins/watcher/public/services/action_defaults/registry.js delete mode 100644 x-pack/plugins/watcher/public/services/settings/index.js delete mode 100644 x-pack/plugins/watcher/public/services/settings/settings_service.factory.js delete mode 100644 x-pack/plugins/watcher/public/services/settings/settings_service.js diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 30900da2dd457..3df254629f043 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7847,7 +7847,6 @@ "xpack.watcher.models.baseWatch.watchStatusJsonPropertyMissingBadRequestMessage": "json 参数必须包含 {watchStatusJson} 属性", "xpack.watcher.models.emailAction.actionJsonEmailPropertyMissingBadRequestMessage": "json 参数必须包含 {actionJsonEmail} 属性", "xpack.watcher.models.emailAction.actionJsonEmailToPropertyMissingBadRequestMessage": "json 参数必须包含 {actionJsonEmailTo} 属性", - "xpack.watcher.models.emailAction.description": "“{subject}” 将发送至 {toList}", "xpack.watcher.models.emailAction.selectMessageText": "从您的服务器发送电子邮件。", "xpack.watcher.models.emailAction.simulateButtonLabel": "立即试发电子邮件", "xpack.watcher.models.emailAction.simulateFailMessage": "无法将电子邮件发至 {toList}。", @@ -7858,7 +7857,6 @@ "xpack.watcher.models.jsonWatch.typeName": "高级监视", "xpack.watcher.models.loggingAction.actionJsonLoggingPropertyMissingBadRequestMessage": "json 参数必须包含 {actionJsonLogging} 属性", "xpack.watcher.models.loggingAction.actionJsonLoggingTextPropertyMissingBadRequestMessage": "json 参数必须包含 {actionJsonLoggingText} 属性", - "xpack.watcher.models.loggingAction.description": "记录消息“{text}”", "xpack.watcher.models.loggingAction.selectMessageText": "向日志添加新项。", "xpack.watcher.models.loggingAction.simulateButtonLabel": "立即记录示例消息", "xpack.watcher.models.loggingAction.simulateFailMessage": "无法记录示例消息。", @@ -7870,7 +7868,6 @@ "xpack.watcher.models.monitoringWatch.upstreamJsonCalledBadRequestMessage": "为监测监视而调用的 {upstreamJson}", "xpack.watcher.models.slackAction.actionJsonSlackMessagePropertyMissingBadRequestMessage": "json 参数必须包含 {actionJsonSlackMessage} 属性", "xpack.watcher.models.slackAction.actionJsonSlackPropertyMissingBadRequestMessage": "json 参数必须包含 {actionJsonSlack} 属性", - "xpack.watcher.models.slackAction.description": "Slack 消息将发至 {toList}", "xpack.watcher.models.slackAction.selectMessageText": "向 slack 用户或渠道发送消息。", "xpack.watcher.models.slackAction.simulateButtonLabel": "立即发送示例消息", "xpack.watcher.models.slackAction.simulateFailMessage": "无法将示例 Slack 消息发至 {toList}。", diff --git a/x-pack/plugins/watcher/common/types/action_types.ts b/x-pack/plugins/watcher/common/types/action_types.ts new file mode 100644 index 0000000000000..be5c2fc1e8e66 --- /dev/null +++ b/x-pack/plugins/watcher/common/types/action_types.ts @@ -0,0 +1,84 @@ +/* + * 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. + */ + +type EmailActionType = 'email'; +type LoggingActionType = 'logging'; +type WebhookActionType = 'webhook'; +type IndexActionType = 'index'; +type SlackActionType = 'slack'; +type JiraActionType = 'jira'; +type PagerDutyActionType = 'pagerduty'; + +export interface BaseAction { + id: string; + typeName: string; + simulateMessage: string; + simulateFailMessage: string; + simulatePrompt: string; + selectMessage: string; + validate: () => { [key: string]: string[] }; + isEnabled: boolean; +} + +export interface EmailAction extends BaseAction { + type: EmailActionType; + iconClass: 'email'; + to: []; + subject?: string; + body: string; +} + +export interface LoggingAction extends BaseAction { + type: LoggingActionType; + iconClass: 'loggingApp'; + text: string; +} + +export interface IndexAction extends BaseAction { + type: IndexActionType; + iconClass: 'indexOpen'; + index: string; +} + +export interface PagerDutyAction extends BaseAction { + type: PagerDutyActionType; + iconClass: 'apps'; + description: string; +} + +export interface WebhookAction extends BaseAction { + type: WebhookActionType; + iconClass: 'logoWebhook'; + method?: 'head' | 'get' | 'post' | 'put' | 'delete'; + host: string; + port: number; + path?: string; + body: string; +} + +export interface SlackAction extends BaseAction { + type: SlackActionType; + iconClass: 'logoSlack'; + text: string; + to: string[]; +} + +export interface JiraAction extends BaseAction { + type: JiraActionType; + iconClass: 'apps'; + account?: string; + projectKey: string; + issueType: string; + summary: string; +} + +export type ActionType = + | EmailAction + | LoggingAction + | IndexAction + | SlackAction + | JiraAction + | PagerDutyAction; diff --git a/x-pack/plugins/watcher/common/types/watch_types.ts b/x-pack/plugins/watcher/common/types/watch_types.ts index c463cd8c4812d..0850911cdab6c 100644 --- a/x-pack/plugins/watcher/common/types/watch_types.ts +++ b/x-pack/plugins/watcher/common/types/watch_types.ts @@ -55,15 +55,3 @@ export interface BaseWatch { }; }; } - -export interface WatchAction { - id: string; - type: 'email' | 'webhook' | 'index' | 'logging' | 'slack' | 'jira' | 'pagerduty'; - typeName: string; - iconClass: 'loggingApp' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email' | 'indexOpen'; - simulateMessage: string; - simulateFailMessage: string; - simulatePrompt: string; - selectMessage: string; - text?: string; -} diff --git a/x-pack/plugins/watcher/public/models/action/email_action.js b/x-pack/plugins/watcher/public/models/action/email_action.js index 910ce599833af..e6d8b273bb149 100644 --- a/x-pack/plugins/watcher/public/models/action/email_action.js +++ b/x-pack/plugins/watcher/public/models/action/email_action.js @@ -18,10 +18,9 @@ export class EmailAction extends BaseAction { this.body = get(props, 'body'); } - validateAction() { + validate() { const errors = { to: [], - subject: [], body: [], }; if (!this.to || this.to.length === 0) { @@ -31,17 +30,10 @@ export class EmailAction extends BaseAction { }) ); } - if (!this.subject) { - errors.subject.push( - i18n.translate('xpack.watcher.watchActions.email.emailSubjectIsRequiredValidationMessage', { - defaultMessage: 'Subject is required.', - }) - ); - } if (!this.body) { errors.body.push( i18n.translate('xpack.watcher.watchActions.email.emailBodyIsRequiredValidationMessage', { - defaultMessage: 'Body is required.', + defaultMessage: 'Email body is required.', }) ); } @@ -56,25 +48,13 @@ export class EmailAction extends BaseAction { subject: this.subject, body: this.body, email: { - to: this.to.length ? this.to : undefined, + to: this.to && this.to.length ? this.to : undefined, } }); return result; } - get description() { - const toList = this.to.join(', '); - const subject = this.subject || ''; - return i18n.translate('xpack.watcher.models.emailAction.description', { - defaultMessage: '"{subject}" will be sent to {toList}', - values: { - subject, - toList - } - }); - } - get simulateMessage() { const toList = this.to.join(', '); return i18n.translate('xpack.watcher.models.emailAction.simulateMessage', { diff --git a/x-pack/plugins/watcher/public/models/action/index_action.js b/x-pack/plugins/watcher/public/models/action/index_action.js index 6f00588cb9e00..a233330f882b1 100644 --- a/x-pack/plugins/watcher/public/models/action/index_action.js +++ b/x-pack/plugins/watcher/public/models/action/index_action.js @@ -16,14 +16,14 @@ export class IndexAction extends BaseAction { this.index = get(props, 'index'); } - validateAction() { + validate() { const errors = { index: [], }; if (!this.index) { errors.index.push( i18n.translate('xpack.watcher.watchActions.index.indexIsRequiredValidationMessage', { - defaultMessage: 'Index is required.', + defaultMessage: 'Index name is required.', }) ); } @@ -43,16 +43,6 @@ export class IndexAction extends BaseAction { return result; } - get description() { - const index = this.index || ''; - return i18n.translate('xpack.watcher.models.indexAction.description', { - defaultMessage: 'The {index} will be indexed', - values: { - index, - } - }); - } - get simulateMessage() { const index = this.index || ''; return i18n.translate('xpack.watcher.models.indexAction.simulateMessage', { diff --git a/x-pack/plugins/watcher/public/models/action/jira_action.js b/x-pack/plugins/watcher/public/models/action/jira_action.js index f977f42eb9958..b4cd36544ec96 100644 --- a/x-pack/plugins/watcher/public/models/action/jira_action.js +++ b/x-pack/plugins/watcher/public/models/action/jira_action.js @@ -17,7 +17,7 @@ export class JiraAction extends BaseAction { this.summary = get(props, 'summary'); } - validateAction() { + validate() { const errors = { projectKey: [], issueType: [], @@ -26,21 +26,21 @@ export class JiraAction extends BaseAction { if (!this.projectKey) { errors.projectKey.push( i18n.translate('xpack.watcher.watchActions.jira.projectKeyIsRequiredValidationMessage', { - defaultMessage: 'Project key is required.', + defaultMessage: 'Jira project key is required.', }) ); } if (!this.issueType) { errors.issueType.push( i18n.translate('xpack.watcher.watchActions.jira.issueTypeNameIsRequiredValidationMessage', { - defaultMessage: 'Issue type is required.', + defaultMessage: 'Jira issue type is required.', }) ); } if (!this.summary) { errors.summary.push( i18n.translate('xpack.watcher.watchActions.jira.summaryIsRequiredValidationMessage', { - defaultMessage: 'Summary is required.', + defaultMessage: 'Jira summary is required.', }) ); } @@ -72,15 +72,6 @@ export class JiraAction extends BaseAction { return result; } - get description() { - return i18n.translate('xpack.watcher.models.jiraAction.description', { - defaultMessage: '{issueType} will be created in Jira', - values: { - issueType: this.issueType, - } - }); - } - get simulateMessage() { return i18n.translate('xpack.watcher.models.jiraAction.simulateMessage', { defaultMessage: 'Jira issue has been created.', diff --git a/x-pack/plugins/watcher/public/models/action/logging_action.js b/x-pack/plugins/watcher/public/models/action/logging_action.js index 9f84e59ada533..485d351443220 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -15,7 +15,7 @@ export class LoggingAction extends BaseAction { this.text = get(props, 'text'); } - validateAction() { + validate() { const errors = { text: [], }; @@ -43,16 +43,6 @@ export class LoggingAction extends BaseAction { return result; } - get description() { - const text = this.text || ''; - return i18n.translate('xpack.watcher.models.loggingAction.description', { - defaultMessage: 'Log message \'{text}\'', - values: { - text - } - }); - } - get simulateMessage() { return i18n.translate('xpack.watcher.models.loggingAction.simulateMessage', { defaultMessage: 'Sample message logged', diff --git a/x-pack/plugins/watcher/public/models/action/pagerduty_action.js b/x-pack/plugins/watcher/public/models/action/pagerduty_action.js index aa4ff44f49fd0..ae2869f0301a6 100644 --- a/x-pack/plugins/watcher/public/models/action/pagerduty_action.js +++ b/x-pack/plugins/watcher/public/models/action/pagerduty_action.js @@ -12,18 +12,18 @@ import { i18n } from '@kbn/i18n'; export class PagerDutyAction extends BaseAction { constructor(props = {}) { super(props); - this.message = get(props, 'message'); + this.description = get(props, 'description'); } - validateAction() { + validate() { const errors = { - message: [], + description: [], }; - if (!this.message) { - errors.message.push( + if (!this.description) { + errors.description.push( i18n.translate('xpack.watcher.watchActions.pagerduty.descriptionIsRequiredValidationMessage', { - defaultMessage: 'Description is required.', + defaultMessage: 'PagerDuty description is required.', }) ); } @@ -34,21 +34,15 @@ export class PagerDutyAction extends BaseAction { const result = super.upstreamJson; Object.assign(result, { - description: this.message, + description: this.description, pagerduty: { - description: this.message, + description: this.description, } }); return result; } - get description() { - return i18n.translate('xpack.watcher.models.pagerDutyAction.description', { - defaultMessage: 'A message will be sent to PagerDuty', - }); - } - get simulateMessage() { return i18n.translate('xpack.watcher.models.pagerDutyAction.simulateMessage', { defaultMessage: 'PagerDuty event has been sent.', @@ -76,7 +70,7 @@ export class PagerDutyAction extends BaseAction { defaultMessage: 'Test fire a PagerDuty event' }); static defaults = { - message: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' + description: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' }; } diff --git a/x-pack/plugins/watcher/public/models/action/slack_action.js b/x-pack/plugins/watcher/public/models/action/slack_action.js index ec3a29025af2e..fc12eb91afec0 100644 --- a/x-pack/plugins/watcher/public/models/action/slack_action.js +++ b/x-pack/plugins/watcher/public/models/action/slack_action.js @@ -4,13 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { get, isArray } from 'lodash'; import { BaseAction } from './base_action'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCode, EuiLink } from '@elastic/eui'; -import { documentationLinks } from '../../lib/documentation_links'; export class SlackAction extends BaseAction { constructor(props = {}) { @@ -20,59 +16,28 @@ export class SlackAction extends BaseAction { this.to = isArray(toArray) ? toArray : toArray && [ toArray ]; this.text = props.text; } - validateAction() { + validate() { const errors = { to: [], text: [] }; - if (!this.to || this.to.length === 0) { + if (!this.to || !this.to.length) { errors.to.push( i18n.translate('xpack.watcher.watchActions.slack.slackRecipientIsRequiredValidationMessage', { - defaultMessage: 'Recipient is required.', + defaultMessage: 'Slack recipient is required.', }) ); } if (!this.text) { errors.text.push( i18n.translate('xpack.watcher.watchActions.slack.slackMessageIsRequiredValidationMessage', { - defaultMessage: 'Message is required.', + defaultMessage: 'Slack message is required.', }) ); } return errors; } - validate() { - const errors = []; - - if (!this.to || !this.to.length) { - const message = ( - message_defaults, - link: ( - - - - ) - }} - /> - ); - errors.push({ - message - }); - } - - return { errors: errors.length ? errors : null }; - } - get upstreamJson() { const result = super.upstreamJson; const message = this.text || this.to.length @@ -92,16 +57,6 @@ export class SlackAction extends BaseAction { return result; } - get description() { - const toList = this.to.join(', '); - return i18n.translate('xpack.watcher.models.slackAction.description', { - defaultMessage: 'Slack message will be sent to {toList}', - values: { - toList - } - }); - } - get simulateMessage() { const toList = this.to.join(', '); return i18n.translate('xpack.watcher.models.slackAction.simulateMessage', { diff --git a/x-pack/plugins/watcher/public/models/action/webhook_action.js b/x-pack/plugins/watcher/public/models/action/webhook_action.js index a960b57b8fe10..b12a1c350bcad 100644 --- a/x-pack/plugins/watcher/public/models/action/webhook_action.js +++ b/x-pack/plugins/watcher/public/models/action/webhook_action.js @@ -21,7 +21,7 @@ export class WebhookAction extends BaseAction { this.fullPath = `${this.host}${this.port}${this.path}`; } - validateAction() { + validate() { const errors = { host: [], port: [], @@ -31,18 +31,18 @@ export class WebhookAction extends BaseAction { if (!this.host) { errors.host.push( i18n.translate('xpack.watcher.watchActions.webhook.hostIsRequiredValidationMessage', { - defaultMessage: 'Host is required.', + defaultMessage: 'Webhook host is required.', }) ); } if (!this.port) { errors.port.push( i18n.translate('xpack.watcher.watchActions.webhook.portIsRequiredValidationMessage', { - defaultMessage: 'Port is required.', + defaultMessage: 'Webhook port is required.', }) ); } - if (this.body || this.body !== '') { + if (typeof this.body === 'string' && this.body !== '') { try { const parsedJson = JSON.parse(this.body); if (parsedJson && typeof parsedJson !== 'object') { @@ -77,16 +77,6 @@ export class WebhookAction extends BaseAction { return result; } - get description() { - return i18n.translate('xpack.watcher.models.webhookAction.description', { - defaultMessage: 'Webhook will trigger a {method} request on {fullPath}', - values: { - method: this.method, - fullPath: this.fullPath - } - }); - } - get simulateMessage() { return i18n.translate('xpack.watcher.models.webhookAction.simulateMessage', { defaultMessage: 'Sample request sent to {fullPath}', diff --git a/x-pack/plugins/watcher/public/models/watch/base_watch.js b/x-pack/plugins/watcher/public/models/watch/base_watch.js index 94d8079387101..a58414abd006a 100644 --- a/x-pack/plugins/watcher/public/models/watch/base_watch.js +++ b/x-pack/plugins/watcher/public/models/watch/base_watch.js @@ -135,44 +135,6 @@ export class BaseWatch { return isEqual(cleanWatch, cleanOtherWatch); } - /** - * Client validation of the Watch. - * Currently we are *only* validating the Watch "Actions" - */ - validate() { - - // Get the errors from each watch action - const actionsErrors = this.actions.reduce((actionsErrors, action) => { - if (action.validate) { - const { errors } = action.validate(); - if (!errors) { - return actionsErrors; - } - return [...actionsErrors, ...errors]; - } - return actionsErrors; - }, []); - - if (!actionsErrors.length) { - return { warning: null }; - } - - // Concatenate their message - const warningMessage = actionsErrors.reduce((message, error) => ( - !!message - ? `${message}, ${error.message}` - : error.message - ), ''); - - // We are not doing any *blocking* validation in the client, - // so we return the errors as a _warning_ - return { - warning: { - message: warningMessage, - } - }; - } - static typeName = i18n.translate('xpack.watcher.models.baseWatch.typeName', { defaultMessage: 'Watch', }); diff --git a/x-pack/plugins/watcher/public/models/watch/json_watch.js b/x-pack/plugins/watcher/public/models/watch/json_watch.js index e051bc63412e4..64527c8cd54dd 100644 --- a/x-pack/plugins/watcher/public/models/watch/json_watch.js +++ b/x-pack/plugins/watcher/public/models/watch/json_watch.js @@ -23,7 +23,7 @@ export class JsonWatch extends BaseWatch { } validate() { - const validationResult = super.validate(); + const validationResult = {}; const idRegex = /^[A-Za-z0-9\-\_]+$/; const errors = { id: [], diff --git a/x-pack/plugins/watcher/public/models/watch/threshold_watch.js b/x-pack/plugins/watcher/public/models/watch/threshold_watch.js index ad6bf88294ab0..15c7cab94c5c8 100644 --- a/x-pack/plugins/watcher/public/models/watch/threshold_watch.js +++ b/x-pack/plugins/watcher/public/models/watch/threshold_watch.js @@ -93,7 +93,7 @@ export class ThresholdWatch extends BaseWatch { return `${staticPart} ${dynamicPartText}`; } validate() { - const validationResult = super.validate(); + const validationResult = {}; const errors = { name: [], index: [], diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/email_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/email_action_fields.tsx index 476cb3ad232c3..2cd3e9125eee3 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/email_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/email_action_fields.tsx @@ -3,21 +3,39 @@ * 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 React, { Fragment, useState, useEffect } from 'react'; -import { EuiFieldText, EuiTextArea } from '@elastic/eui'; +import { EuiComboBox, EuiFieldText, EuiFormRow, EuiTextArea } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../components/form_errors'; +import { EmailAction } from '../../../../common/types/action_types'; interface Props { - action: {}; + action: EmailAction; editAction: (changedProperty: { key: string; value: any }) => void; + errors: { [key: string]: string[] }; + hasErrors: boolean; } -export const EmailActionFields: React.FunctionComponent = ({ action, editAction }) => { +export const EmailActionFields: React.FunctionComponent = ({ + action, + editAction, + errors, + hasErrors, +}) => { const { to, subject, body } = action; - const errors = action.validateAction(); - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + const [toOptions, setToOptions] = useState>([]); + + useEffect(() => { + if (to && to.length > 0) { + const toOptionsList = to.map(toItem => { + return { + label: toItem, + }; + }); + setToOptions(toOptionsList); + } + }, []); return ( @@ -34,28 +52,26 @@ export const EmailActionFields: React.FunctionComponent = ({ action, edit } )} > - { - const toValues = e.target.value; - const toArray = (toValues || '').split(',').map(toVal => toVal.trim()); - editAction({ key: 'to', value: toArray.join(', ') }); + selectedOptions={toOptions} + onCreateOption={(searchValue: string) => { + const newOptions = [...toOptions, { label: searchValue }]; + setToOptions(newOptions); + editAction({ key: 'to', value: newOptions.map(newOption => newOption.label) }); }} - onBlur={() => { - if (!to) { - editAction({ key: 'to', value: [] }); - } + onChange={(selectedOptions: Array<{ label: string }>) => { + setToOptions(selectedOptions); + editAction({ + key: 'to', + value: selectedOptions.map(selectedOption => selectedOption.label), + }); }} /> - = ({ action, edit onChange={e => { editAction({ key: 'subject', value: e.target.value }); }} - onBlur={() => { - if (!subject) { - editAction({ key: 'subject', value: '' }); - } - }} /> - + void; + errors: { [key: string]: string[] }; + hasErrors: boolean; } -export const IndexActionFields: React.FunctionComponent = ({ action, editAction }) => { +export const IndexActionFields: React.FunctionComponent = ({ + action, + editAction, + errors, + hasErrors, +}) => { const { index } = action; - const errors = action.validateAction(); - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); return ( void; + errors: { [key: string]: string[] }; + hasErrors: boolean; } -export const JiraActionFields: React.FunctionComponent = ({ action, editAction }) => { +export const JiraActionFields: React.FunctionComponent = ({ + action, + editAction, + errors, + hasErrors, +}) => { const { account, projectKey, issueType, summary } = action; - const errors = action.validateAction(); - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); return ( diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx index f8fa010628ca5..eb8215261ba22 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx @@ -27,11 +27,25 @@ import { LicenseServiceContext } from '../../../license_service_context'; export const JsonWatchEditForm = () => { const { watch, setWatchProperty } = useContext(WatchContext); - // hooks - const [modal, setModal] = useState<{ title: string; message: string } | null>(null); + const licenseService = useContext(LicenseServiceContext); const { errors } = watch.validate(); const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); - const licenseService = useContext(LicenseServiceContext); + const [validationResult, setValidationResult] = useState<{ + type: string; + title: string; + message: string; + } | null>(null); + const hasActionErrors = !!validationResult && validationResult.type === 'error'; + const invalidActionMessage = i18n.translate( + 'xpack.watcher.sections.watchEdit.json.form.actionValidationErrorMessage', + { + defaultMessage: 'Invalid watch actions', + } + ); + const jsonErrors = { + ...errors, + json: hasActionErrors ? [...errors.json, invalidActionMessage] : [...errors.json], + }; if (errors.json.length === 0) { setWatchProperty('watch', JSON.parse(watch.watchString)); @@ -39,16 +53,21 @@ export const JsonWatchEditForm = () => { return ( - { - if (isConfirmed) { - saveWatch(watch, licenseService); - } - setModal(null); - }} - /> - + {validationResult && validationResult.type === 'warning' && ( + { + if (isConfirmed) { + saveWatch(watch, licenseService); + } + setValidationResult(null); + }} + /> + )} + { } errorKey="json" - isShowingErrors={hasErrors} + isShowingErrors={hasErrors || hasActionErrors} fullWidth - errors={errors} + errors={jsonErrors} > { )} value={watch.watchString} onChange={(json: string) => { + if (validationResult && validationResult.type === 'error') { + setValidationResult(null); + } setWatchProperty('watchString', json); }} /> @@ -144,8 +166,8 @@ export const JsonWatchEditForm = () => { isDisabled={hasErrors} onClick={async () => { const savedWatch = await onWatchSave(watch, licenseService); - if (savedWatch && savedWatch.error) { - return setModal(savedWatch.error); + if (savedWatch && savedWatch.validationError) { + return setValidationResult(savedWatch.validationError); } }} > diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx index 0ab979c757bdd..e594f6a41aad1 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/logging_action_fields.tsx @@ -7,16 +7,22 @@ import React, { Fragment } from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../components/form_errors'; +import { LoggingAction } from '../../../../common/types/action_types'; interface Props { - action: { text?: string }; + action: LoggingAction; editAction: (changedProperty: { key: string; value: string }) => void; + errors: { [key: string]: string[] }; + hasErrors: boolean; } -export const LoggingActionFields: React.FunctionComponent = ({ action, editAction }) => { +export const LoggingActionFields: React.FunctionComponent = ({ + action, + editAction, + errors, + hasErrors, +}) => { const { text } = action; - const errors = action.validateAction(); - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); return ( void; + errors: { [key: string]: string[] }; + hasErrors: boolean; } -export const PagerDutyActionFields: React.FunctionComponent = ({ action, editAction }) => { - const { message } = action; - const errors = action.validateAction(); - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); +export const PagerDutyActionFields: React.FunctionComponent = ({ + errors, + hasErrors, + action, + editAction, +}) => { + const { description } = action; return ( = ({ action, > ) => { - editAction({ key: 'message', value: e.target.value }); + editAction({ key: 'description', value: e.target.value }); }} onBlur={() => { - if (!message) { - editAction({ key: 'message', value: '' }); + if (!description) { + editAction({ key: 'description', value: '' }); } }} /> diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/slack_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/slack_action_fields.tsx index 5f10b99a9625c..f95b500781540 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/slack_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/slack_action_fields.tsx @@ -3,20 +3,39 @@ * 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 { EuiFieldText, EuiTextArea } from '@elastic/eui'; +import React, { Fragment, useState, useEffect } from 'react'; +import { EuiComboBox, EuiTextArea } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../components/form_errors'; +import { SlackAction } from '../../../../common/types/action_types'; interface Props { - action: {}; + action: SlackAction; editAction: (changedProperty: { key: string; value: any }) => void; + errors: { [key: string]: string[] }; + hasErrors: boolean; } -export const SlackActionFields: React.FunctionComponent = ({ action, editAction }) => { +export const SlackActionFields: React.FunctionComponent = ({ + action, + editAction, + errors, + hasErrors, +}) => { const { text, to } = action; - const errors = action.validateAction(); - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + const [toOptions, setToOptions] = useState>([]); + + useEffect(() => { + if (to && to.length > 0) { + const toOptionsList = to.map(toItem => { + return { + label: toItem, + }; + }); + setToOptions(toOptionsList); + } + }, []); + return ( = ({ action, edit } )} > - { - const toValues = e.target.value; - const toArray = (toValues || '').split(',').map(toVal => toVal.trim()); - editAction({ key: 'to', value: toArray.join(', ') }); + selectedOptions={toOptions} + onCreateOption={(searchValue: string) => { + const newOptions = [...toOptions, { label: searchValue }]; + setToOptions(newOptions); + editAction({ key: 'to', value: newOptions.map(newOption => newOption.label) }); }} - onBlur={() => { - if (!to) { - editAction({ key: 'to', value: [] }); - } + onChange={(selectedOptions: Array<{ label: string }>) => { + setToOptions(selectedOptions); + editAction({ + key: 'to', + value: selectedOptions.map(selectedOption => selectedOption.label), + }); }} /> diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_actions_form_builder.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_actions_form_builder.tsx deleted file mode 100644 index f0209da3352c0..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_actions_form_builder.tsx +++ /dev/null @@ -1,81 +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 React, { Fragment } from 'react'; -import { EuiFieldNumber, EuiFieldText, EuiTextArea } from '@elastic/eui'; -import { ErrableFormRow } from '../../../components/form_errors'; - -interface Props { - action: any; // TODO fix - // fields: Array<{ - // required: boolean; - // fieldType: 'text' | 'textarea' | 'array' | 'number'; - // fieldName: string; - // fieldLabel: string; - // }>; - editAction: (changedProperty: { key: string; value: string }) => void; -} - -const FORM_CONTROLS = { - text: EuiFieldText, - textarea: EuiTextArea, - array: EuiFieldText, // TODO replace with combo box? - number: EuiFieldNumber, -}; - -export const ThresholdActionsFormBuilder: React.FunctionComponent = ({ - action, - editAction, -}) => { - const errors = action.validateAction(); - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); - return ( - - {action.fields.map((field, index) => { - const { required, fieldName, fieldLabel, fieldType } = field; - const fieldValue = action[fieldName]; - if (required) { - const FormControl = FORM_CONTROLS[fieldType]; - const currentValue = Array.isArray(fieldValue) ? fieldValue.join(', ') : fieldValue; // TODO cleanup/rename? - return ( - - ) => { - let newValue = e.target.value; - if (fieldType === 'array') { - const toArray = (newValue || '').split(',').map(val => val.trim()); - newValue = toArray.join(', '); - } - if (fieldType === 'number') { - newValue = parseInt(newValue, 10); - } - editAction({ key: fieldName, value: newValue }); - }} - onBlur={() => { - if (!action[fieldName]) { - editAction({ key: fieldName, value: fieldType === 'array' ? [] : '' }); - } - }} - /> - - ); - } - // TODO implement non-required form row - return null; - })} - - ); -}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx index b607ba41b2705..a02886a6b74d1 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_accordion.tsx @@ -20,7 +20,7 @@ import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_d import { Action } from 'plugins/watcher/models/action'; import { toastNotifications } from 'ui/notify'; import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; -import { WatchAction } from '../../../../common/types/watch_types'; +import { ActionType } from '../../../../common/types/action_types'; import { ACTION_TYPES, ACTION_MODES } from '../../../../common/constants'; import { WatchContext } from './watch_context'; import { WebhookActionFields } from './webhook_action_fields'; @@ -83,8 +83,11 @@ export const WatchActionsAccordion: React.FunctionComponent = () => { ); if (actions && actions.length >= 1) { - return actions.map((action: WatchAction) => { + return actions.map((action: any) => { const FieldsComponent = ActionFieldsComponent[action.type]; + const errors = action.validate(); + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + return ( { { const updatedActions = actions.filter( - (actionItem: WatchAction) => actionItem.id !== action.id + (actionItem: ActionType) => actionItem.id !== action.id ); setWatchProperty('actions', updatedActions); }} @@ -108,13 +111,15 @@ export const WatchActionsAccordion: React.FunctionComponent = () => { { - const updatedActions = actions.map((actionItem: WatchAction) => { + errors={errors} + hasErrors={hasErrors} + editAction={(changedProperty: { key: string; value: string }) => { + const updatedActions = actions.map((actionItem: ActionType) => { if (actionItem.id === action.id) { const ActionTypes = Action.getActionTypes(); - const ActionType = ActionTypes[action.type]; + const ActionTypeModel = ActionTypes[action.type]; const { key, value } = changedProperty; - return new ActionType({ ...action, [key]: value }); + return new ActionTypeModel({ ...action, [key]: value }); } return actionItem; }); @@ -124,9 +129,9 @@ export const WatchActionsAccordion: React.FunctionComponent = () => { { - const actionModes = watch.actions.reduce((acc: any, actionItem: WatchAction) => { + const actionModes = watch.actions.reduce((acc: any, actionItem: ActionType) => { acc[action.id] = action === actionItem ? ACTION_MODES.FORCE_EXECUTE : ACTION_MODES.SKIP; return acc; @@ -143,7 +148,7 @@ export const WatchActionsAccordion: React.FunctionComponent = () => { ); const actionStatuses = executeResults.watchStatus.actionStatuses; const actionStatus = actionStatuses.find( - (actionItem: WatchAction) => actionItem.id === action.id + (actionItem: ActionType) => actionItem.id === action.id ); if (actionStatus.lastExecutionSuccessful === false) { diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx index 3a06b979083d7..e6727ce741d25 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx @@ -3,35 +3,38 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; +import { + EuiSpacer, + EuiSuperSelect, + EuiText, + EuiFlexItem, + EuiIcon, + EuiFlexGroup, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { Fragment, useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Action } from 'plugins/watcher/models/action'; -import { WatchAction } from '../../../../common/types/watch_types'; +import { ActionType } from '../../../../common/types/action_types'; import { fetchSettings } from '../../../lib/api'; import { WatchContext } from './watch_context'; -interface ActionOption extends WatchAction { - isEnabled: boolean; -} +const EMPTY_FIRST_OPTION_VALUE = 'empty-first-option'; -export const WatchActionsDropdown: React.FunctionComponent = () => { - const EMPTY_FIRST_OPTION_VALUE = 'empty-first-option'; - - const disabledMessage = i18n.translate( - 'xpack.watcher.sections.watchEdit.actions.disabledOptionLabel', - { - defaultMessage: 'Disabled. Configure elasticsearch.yml.', - } - ); +const disabledMessage = i18n.translate( + 'xpack.watcher.sections.watchEdit.actions.disabledOptionLabel', + { + defaultMessage: 'Disabled. Configure elasticsearch.yml.', + } +); - const firstActionOption = { - inputDisplay: i18n.translate('xpack.watcher.sections.watchEdit.actions.emptyFirstOptionLabel', { - defaultMessage: 'Add an action', - }), - value: EMPTY_FIRST_OPTION_VALUE, - }; +const firstActionOption = { + inputDisplay: i18n.translate('xpack.watcher.sections.watchEdit.actions.emptyFirstOptionLabel', { + defaultMessage: 'Add an action', + }), + value: EMPTY_FIRST_OPTION_VALUE, +}; +export const WatchActionsDropdown: React.FunctionComponent = () => { const allActionTypes = Action.getActionTypes(); const { addAction } = useContext(WatchContext); const [actions, setActions] = useState(null); @@ -44,8 +47,7 @@ export const WatchActionsDropdown: React.FunctionComponent = () => { typeName, iconClass, selectMessage, - isEnabled: true, // TODO temp - // isEnabled: settings.actionTypes[actionKey].enabled, + isEnabled: settings.actionTypes[actionKey].enabled, }; }); setActions(newActions); @@ -55,20 +57,25 @@ export const WatchActionsDropdown: React.FunctionComponent = () => { }, []); const actionOptions = actions && - actions.map((action: ActionOption) => { + actions.map((action: ActionType) => { const description = action.isEnabled ? action.selectMessage : disabledMessage; return { value: action.type, inputDisplay: action.typeName, disabled: !action.isEnabled, dropdownDisplay: ( - - {action.typeName} - - -

{description}

-
-
+ + + + + + {action.typeName} + + +

{description}

+
+
+
), }; }); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx index b81dd3f20e3bf..be10c4d4d88d0 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx @@ -764,8 +764,8 @@ const ThresholdWatchEditUi = ({ intl, pageTitle }: { intl: InjectedIntl; pageTit isDisabled={hasErrors} onClick={async () => { const savedWatch = await onWatchSave(watch, licenseService); - if (savedWatch && savedWatch.error) { - return setModal(savedWatch.error); + if (savedWatch && savedWatch.validationError) { + return setModal(savedWatch.validationError); } }} > diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx index 602fcc2e2d382..3f7b76e2819f9 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx @@ -5,26 +5,25 @@ */ import React, { Fragment } from 'react'; -import { - EuiCodeEditor, - EuiFieldNumber, - EuiFieldText, - EuiFormRow, - EuiSelect, - EuiTextArea, -} from '@elastic/eui'; +import { EuiCodeEditor, EuiFieldNumber, EuiFieldText, EuiFormRow, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../components/form_errors'; +import { WebhookAction } from '../../../../common/types/action_types'; interface Props { - action: {}; + action: WebhookAction; editAction: (changedProperty: { key: string; value: any }) => void; + errors: { [key: string]: string[] }; + hasErrors: boolean; } -export const WebhookActionFields: React.FunctionComponent = ({ action, editAction }) => { +export const WebhookActionFields: React.FunctionComponent = ({ + action, + editAction, + errors, + hasErrors, +}) => { const { method, host, port, path, body } = action; - const errors = action.validateAction(); - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); return ( diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts b/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts index 4f086fed8a4bb..08cb13edb01ed 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts +++ b/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES, WATCH_TYPES } from '../../../common/constants'; import { BaseWatch } from '../../../common/types/watch_types'; import { createWatch, loadWatch } from '../../lib/api'; import { goToWatchList } from '../../lib/navigation'; @@ -75,21 +75,30 @@ export async function saveWatch(watch: BaseWatch, licenseService: any) { } export async function validateActionsAndSaveWatch(watch: BaseWatch, licenseService: any) { - const { warning } = watch.validate(); - if (warning) { - return { - error: { - title: i18n.translate( - 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.errorValidationTitleText', - { - defaultMessage: 'Save watch?', - } - ), - message: warning.message, - }, - }; + if (watch.type === WATCH_TYPES.JSON) { + const actionsErrors = watch.actions.reduce((actionsErrorsAcc: any, action: any) => { + if (action.validate) { + const errors = action.validate(); + const errorKeys = Object.keys(errors); + const hasErrors = !!errorKeys.find(errorKey => errors[errorKey].length >= 1); + if (!hasErrors) { + return actionsErrorsAcc; + } + const newErrors = errorKeys.map(errorKey => errors[errorKey]).flat(); + return [...actionsErrorsAcc, ...newErrors]; + } + return actionsErrorsAcc; + }, []); + if (actionsErrors.length > 0) { + return { + validationError: { + type: 'error', + message: actionsErrors, + }, + }; + } + return saveWatch(watch, licenseService); } - // client validation passed, make request to create watch return saveWatch(watch, licenseService); } @@ -103,7 +112,8 @@ export async function onWatchSave(watch: BaseWatch, licenseService: any): Promis const existingWatch = await loadWatch(watchData.id); if (existingWatch) { return { - error: { + validationError: { + type: 'warning', title: i18n.translate( 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.existingWatchTitleText', { diff --git a/x-pack/plugins/watcher/public/services/action_defaults/action_defaults_service.factory.js b/x-pack/plugins/watcher/public/services/action_defaults/action_defaults_service.factory.js deleted file mode 100644 index c7df96105afb6..0000000000000 --- a/x-pack/plugins/watcher/public/services/action_defaults/action_defaults_service.factory.js +++ /dev/null @@ -1,22 +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 { uiModules } from 'ui/modules'; -import { XpackWatcherActionDefaultsService } from './action_defaults_service'; -import { ActionDefaultsRegistryProvider } from './registry'; - -import './actions/email_action'; -import './actions/logging_action'; -import './actions/slack_action'; - -uiModules.get('xpack/watcher') - .factory('xpackWatcherActionDefaultsService', ($injector) => { - const config = $injector.get('config'); - const Private = $injector.get('Private'); - const registry = Private(ActionDefaultsRegistryProvider); - - return new XpackWatcherActionDefaultsService(config, registry); - }); diff --git a/x-pack/plugins/watcher/public/services/action_defaults/action_defaults_service.js b/x-pack/plugins/watcher/public/services/action_defaults/action_defaults_service.js deleted file mode 100644 index c261ba43db930..0000000000000 --- a/x-pack/plugins/watcher/public/services/action_defaults/action_defaults_service.js +++ /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. - */ - -export class XpackWatcherActionDefaultsService { - constructor(config, registry) { - this.config = config; - this.registry = registry; - } - - getDefaults = (watchType, actionType) => { - const reg = this.registry; - const match = reg.find(registryEntry => registryEntry.watchType === watchType && registryEntry.actionType === actionType); - - return match ? match.getDefaults(this.config, watchType) : {}; - } -} diff --git a/x-pack/plugins/watcher/public/services/action_defaults/actions/email_action.js b/x-pack/plugins/watcher/public/services/action_defaults/actions/email_action.js deleted file mode 100644 index e6a7ff9ebe461..0000000000000 --- a/x-pack/plugins/watcher/public/services/action_defaults/actions/email_action.js +++ /dev/null @@ -1,33 +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 { merge } from 'lodash'; -import { ACTION_TYPES, WATCH_TYPES } from 'plugins/watcher/../common/constants'; -import { ActionDefaultsRegistryProvider } from '../registry'; - -const DEFAULT_ADMIN_EMAIL_CONFIG_KEY = 'xPack:defaultAdminEmail'; -const actionType = ACTION_TYPES.EMAIL; - -function getActionDefaults(config) { - return { - to: config.get(DEFAULT_ADMIN_EMAIL_CONFIG_KEY) - }; -} - -ActionDefaultsRegistryProvider.register(() => { - return { - actionType, - watchType: WATCH_TYPES.THRESHOLD, - getDefaults: (config) => { - const actionDefaults = getActionDefaults(config); - const actionWatchComboDefaults = { - subject: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' - }; - - return merge(actionDefaults, actionWatchComboDefaults); - } - }; -}); diff --git a/x-pack/plugins/watcher/public/services/action_defaults/actions/logging_action.js b/x-pack/plugins/watcher/public/services/action_defaults/actions/logging_action.js deleted file mode 100644 index 8f1ac0bf3f580..0000000000000 --- a/x-pack/plugins/watcher/public/services/action_defaults/actions/logging_action.js +++ /dev/null @@ -1,30 +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 { merge } from 'lodash'; -import { ACTION_TYPES, WATCH_TYPES } from 'plugins/watcher/../common/constants'; -import { ActionDefaultsRegistryProvider } from '../registry'; - -const actionType = ACTION_TYPES.LOGGING; - -function getActionDefaults() { - return {}; -} - -ActionDefaultsRegistryProvider.register(() => { - return { - actionType, - watchType: WATCH_TYPES.THRESHOLD, - getDefaults: (config) => { - const actionDefaults = getActionDefaults(config); - const actionWatchComboDefaults = { - text: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' - }; - - return merge(actionDefaults, actionWatchComboDefaults); - } - }; -}); diff --git a/x-pack/plugins/watcher/public/services/action_defaults/actions/slack_action.js b/x-pack/plugins/watcher/public/services/action_defaults/actions/slack_action.js deleted file mode 100644 index ed2e9a5b809c8..0000000000000 --- a/x-pack/plugins/watcher/public/services/action_defaults/actions/slack_action.js +++ /dev/null @@ -1,30 +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 { merge } from 'lodash'; -import { ACTION_TYPES, WATCH_TYPES } from 'plugins/watcher/../common/constants'; -import { ActionDefaultsRegistryProvider } from '../registry'; - -const actionType = ACTION_TYPES.SLACK; - -function getActionDefaults() { - return {}; -} - -ActionDefaultsRegistryProvider.register(() => { - return { - actionType, - watchType: WATCH_TYPES.THRESHOLD, - getDefaults: (config) => { - const actionDefaults = getActionDefaults(config); - const actionWatchComboDefaults = { - text: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' - }; - - return merge(actionDefaults, actionWatchComboDefaults); - } - }; -}); diff --git a/x-pack/plugins/watcher/public/services/action_defaults/index.js b/x-pack/plugins/watcher/public/services/action_defaults/index.js deleted file mode 100644 index a3ccc4d8d67b0..0000000000000 --- a/x-pack/plugins/watcher/public/services/action_defaults/index.js +++ /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. - */ - -import './action_defaults_service.factory'; -import './registry'; diff --git a/x-pack/plugins/watcher/public/services/action_defaults/registry.js b/x-pack/plugins/watcher/public/services/action_defaults/registry.js deleted file mode 100644 index 475d692b58604..0000000000000 --- a/x-pack/plugins/watcher/public/services/action_defaults/registry.js +++ /dev/null @@ -1,11 +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 { uiRegistry } from 'ui/registry/_registry'; - -export const ActionDefaultsRegistryProvider = uiRegistry({ - name: 'actionDefaultsRegistry' -}); diff --git a/x-pack/plugins/watcher/public/services/settings/index.js b/x-pack/plugins/watcher/public/services/settings/index.js deleted file mode 100644 index 608ed2fbd6902..0000000000000 --- a/x-pack/plugins/watcher/public/services/settings/index.js +++ /dev/null @@ -1,7 +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 './settings_service.factory'; diff --git a/x-pack/plugins/watcher/public/services/settings/settings_service.factory.js b/x-pack/plugins/watcher/public/services/settings/settings_service.factory.js deleted file mode 100644 index 8ff8a4a99ce29..0000000000000 --- a/x-pack/plugins/watcher/public/services/settings/settings_service.factory.js +++ /dev/null @@ -1,14 +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 { uiModules } from 'ui/modules'; -import { SettingsService } from './settings_service'; - -uiModules.get('xpack/watcher') - .factory('xpackWatcherSettingsService', ($injector) => { - const $http = $injector.get('$http'); - return new SettingsService($http); - }); diff --git a/x-pack/plugins/watcher/public/services/settings/settings_service.js b/x-pack/plugins/watcher/public/services/settings/settings_service.js deleted file mode 100644 index f8ecd33cd662f..0000000000000 --- a/x-pack/plugins/watcher/public/services/settings/settings_service.js +++ /dev/null @@ -1,23 +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 chrome from 'ui/chrome'; -import { ROUTES } from '../../../common/constants'; -import { Settings } from 'plugins/watcher/models/settings'; - -export class SettingsService { - constructor($http) { - this.$http = $http; - this.basePath = chrome.addBasePath(ROUTES.API_ROOT); - } - - getSettings() { - return this.$http.get(`${this.basePath}/settings`) - .then(response => { - return Settings.fromUpstreamJson(response.data); - }); - } -} diff --git a/x-pack/plugins/watcher/server/models/action/webhook_action.js b/x-pack/plugins/watcher/server/models/action/webhook_action.js index 42dfbf70d17ba..1b1567f6b66d8 100644 --- a/x-pack/plugins/watcher/server/models/action/webhook_action.js +++ b/x-pack/plugins/watcher/server/models/action/webhook_action.js @@ -108,7 +108,7 @@ export class WebhookAction extends BaseAction { static validateJson(json) { const errors = []; - if (!json.webhook.host) { // TODO this is broken when called from the client + if (!json.webhook.host) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonWebhookHostPropertyMissingBadRequestMessage', { From e858f66dec124748683c6cbbfe3695adb41da5a9 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Wed, 24 Apr 2019 14:40:56 -0400 Subject: [PATCH 08/13] remove extra spacers --- .../sections/watch_edit/components/threshold_watch_edit.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx index d4220e6dd44fa..65d4f1b06bf1b 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx @@ -753,13 +753,10 @@ const ThresholdWatchEditUi = ({ intl, pageTitle }: { intl: InjectedIntl; pageTit
- {hasErrors ? null : } - ) : null} - Date: Wed, 24 Apr 2019 15:33:23 -0700 Subject: [PATCH 09/13] Add SCSS file and tweak actions dropdown position. --- x-pack/plugins/watcher/plugin_definition.js | 1 + x-pack/plugins/watcher/public/_hacks.scss | 4 ---- x-pack/plugins/watcher/public/index.scss | 16 ++++++++++++++++ .../components/threshold_watch_action_panel.tsx | 4 ++-- 4 files changed, 19 insertions(+), 6 deletions(-) delete mode 100644 x-pack/plugins/watcher/public/_hacks.scss create mode 100644 x-pack/plugins/watcher/public/index.scss diff --git a/x-pack/plugins/watcher/plugin_definition.js b/x-pack/plugins/watcher/plugin_definition.js index a03341edb6f6e..36f5f4865e6d4 100644 --- a/x-pack/plugins/watcher/plugin_definition.js +++ b/x-pack/plugins/watcher/plugin_definition.js @@ -21,6 +21,7 @@ export const pluginDefinition = { publicDir: resolve(__dirname, 'public'), require: ['kibana', 'elasticsearch', 'xpack_main'], uiExports: { + styleSheetPaths: resolve(__dirname, 'public/index.scss'), managementSections: ['plugins/watcher'], home: ['plugins/watcher/register_feature'], }, diff --git a/x-pack/plugins/watcher/public/_hacks.scss b/x-pack/plugins/watcher/public/_hacks.scss deleted file mode 100644 index 3b42e2d3a7642..0000000000000 --- a/x-pack/plugins/watcher/public/_hacks.scss +++ /dev/null @@ -1,4 +0,0 @@ -.mgtWatcher__list { - display: flex; - flex-grow: 1; -} \ No newline at end of file diff --git a/x-pack/plugins/watcher/public/index.scss b/x-pack/plugins/watcher/public/index.scss new file mode 100644 index 0000000000000..856fa656c6330 --- /dev/null +++ b/x-pack/plugins/watcher/public/index.scss @@ -0,0 +1,16 @@ +// Import the EUI global scope so we can use EUI constants +@import 'src/legacy/ui/public/styles/_styling_constants'; + +// Watcher plugin styles + +// Prefix all styles with "watcher" to avoid conflicts. +// Examples +// watcherChart +// watcherChart__legend +// watcherChart__legend--small +// watcherChart__legend-isLoading + +.watcherThresholdWatchActionDropdownContainer { + justify-content: flex-end; + flex-direction: row; +} \ No newline at end of file diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_panel.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_panel.tsx index e6c5ccf538d00..2902c4919461c 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_panel.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_panel.tsx @@ -14,7 +14,7 @@ export const WatchActionsPanel = () => { const { watch } = useContext(WatchContext); return ( - +

@@ -28,7 +28,7 @@ export const WatchActionsPanel = () => {

- +
From 03b58c06be73347f78a30e8e42e1bf59e555f5cf Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Thu, 25 Apr 2019 14:55:00 -0400 Subject: [PATCH 10/13] address review comments --- .../public/models/action/email_action.js | 14 +++-- .../public/models/action/slack_action.js | 41 +++++-------- .../models/execute_details/execute_details.js | 30 +++------- .../components/json_watch_edit/index.ts | 7 +++ .../{ => json_watch_edit}/json_watch_edit.tsx | 8 +-- .../json_watch_edit_form.tsx | 17 ++++-- .../json_watch_edit_simulate.tsx | 17 +++--- .../json_watch_edit_simulate_results.tsx | 11 ++-- .../action_fields}/email_action_fields.tsx | 31 +++++----- .../action_fields/index.ts | 13 +++++ .../action_fields}/index_action_fields.tsx | 4 +- .../action_fields}/jira_action_fields.tsx | 7 ++- .../action_fields}/logging_action_fields.tsx | 4 +- .../pagerduty_action_fields.tsx | 4 +- .../action_fields}/slack_action_fields.tsx | 53 ++++------------- .../action_fields}/webhook_action_fields.tsx | 8 ++- .../components/threshold_watch_edit/index.ts | 7 +++ .../threshold_watch_action_accordion.tsx | 30 +++++----- .../threshold_watch_action_dropdown.tsx | 58 +++++++++---------- .../threshold_watch_action_panel.tsx | 2 +- .../threshold_watch_edit.tsx | 22 +++---- .../watch_visualization.tsx | 6 +- .../watch_edit/components/watch_edit.tsx | 2 +- .../{components => }/watch_context.ts | 0 24 files changed, 194 insertions(+), 202 deletions(-) create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/index.ts rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => json_watch_edit}/json_watch_edit.tsx (92%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => json_watch_edit}/json_watch_edit_form.tsx (93%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => json_watch_edit}/json_watch_edit_simulate.tsx (96%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => json_watch_edit}/json_watch_edit_simulate_results.tsx (94%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit/action_fields}/email_action_fields.tsx (83%) create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit/action_fields}/index_action_fields.tsx (91%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit/action_fields}/jira_action_fields.tsx (95%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit/action_fields}/logging_action_fields.tsx (91%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit/action_fields}/pagerduty_action_fields.tsx (91%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit/action_fields}/slack_action_fields.tsx (58%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit/action_fields}/webhook_action_fields.tsx (96%) create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/index.ts rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit}/threshold_watch_action_accordion.tsx (87%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit}/threshold_watch_action_dropdown.tsx (64%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit}/threshold_watch_action_panel.tsx (96%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit}/threshold_watch_edit.tsx (97%) rename x-pack/plugins/watcher/public/sections/watch_edit/components/{ => threshold_watch_edit}/watch_visualization.tsx (97%) rename x-pack/plugins/watcher/public/sections/watch_edit/{components => }/watch_context.ts (100%) diff --git a/x-pack/plugins/watcher/public/models/action/email_action.js b/x-pack/plugins/watcher/public/models/action/email_action.js index e6d8b273bb149..9f6135b3dd09c 100644 --- a/x-pack/plugins/watcher/public/models/action/email_action.js +++ b/x-pack/plugins/watcher/public/models/action/email_action.js @@ -7,12 +7,14 @@ import { get, isArray } from 'lodash'; import { BaseAction } from './base_action'; import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; export class EmailAction extends BaseAction { constructor(props = {}) { super(props); - - const toArray = get(props, 'to'); + const uiSettings = chrome.getUiSettingsClient(); + const defaultToEmail = uiSettings.get('xPack:defaultAdminEmail') || undefined; + const toArray = get(props, 'to', defaultToEmail); this.to = isArray(toArray) ? toArray : toArray && [ toArray ]; this.subject = get(props, 'subject'); this.body = get(props, 'body'); @@ -23,7 +25,7 @@ export class EmailAction extends BaseAction { to: [], body: [], }; - if (!this.to || this.to.length === 0) { + if (!this.to || !this.to.length) { errors.to.push( i18n.translate('xpack.watcher.watchActions.email.emailRecipientIsRequiredValidationMessage', { defaultMessage: 'To email address is required.', @@ -48,7 +50,11 @@ export class EmailAction extends BaseAction { subject: this.subject, body: this.body, email: { - to: this.to && this.to.length ? this.to : undefined, + to: this.to && this.to.length > 0 ? this.to : undefined, + subject: this.subject, + body: { + text: this.body, + }, } }); diff --git a/x-pack/plugins/watcher/public/models/action/slack_action.js b/x-pack/plugins/watcher/public/models/action/slack_action.js index fc12eb91afec0..baf77878b8b5f 100644 --- a/x-pack/plugins/watcher/public/models/action/slack_action.js +++ b/x-pack/plugins/watcher/public/models/action/slack_action.js @@ -16,63 +16,52 @@ export class SlackAction extends BaseAction { this.to = isArray(toArray) ? toArray : toArray && [ toArray ]; this.text = props.text; } + validate() { + // Currently no validation required const errors = { to: [], - text: [] + text: [], }; - if (!this.to || !this.to.length) { - errors.to.push( - i18n.translate('xpack.watcher.watchActions.slack.slackRecipientIsRequiredValidationMessage', { - defaultMessage: 'Slack recipient is required.', - }) - ); - } - if (!this.text) { - errors.text.push( - i18n.translate('xpack.watcher.watchActions.slack.slackMessageIsRequiredValidationMessage', { - defaultMessage: 'Slack message is required.', - }) - ); - } return errors; } get upstreamJson() { const result = super.upstreamJson; - const message = this.text || this.to.length + const to = this.to && this.to.length > 0 ? this.to : undefined; + const message = this.text || to ? { text: this.text, - to: this.to.length ? this.to : undefined + to, } - : undefined; + : {}; Object.assign(result, { - to: this.to, + to, text: this.text, slack: { message - } + }, }); return result; } get simulateMessage() { - const toList = this.to.join(', '); + const toList = this.to && this.to.join(', '); return i18n.translate('xpack.watcher.models.slackAction.simulateMessage', { - defaultMessage: 'Sample Slack message sent to {toList}.', + defaultMessage: 'Sample Slack message sent {toList}.', values: { - toList + toList: toList ? `to ${toList}` : '', } }); } get simulateFailMessage() { - const toList = this.to.join(', '); + const toList = this.to && this.to.join(', '); return i18n.translate('xpack.watcher.models.slackAction.simulateFailMessage', { - defaultMessage: 'Failed to send sample Slack message to {toList}.', + defaultMessage: 'Failed to send sample Slack message {toList}.', values: { - toList + toList: toList ? `to ${toList}` : '', } }); } diff --git a/x-pack/plugins/watcher/public/models/execute_details/execute_details.js b/x-pack/plugins/watcher/public/models/execute_details/execute_details.js index 68cc4e45c9a87..ffe4780a70602 100644 --- a/x-pack/plugins/watcher/public/models/execute_details/execute_details.js +++ b/x-pack/plugins/watcher/public/models/execute_details/execute_details.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TIME_UNITS } from '../../../common/constants'; -import moment from 'moment'; import { i18n } from '@kbn/i18n'; export class ExecuteDetails { @@ -47,30 +45,18 @@ export class ExecuteDetails { return errors; } - formatTime(timeUnit, value) { - let timeValue = moment(); - switch (timeUnit) { - case TIME_UNITS.SECOND: - timeValue = timeValue.add(value, 'seconds'); - break; - case TIME_UNITS.MINUTE: - timeValue = timeValue.add(value, 'minutes'); - break; - case TIME_UNITS.HOUR: - timeValue = timeValue.add(value, 'hours'); - break; - case TIME_UNITS.MILLISECOND: - timeValue = timeValue.add(value, 'milliseconds'); - break; + formatTime(timeUnit, timeValue) { + const now = 'now'; + if (timeValue === 0) { + return now; } - return timeValue.format(); + return `${now}+${timeValue}${timeUnit}`; } get upstreamJson() { - const hasTriggerTime = this.triggeredTimeValue !== ''; - const hasScheduleTime = this.scheduledTimeValue !== ''; - const triggeredTime = hasTriggerTime ? this.formatTime(this.triggeredTimeUnit, this.triggeredTimeValue) : undefined; - const scheduledTime = hasScheduleTime ? this.formatTime(this.scheduledTimeUnit, this.scheduledTimeValue) : undefined; + const triggeredTime = this.triggeredTimeValue ? this.formatTime(this.triggeredTimeUnit, this.triggeredTimeValue) : undefined; + const scheduledTime = this.scheduledTimeValue ? this.formatTime(this.scheduledTimeUnit, this.scheduledTimeValue) : undefined; + return { triggerData: { triggeredTime, diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/index.ts b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/index.ts new file mode 100644 index 0000000000000..21716f9e544e7 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { JsonWatchEdit } from './json_watch_edit'; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx similarity index 92% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx index 161cd355c715a..21befcfa59104 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx @@ -18,12 +18,12 @@ import { import { i18n } from '@kbn/i18n'; import { injectI18n } from '@kbn/i18n/react'; import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; -import { getActionType } from '../../../../common/lib/get_action_type'; -import { BaseWatch, ExecutedWatchDetails } from '../../../../common/types/watch_types'; -import { ACTION_MODES, TIME_UNITS } from '../../../../common/constants'; +import { getActionType } from '../../../../../common/lib/get_action_type'; +import { BaseWatch, ExecutedWatchDetails } from '../../../../../common/types/watch_types'; +import { ACTION_MODES, TIME_UNITS } from '../../../../../common/constants'; import { JsonWatchEditForm } from './json_watch_edit_form'; import { JsonWatchEditSimulate } from './json_watch_edit_simulate'; -import { WatchContext } from './watch_context'; +import { WatchContext } from '../../watch_context'; interface WatchAction { actionId: string; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx similarity index 93% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx index effa175a8219c..488c541daa934 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx @@ -18,30 +18,35 @@ import { EuiLink, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ConfirmWatchesModal } from '../../../components/confirm_watches_modal'; -import { ErrableFormRow } from '../../../components/form_errors'; -import { putWatchApiUrl } from '../../../lib/documentation_links'; -import { onWatchSave, saveWatch } from '../watch_edit_actions'; -import { WatchContext } from './watch_context'; -import { LicenseServiceContext } from '../../../license_service_context'; +import { ConfirmWatchesModal } from '../../../../components/confirm_watches_modal'; +import { ErrableFormRow } from '../../../../components/form_errors'; +import { putWatchApiUrl } from '../../../../lib/documentation_links'; +import { onWatchSave, saveWatch } from '../../watch_edit_actions'; +import { WatchContext } from '../../watch_context'; +import { LicenseServiceContext } from '../../../../license_service_context'; export const JsonWatchEditForm = () => { const { watch, setWatchProperty } = useContext(WatchContext); const licenseService = useContext(LicenseServiceContext); + const { errors } = watch.validate(); const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + const [validationResult, setValidationResult] = useState<{ type: string; title: string; message: string; } | null>(null); + const hasActionErrors = !!validationResult && validationResult.type === 'error'; + const invalidActionMessage = i18n.translate( 'xpack.watcher.sections.watchEdit.json.form.actionValidationErrorMessage', { defaultMessage: 'Invalid watch actions', } ); + const jsonErrors = { ...errors, json: hasActionErrors ? [...errors.json, invalidActionMessage] : [...errors.json], diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx similarity index 96% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx index cc12c842aff54..7d6c917efc8c1 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx @@ -27,13 +27,16 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; import { toastNotifications } from 'ui/notify'; -import { ACTION_MODES, TIME_UNITS } from '../../../../common/constants'; -import { ExecutedWatchDetails, ExecutedWatchResults } from '../../../../common/types/watch_types'; -import { ErrableFormRow } from '../../../components/form_errors'; -import { executeWatch } from '../../../lib/api'; -import { executeWatchApiUrl } from '../../../lib/documentation_links'; -import { WatchContext } from '../../../sections/watch_edit/components/watch_context'; -import { timeUnits } from '../time_units'; +import { ACTION_MODES, TIME_UNITS } from '../../../../../common/constants'; +import { + ExecutedWatchDetails, + ExecutedWatchResults, +} from '../../../../../common/types/watch_types'; +import { ErrableFormRow } from '../../../../components/form_errors'; +import { executeWatch } from '../../../../lib/api'; +import { executeWatchApiUrl } from '../../../../lib/documentation_links'; +import { WatchContext } from '../../watch_context'; +import { timeUnits } from '../../time_units'; import { JsonWatchEditSimulateResults } from './json_watch_edit_simulate_results'; export const JsonWatchEditSimulate = ({ diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate_results.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx similarity index 94% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate_results.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx index ce7c0d194bf53..a5a4bbc6e06a8 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate_results.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx @@ -18,10 +18,13 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { WATCH_STATES } from '../../../../common/constants'; -import { ExecutedWatchDetails, ExecutedWatchResults } from '../../../../common/types/watch_types'; -import { getTypeFromAction } from '../watch_edit_actions'; -import { WatchContext } from './watch_context'; +import { WATCH_STATES } from '../../../../../common/constants'; +import { + ExecutedWatchDetails, + ExecutedWatchResults, +} from '../../../../../common/types/watch_types'; +import { getTypeFromAction } from '../../watch_edit_actions'; +import { WatchContext } from '../../watch_context'; const WATCH_ICON_COLORS = { [WATCH_STATES.DISABLED]: 'subdued', diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/email_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx similarity index 83% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/email_action_fields.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx index 2cd3e9125eee3..7eec71dbd9474 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/email_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx @@ -3,12 +3,12 @@ * 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 } from 'react'; +import React, { Fragment } from 'react'; import { EuiComboBox, EuiFieldText, EuiFormRow, EuiTextArea } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ErrableFormRow } from '../../../components/form_errors'; -import { EmailAction } from '../../../../common/types/action_types'; +import { ErrableFormRow } from '../../../../../components/form_errors'; +import { EmailAction } from '../../../../../../common/types/action_types'; interface Props { action: EmailAction; @@ -24,18 +24,7 @@ export const EmailActionFields: React.FunctionComponent = ({ hasErrors, }) => { const { to, subject, body } = action; - const [toOptions, setToOptions] = useState>([]); - - useEffect(() => { - if (to && to.length > 0) { - const toOptionsList = to.map(toItem => { - return { - label: toItem, - }; - }); - setToOptions(toOptionsList); - } - }, []); + const toOptions = to ? to.map(label => ({ label })) : []; return ( @@ -58,18 +47,25 @@ export const EmailActionFields: React.FunctionComponent = ({ selectedOptions={toOptions} onCreateOption={(searchValue: string) => { const newOptions = [...toOptions, { label: searchValue }]; - setToOptions(newOptions); editAction({ key: 'to', value: newOptions.map(newOption => newOption.label) }); }} onChange={(selectedOptions: Array<{ label: string }>) => { - setToOptions(selectedOptions); editAction({ key: 'to', value: selectedOptions.map(selectedOption => selectedOption.label), }); }} + onBlur={() => { + if (!to) { + editAction({ + key: 'to', + value: [], + }); + } + }} /> + = ({ }} /> + = ({ }} /> + = ({ }} /> + = ({ }} /> + void; - errors: { [key: string]: string[] }; - hasErrors: boolean; } -export const SlackActionFields: React.FunctionComponent = ({ - action, - editAction, - errors, - hasErrors, -}) => { +export const SlackActionFields: React.FunctionComponent = ({ action, editAction }) => { const { text, to } = action; - const [toOptions, setToOptions] = useState>([]); - - useEffect(() => { - if (to && to.length > 0) { - const toOptionsList = to.map(toItem => { - return { - label: toItem, - }; - }); - setToOptions(toOptionsList); - } - }, []); + const toOptions = to ? to.map(label => ({ label })) : []; return ( - = ({ selectedOptions={toOptions} onCreateOption={(searchValue: string) => { const newOptions = [...toOptions, { label: searchValue }]; - setToOptions(newOptions); editAction({ key: 'to', value: newOptions.map(newOption => newOption.label) }); }} onChange={(selectedOptions: Array<{ label: string }>) => { - setToOptions(selectedOptions); editAction({ key: 'to', value: selectedOptions.map(selectedOption => selectedOption.label), }); }} /> - - + + = ({ onChange={e => { editAction({ key: 'text', value: e.target.value }); }} - onBlur={() => { - if (!text) { - editAction({ key: 'text', value: [] }); - } - }} /> - + ); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx similarity index 96% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx index 3f7b76e2819f9..919dd73d3a03d 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/webhook_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx @@ -7,8 +7,8 @@ import React, { Fragment } from 'react'; import { EuiCodeEditor, EuiFieldNumber, EuiFieldText, EuiFormRow, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ErrableFormRow } from '../../../components/form_errors'; -import { WebhookAction } from '../../../../common/types/action_types'; +import { ErrableFormRow } from '../../../../../components/form_errors'; +import { WebhookAction } from '../../../../../../common/types/action_types'; interface Props { action: WebhookAction; @@ -67,6 +67,7 @@ export const WebhookActionFields: React.FunctionComponent = ({ }} /> + = ({ }} /> + = ({ }} /> + = ({ }} /> + { if (actions && actions.length >= 1) { return actions.map((action: any) => { - const FieldsComponent = ActionFieldsComponent[action.type]; + const FieldsComponent = ActionFieldsComponentMap[action.type]; const errors = action.validate(); const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); @@ -126,8 +128,8 @@ export const WatchActionsAccordion: React.FunctionComponent = () => { setWatchProperty('actions', updatedActions); }} /> + { diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx similarity index 64% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx index e6727ce741d25..d116e4111644b 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_action_dropdown.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx @@ -14,9 +14,9 @@ import { import { i18n } from '@kbn/i18n'; import React, { useContext, useEffect, useState } from 'react'; import { Action } from 'plugins/watcher/models/action'; -import { ActionType } from '../../../../common/types/action_types'; -import { fetchSettings } from '../../../lib/api'; -import { WatchContext } from './watch_context'; +import { ActionType } from '../../../../../common/types/action_types'; +import { fetchSettings } from '../../../../lib/api'; +import { WatchContext } from '../../watch_context'; const EMPTY_FIRST_OPTION_VALUE = 'empty-first-option'; @@ -55,33 +55,31 @@ export const WatchActionsDropdown: React.FunctionComponent = () => { useEffect(() => { getSettings(); }, []); - const actionOptions = - actions && - actions.map((action: ActionType) => { - const description = action.isEnabled ? action.selectMessage : disabledMessage; - return { - value: action.type, - inputDisplay: action.typeName, - disabled: !action.isEnabled, - dropdownDisplay: ( - - - - - - {action.typeName} - - -

{description}

-
-
-
- ), - }; - }); - const actionOptionsWithEmptyValue = actionOptions - ? [firstActionOption, ...actionOptions] - : [firstActionOption]; + const actionOptions = actions + ? actions.map((action: ActionType) => { + const description = action.isEnabled ? action.selectMessage : disabledMessage; + return { + value: action.type, + inputDisplay: action.typeName, + disabled: !action.isEnabled, + dropdownDisplay: ( + + + + + + {action.typeName} + + +

{description}

+
+
+
+ ), + }; + }) + : []; + const actionOptionsWithEmptyValue = [firstActionOption, ...actionOptions]; return ( { const { watch } = useContext(WatchContext); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx similarity index 97% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx index 65d4f1b06bf1b..11b7dcb5b5551 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx @@ -27,18 +27,18 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { ConfirmWatchesModal } from '../../../components/confirm_watches_modal'; -import { ErrableFormRow } from '../../../components/form_errors'; -import { fetchFields, getMatchingIndices, loadIndexPatterns } from '../../../lib/api'; -import { aggTypes } from '../../../models/watch/agg_types'; -import { groupByTypes } from '../../../models/watch/group_by_types'; -import { comparators } from '../comparators'; -import { timeUnits } from '../time_units'; -import { onWatchSave, saveWatch } from '../watch_edit_actions'; +import { ConfirmWatchesModal } from '../../../../components/confirm_watches_modal'; +import { ErrableFormRow } from '../../../../components/form_errors'; +import { fetchFields, getMatchingIndices, loadIndexPatterns } from '../../../../lib/api'; +import { aggTypes } from '../../../../models/watch/agg_types'; +import { groupByTypes } from '../../../../models/watch/group_by_types'; +import { comparators } from '../../comparators'; +import { timeUnits } from '../../time_units'; +import { onWatchSave, saveWatch } from '../../watch_edit_actions'; import { WatchActionsPanel } from './threshold_watch_action_panel'; -import { WatchContext } from './watch_context'; +import { WatchContext } from '../../watch_context'; import { WatchVisualization } from './watch_visualization'; -import { LicenseServiceContext } from '../../../license_service_context'; +import { LicenseServiceContext } from '../../../../license_service_context'; const firstFieldOption = { text: i18n.translate('xpack.watcher.sections.watchEdit.titlePanel.timeFieldOptionLabel', { defaultMessage: 'Select a field', @@ -754,7 +754,9 @@ const ThresholdWatchEditUi = ({ intl, pageTitle }: { intl: InjectedIntl; pageTit
{hasErrors ? null : } + + ) : null} diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_visualization.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx similarity index 97% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/watch_visualization.tsx rename to x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx index 890e284fea6a8..11ef097bd18cb 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_visualization.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx @@ -28,9 +28,9 @@ import moment from 'moment-timezone'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { VisualizeOptions } from 'plugins/watcher/models/visualize_options'; -import { getWatchVisualizationData } from '../../../lib/api'; -import { WatchContext } from './watch_context'; -import { aggTypes } from '../../../models/watch/agg_types'; +import { getWatchVisualizationData } from '../../../../lib/api'; +import { WatchContext } from '../../watch_context'; +import { aggTypes } from '../../../../models/watch/agg_types'; const getChartTheme = () => { const isDarkTheme = chrome.getUiSettingsClient().get('theme:darkMode'); const baseTheme = isDarkTheme ? DARK_THEME : LIGHT_THEME; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx index 39f3836662832..57370de0aefc4 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx @@ -20,7 +20,7 @@ import { loadWatch } from '../../../lib/api'; import { listBreadcrumb, editBreadcrumb, createBreadcrumb } from '../../../lib/breadcrumbs'; import { JsonWatchEdit } from './json_watch_edit'; import { ThresholdWatchEdit } from './threshold_watch_edit'; -import { WatchContext } from './watch_context'; +import { WatchContext } from '../watch_context'; const getTitle = (watch: BaseWatch) => { if (watch.isNew) { diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_context.ts b/x-pack/plugins/watcher/public/sections/watch_edit/watch_context.ts similarity index 100% rename from x-pack/plugins/watcher/public/sections/watch_edit/components/watch_context.ts rename to x-pack/plugins/watcher/public/sections/watch_edit/watch_context.ts From 85fc8df7576abad1afa0b3292435ceb2a5483310 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Fri, 26 Apr 2019 16:05:33 -0400 Subject: [PATCH 11/13] adjust logic that determines if actions are enabled/disabled --- .../documentation_links.ts | 9 ++- .../public/models/action/email_action.js | 14 +++-- .../public/models/action/index_action.js | 1 - .../public/models/action/jira_action.js | 13 ++-- .../public/models/action/logging_action.js | 12 ++-- .../public/models/action/pagerduty_action.js | 12 ++-- .../public/models/action/slack_action.js | 13 ++-- .../public/models/action/webhook_action.js | 8 +-- .../watcher/public/models/watch/base_watch.js | 10 +--- .../action_fields/jira_action_fields.tsx | 3 + .../action_fields/pagerduty_action_fields.tsx | 3 + .../action_fields/slack_action_fields.tsx | 8 ++- .../threshold_watch_action_accordion.tsx | 59 +++++++++++++++++- .../threshold_watch_action_dropdown.tsx | 60 ++++++++++--------- .../threshold_watch_action_panel.tsx | 25 +++++++- .../watch_edit/components/watch_edit.tsx | 4 +- .../server/models/settings/settings.js | 3 +- 17 files changed, 184 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.ts b/x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.ts index c5bd1c2f53c82..32e6cb2887956 100644 --- a/x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.ts +++ b/x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.ts @@ -5,9 +5,16 @@ */ import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { ACTION_TYPES } from '../../../common/constants'; const esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; +const esStackBase = `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack-overview/${DOC_LINK_VERSION}`; export const putWatchApiUrl = `${esBase}/watcher-api-put-watch.html`; export const executeWatchApiUrl = `${esBase}/watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode`; -export const watchNotificationSettingsUrl = `${esBase}/notification-settings.html#slack-notification-settings`; + +export const watchActionsConfigurationMap = { + [ACTION_TYPES.SLACK]: `${esStackBase}/actions-slack.html#configuring-slack`, + [ACTION_TYPES.PAGERDUTY]: `${esStackBase}/actions-pagerduty.html#configuring-pagerduty`, + [ACTION_TYPES.JIRA]: `${esStackBase}/actions-jira.html#configuring-jira`, +}; diff --git a/x-pack/plugins/watcher/public/models/action/email_action.js b/x-pack/plugins/watcher/public/models/action/email_action.js index 9f6135b3dd09c..79c9289fd52b8 100644 --- a/x-pack/plugins/watcher/public/models/action/email_action.js +++ b/x-pack/plugins/watcher/public/models/action/email_action.js @@ -12,11 +12,20 @@ import chrome from 'ui/chrome'; export class EmailAction extends BaseAction { constructor(props = {}) { super(props); + const uiSettings = chrome.getUiSettingsClient(); const defaultToEmail = uiSettings.get('xPack:defaultAdminEmail') || undefined; const toArray = get(props, 'to', defaultToEmail); this.to = isArray(toArray) ? toArray : toArray && [ toArray ]; - this.subject = get(props, 'subject'); + + const defaultSubject = i18n.translate('xpack.watcher.models.emailAction.defaultSubjectText', { + defaultMessage: 'Watch [{context}] has exceeded the threshold', + values: { + context: '{{ctx.metadata.name}}', + } + }); + this.subject = get(props, 'subject', defaultSubject); + this.body = get(props, 'body'); } @@ -95,7 +104,4 @@ export class EmailAction extends BaseAction { static simulatePrompt = i18n.translate('xpack.watcher.models.emailAction.simulateButtonLabel', { defaultMessage: 'Test fire an email now' }); - static defaults = { - subject: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' - }; } diff --git a/x-pack/plugins/watcher/public/models/action/index_action.js b/x-pack/plugins/watcher/public/models/action/index_action.js index a233330f882b1..f08e8972c7cec 100644 --- a/x-pack/plugins/watcher/public/models/action/index_action.js +++ b/x-pack/plugins/watcher/public/models/action/index_action.js @@ -67,7 +67,6 @@ export class IndexAction extends BaseAction { return new IndexAction(upstreamAction); } - static defaults = {}; static typeName = i18n.translate('xpack.watcher.models.indexAction.typeName', { defaultMessage: 'Index', }); diff --git a/x-pack/plugins/watcher/public/models/action/jira_action.js b/x-pack/plugins/watcher/public/models/action/jira_action.js index b4cd36544ec96..8b2f780732187 100644 --- a/x-pack/plugins/watcher/public/models/action/jira_action.js +++ b/x-pack/plugins/watcher/public/models/action/jira_action.js @@ -11,10 +11,18 @@ import { i18n } from '@kbn/i18n'; export class JiraAction extends BaseAction { constructor(props = {}) { super(props); + + const defaultSummary = i18n.translate('xpack.watcher.models.jiraAction.defaultSummaryText', { + defaultMessage: 'Watch [{context}] has exceeded the threshold', + values: { + context: '{{ctx.metadata.name}}', + } + }); + this.summary = get(props, 'summary', defaultSummary); + this.account = get(props, 'account'); this.projectKey = get(props, 'projectKey'); this.issueType = get(props, 'issueType'); - this.summary = get(props, 'summary'); } validate() { @@ -98,7 +106,4 @@ export class JiraAction extends BaseAction { static simulatePrompt = i18n.translate('xpack.watcher.models.jiraAction.simulateButtonLabel', { defaultMessage: 'Create a sample Jira issue now' }); - static defaults = { - summary: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' - }; } diff --git a/x-pack/plugins/watcher/public/models/action/logging_action.js b/x-pack/plugins/watcher/public/models/action/logging_action.js index 485d351443220..18a4f4a1c8940 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -12,7 +12,13 @@ export class LoggingAction extends BaseAction { constructor(props = {}) { super(props); - this.text = get(props, 'text'); + const defaultText = i18n.translate('xpack.watcher.models.loggingAction.defaultText', { + defaultMessage: 'Watch [{context}] has exceeded the threshold', + values: { + context: '{{ctx.metadata.name}}', + } + }); + this.text = get(props, 'text', defaultText); } validate() { @@ -55,10 +61,6 @@ export class LoggingAction extends BaseAction { }); } - static defaults = { - text: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' - }; - static fromUpstreamJson(upstreamAction) { return new LoggingAction(upstreamAction); } diff --git a/x-pack/plugins/watcher/public/models/action/pagerduty_action.js b/x-pack/plugins/watcher/public/models/action/pagerduty_action.js index ae2869f0301a6..370c5310c5ea6 100644 --- a/x-pack/plugins/watcher/public/models/action/pagerduty_action.js +++ b/x-pack/plugins/watcher/public/models/action/pagerduty_action.js @@ -12,7 +12,14 @@ import { i18n } from '@kbn/i18n'; export class PagerDutyAction extends BaseAction { constructor(props = {}) { super(props); - this.description = get(props, 'description'); + + const defaultDescription = i18n.translate('xpack.watcher.models.pagerdutyAction.defaultDescriptionText', { + defaultMessage: 'Watch [{context}] has exceeded the threshold', + values: { + context: '{{ctx.metadata.name}}', + } + }); + this.description = get(props, 'description', defaultDescription); } validate() { @@ -69,8 +76,5 @@ export class PagerDutyAction extends BaseAction { static simulatePrompt = i18n.translate('xpack.watcher.models.pagerDutyAction.simulateButtonLabel', { defaultMessage: 'Test fire a PagerDuty event' }); - static defaults = { - description: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' - }; } diff --git a/x-pack/plugins/watcher/public/models/action/slack_action.js b/x-pack/plugins/watcher/public/models/action/slack_action.js index baf77878b8b5f..b0ad097ac3f01 100644 --- a/x-pack/plugins/watcher/public/models/action/slack_action.js +++ b/x-pack/plugins/watcher/public/models/action/slack_action.js @@ -14,7 +14,14 @@ export class SlackAction extends BaseAction { const toArray = get(props, 'to'); this.to = isArray(toArray) ? toArray : toArray && [ toArray ]; - this.text = props.text; + + const defaultText = i18n.translate('xpack.watcher.models.slackAction.defaultText', { + defaultMessage: 'Watch [{context}] has exceeded the threshold', + values: { + context: '{{ctx.metadata.name}}', + } + }); + this.text = props.text || defaultText; } validate() { @@ -66,10 +73,6 @@ export class SlackAction extends BaseAction { }); } - static defaults = { - text: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' - } - static fromUpstreamJson(upstreamAction) { return new SlackAction(upstreamAction); } diff --git a/x-pack/plugins/watcher/public/models/action/webhook_action.js b/x-pack/plugins/watcher/public/models/action/webhook_action.js index b12a1c350bcad..d1fba0700f6f6 100644 --- a/x-pack/plugins/watcher/public/models/action/webhook_action.js +++ b/x-pack/plugins/watcher/public/models/action/webhook_action.js @@ -13,11 +13,14 @@ export class WebhookAction extends BaseAction { constructor(props = {}) { super(props); + const defaultJson = JSON.stringify({ message: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' }, null, 2); + this.body = get(props, 'body', defaultJson); + this.method = get(props, 'method'); this.host = get(props, 'host'); this.port = get(props, 'port'); this.path = get(props, 'path'); - this.body = get(props, 'body'); + this.fullPath = `${this.host}${this.port}${this.path}`; } @@ -99,9 +102,6 @@ export class WebhookAction extends BaseAction { return new WebhookAction(upstreamAction); } - static defaults = { - body: JSON.stringify({ message: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' }, null, 2) - }; static typeName = i18n.translate('xpack.watcher.models.webhookAction.typeName', { defaultMessage: 'Webhook', }); diff --git a/x-pack/plugins/watcher/public/models/watch/base_watch.js b/x-pack/plugins/watcher/public/models/watch/base_watch.js index a58414abd006a..14c269b197599 100644 --- a/x-pack/plugins/watcher/public/models/watch/base_watch.js +++ b/x-pack/plugins/watcher/public/models/watch/base_watch.js @@ -5,7 +5,7 @@ */ import { getSearchValue } from 'plugins/watcher/lib/get_search_value'; -import { get, isEqual, remove, map, merge } from 'lodash'; +import { get, isEqual, remove, map } from 'lodash'; import { Action } from '../action'; import { WatchStatus } from '../watch_status'; import { WatchErrors } from '../watch_errors'; @@ -41,7 +41,7 @@ export class BaseWatch { this.watchStatus = watchStatus; } - createAction = (type, defaults) => { + createAction = (type) => { const ActionTypes = Action.getActionTypes(); const ActionType = ActionTypes[type]; @@ -55,11 +55,7 @@ export class BaseWatch { } const id = createActionId(this.actions, type); - const props = merge( - {}, - defaults, - { id, type } - ); + const props = { id, type }; const action = new ActionType(props); this.addAction(action); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx index 4673ba411d62f..09b70fe26a313 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx @@ -15,6 +15,7 @@ interface Props { editAction: (changedProperty: { key: string; value: any }) => void; errors: { [key: string]: string[] }; hasErrors: boolean; + children: React.ReactNode; } export const JiraActionFields: React.FunctionComponent = ({ @@ -22,11 +23,13 @@ export const JiraActionFields: React.FunctionComponent = ({ editAction, errors, hasErrors, + children, }) => { const { account, projectKey, issueType, summary } = action; return ( + {children} void; errors: { [key: string]: string[] }; hasErrors: boolean; + children: React.ReactNode; } export const PagerDutyActionFields: React.FunctionComponent = ({ @@ -21,10 +22,12 @@ export const PagerDutyActionFields: React.FunctionComponent = ({ hasErrors, action, editAction, + children, }) => { const { description } = action; return ( + {children} void; + children: React.ReactNode; } -export const SlackActionFields: React.FunctionComponent = ({ action, editAction }) => { +export const SlackActionFields: React.FunctionComponent = ({ + action, + editAction, + children, +}) => { const { text, to } = action; const toOptions = to ? to.map(label => ({ label })) : []; return ( + {children} { +interface Props { + settings: { + actionTypes: { + [key: string]: { + enabled: boolean; + }; + }; + } | null; +} + +export const WatchActionsAccordion: React.FunctionComponent = ({ settings }) => { const { watch, setWatchProperty } = useContext(WatchContext); const { actions } = watch; @@ -127,7 +143,46 @@ export const WatchActionsAccordion: React.FunctionComponent = () => { }); setWatchProperty('actions', updatedActions); }} - /> + > + {settings && settings.actionTypes[action.type].enabled === false ? ( + + + +

+ + + + ), + }} + /> +

+
+
+ +
+ ) : null} + { - const allActionTypes = Action.getActionTypes(); - const { addAction } = useContext(WatchContext); - const [actions, setActions] = useState(null); - const getSettings = async () => { - const settings = await fetchSettings(); - const newActions = Object.keys(allActionTypes).map(actionKey => { - const { typeName, iconClass, selectMessage } = allActionTypes[actionKey]; - return { - type: actionKey, - typeName, - iconClass, - selectMessage, - isEnabled: settings.actionTypes[actionKey].enabled, +interface Props { + settings: { + actionTypes: { + [key: string]: { + enabled: boolean; }; - }); - setActions(newActions); - }; - useEffect(() => { - getSettings(); - }, []); + }; + } | null; +} + +export const WatchActionsDropdown: React.FunctionComponent = ({ settings }) => { + const { addAction } = useContext(WatchContext); + + const allActionTypes = Action.getActionTypes(); + + const actions = Object.keys(allActionTypes).map(actionKey => { + const { typeName, iconClass, selectMessage } = allActionTypes[actionKey]; + return { + type: actionKey, + typeName, + iconClass, + selectMessage, + isEnabled: settings ? settings.actionTypes[actionKey].enabled : true, + }; + }); + const actionOptions = actions - ? actions.map((action: ActionType) => { - const description = action.isEnabled ? action.selectMessage : disabledMessage; + ? actions.map((action: any) => { + const isActionDisabled = action.type === ACTION_TYPES.EMAIL && !action.isEnabled; // Currently can only fully verify email action + const description = isActionDisabled ? disabledMessage : action.selectMessage; return { value: action.type, inputDisplay: action.typeName, - disabled: !action.isEnabled, + disabled: isActionDisabled && !action.isEnabled, dropdownDisplay: ( @@ -85,8 +90,7 @@ export const WatchActionsDropdown: React.FunctionComponent = () => { options={actionOptionsWithEmptyValue} valueOfSelected={EMPTY_FIRST_OPTION_VALUE} onChange={(value: string) => { - const defaults = allActionTypes[value].defaults; - addAction({ defaults, type: value }); + addAction({ type: value }); }} itemLayoutAlign="top" hasDividers diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx index e36fd9cd3d253..35936680d3ce2 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx @@ -5,13 +5,32 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { Fragment, useContext } from 'react'; +import React, { Fragment, useContext, useEffect, useState } from 'react'; +import { fetchSettings } from '../../../../lib/api'; import { WatchActionsDropdown } from './threshold_watch_action_dropdown'; import { WatchActionsAccordion } from './threshold_watch_action_accordion'; import { WatchContext } from '../../watch_context'; export const WatchActionsPanel = () => { const { watch } = useContext(WatchContext); + + const [settings, setSettings] = useState<{ + actionTypes: { + [key: string]: { + enabled: boolean; + }; + }; + } | null>(null); + + const getSettings = async () => { + const actionSettings = await fetchSettings(); + setSettings(actionSettings); + }; + + useEffect(() => { + getSettings(); + }, []); + return ( @@ -29,11 +48,11 @@ export const WatchActionsPanel = () => { - + - +
); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx index 57370de0aefc4..bf2799db12838 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx @@ -66,9 +66,9 @@ const watchReducer = (state: any, action: any) => { } case 'addAction': - const { type, defaults } = payload; + const { type } = payload; const newWatch = new (Watch.getWatchTypes())[watch.type](watch); - newWatch.createAction(type, defaults); + newWatch.createAction(type); return { ...state, watch: newWatch, diff --git a/x-pack/plugins/watcher/server/models/settings/settings.js b/x-pack/plugins/watcher/server/models/settings/settings.js index 0d6a9244c6c3d..95a1db7533f41 100644 --- a/x-pack/plugins/watcher/server/models/settings/settings.js +++ b/x-pack/plugins/watcher/server/models/settings/settings.js @@ -12,7 +12,6 @@ function isEnabledByDefault(actionType) { case ACTION_TYPES.WEBHOOK: case ACTION_TYPES.INDEX: case ACTION_TYPES.LOGGING: - case ACTION_TYPES.SLACK: // https://github.com/elastic/x-pack-elasticsearch/issues/1573 return true; default: return false; @@ -22,7 +21,7 @@ function isEnabledByDefault(actionType) { function requiresAccountInfo(actionType) { switch (actionType) { case ACTION_TYPES.EMAIL: - // case ACTION_TYPES.SLACK: // https://github.com/elastic/x-pack-elasticsearch/issues/1573 + case ACTION_TYPES.SLACK: case ACTION_TYPES.JIRA: case ACTION_TYPES.PAGERDUTY: return true; From 884335cd6bb5739973f4718c3a6d851fe9fb15fc Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 29 Apr 2019 13:56:49 -0400 Subject: [PATCH 12/13] addressing pr feedback --- .../watcher/common/types/action_types.ts | 10 +- x-pack/plugins/watcher/public/index.scss | 2 +- x-pack/plugins/watcher/public/lib/api.ts | 15 ++- .../public/models/action/base_action.js | 1 + .../public/models/action/email_action.js | 6 +- .../public/models/action/index_action.js | 1 + .../public/models/action/jira_action.js | 5 +- .../public/models/action/logging_action.js | 2 +- .../public/models/action/pagerduty_action.js | 2 +- .../public/models/action/slack_action.js | 2 +- .../public/models/action/webhook_action.js | 7 +- .../watcher/public/models/watch/base_watch.js | 4 +- .../json_watch_edit/json_watch_edit.tsx | 5 +- .../action_fields/index_action_fields.tsx | 54 +++++----- .../action_fields/jira_action_fields.tsx | 22 +---- .../action_fields/logging_action_fields.tsx | 52 +++++----- .../action_fields/webhook_action_fields.tsx | 31 ++---- .../threshold_watch_action_accordion.tsx | 99 +++++++++---------- .../threshold_watch_action_dropdown.tsx | 22 ++--- .../threshold_watch_action_panel.tsx | 33 +++---- .../threshold_watch_edit.tsx | 14 ++- .../watch_edit/components/watch_edit.tsx | 4 +- .../sections/watch_edit/watch_edit_actions.ts | 12 ++- .../server/models/action/jira_action.js | 17 ---- .../server/models/action/webhook_action.js | 15 ++- .../models/settings/__tests__/settings.js | 4 +- 26 files changed, 210 insertions(+), 231 deletions(-) diff --git a/x-pack/plugins/watcher/common/types/action_types.ts b/x-pack/plugins/watcher/common/types/action_types.ts index be5c2fc1e8e66..253ab31f69523 100644 --- a/x-pack/plugins/watcher/common/types/action_types.ts +++ b/x-pack/plugins/watcher/common/types/action_types.ts @@ -15,6 +15,7 @@ type PagerDutyActionType = 'pagerduty'; export interface BaseAction { id: string; typeName: string; + isNew: boolean; simulateMessage: string; simulateFailMessage: string; simulatePrompt: string; @@ -26,7 +27,7 @@ export interface BaseAction { export interface EmailAction extends BaseAction { type: EmailActionType; iconClass: 'email'; - to: []; + to: string[]; subject?: string; body: string; } @@ -56,20 +57,19 @@ export interface WebhookAction extends BaseAction { host: string; port: number; path?: string; - body: string; + body?: string; } export interface SlackAction extends BaseAction { type: SlackActionType; iconClass: 'logoSlack'; - text: string; - to: string[]; + text?: string; + to?: string[]; } export interface JiraAction extends BaseAction { type: JiraActionType; iconClass: 'apps'; - account?: string; projectKey: string; issueType: string; summary: string; diff --git a/x-pack/plugins/watcher/public/index.scss b/x-pack/plugins/watcher/public/index.scss index 856fa656c6330..ba4ed2516625a 100644 --- a/x-pack/plugins/watcher/public/index.scss +++ b/x-pack/plugins/watcher/public/index.scss @@ -13,4 +13,4 @@ .watcherThresholdWatchActionDropdownContainer { justify-content: flex-end; flex-direction: row; -} \ No newline at end of file +} diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts index 81faa9037ecef..9c9c81d77314f 100644 --- a/x-pack/plugins/watcher/public/lib/api.ts +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -167,7 +167,16 @@ export const getWatchVisualizationData = async (watchModel: BaseWatch, visualize return response.data; }; -export const fetchSettings = async () => { - const { data } = await getHttpClient().get(`${basePath}/settings`); - return Settings.fromUpstreamJson(data); +export const loadSettings = () => { + return useRequest({ + path: `${basePath}/settings`, + method: 'get', + processData: (data: { + actionTypes: { + [key: string]: { + enabled: boolean; + }; + }; + }) => Settings.fromUpstreamJson(data), + }); }; diff --git a/x-pack/plugins/watcher/public/models/action/base_action.js b/x-pack/plugins/watcher/public/models/action/base_action.js index 4375da3ee2673..3b358b9560376 100644 --- a/x-pack/plugins/watcher/public/models/action/base_action.js +++ b/x-pack/plugins/watcher/public/models/action/base_action.js @@ -11,6 +11,7 @@ export class BaseAction { constructor(props = {}) { this.id = get(props, 'id'); this.type = get(props, 'type'); + this.isNew = get(props, 'isNew', false); } get upstreamJson() { diff --git a/x-pack/plugins/watcher/public/models/action/email_action.js b/x-pack/plugins/watcher/public/models/action/email_action.js index 79c9289fd52b8..bca78e580a774 100644 --- a/x-pack/plugins/watcher/public/models/action/email_action.js +++ b/x-pack/plugins/watcher/public/models/action/email_action.js @@ -24,7 +24,7 @@ export class EmailAction extends BaseAction { context: '{{ctx.metadata.name}}', } }); - this.subject = get(props, 'subject', defaultSubject); + this.subject = get(props, 'subject', props.ignoreDefaults ? null : defaultSubject); this.body = get(props, 'body'); } @@ -34,10 +34,11 @@ export class EmailAction extends BaseAction { to: [], body: [], }; + if (!this.to || !this.to.length) { errors.to.push( i18n.translate('xpack.watcher.watchActions.email.emailRecipientIsRequiredValidationMessage', { - defaultMessage: 'To email address is required.', + defaultMessage: '"To" email address is required.', }) ); } @@ -48,6 +49,7 @@ export class EmailAction extends BaseAction { }) ); } + return errors; } diff --git a/x-pack/plugins/watcher/public/models/action/index_action.js b/x-pack/plugins/watcher/public/models/action/index_action.js index f08e8972c7cec..c254f2a63586a 100644 --- a/x-pack/plugins/watcher/public/models/action/index_action.js +++ b/x-pack/plugins/watcher/public/models/action/index_action.js @@ -35,6 +35,7 @@ export class IndexAction extends BaseAction { const result = super.upstreamJson; Object.assign(result, { + index: this.index, index: { index: this.index, } diff --git a/x-pack/plugins/watcher/public/models/action/jira_action.js b/x-pack/plugins/watcher/public/models/action/jira_action.js index 8b2f780732187..0dc90560b65c4 100644 --- a/x-pack/plugins/watcher/public/models/action/jira_action.js +++ b/x-pack/plugins/watcher/public/models/action/jira_action.js @@ -18,9 +18,8 @@ export class JiraAction extends BaseAction { context: '{{ctx.metadata.name}}', } }); - this.summary = get(props, 'summary', defaultSummary); - this.account = get(props, 'account'); + this.summary = get(props, 'summary', props.ignoreDefaults ? null : defaultSummary); this.projectKey = get(props, 'projectKey'); this.issueType = get(props, 'issueType'); } @@ -60,7 +59,6 @@ export class JiraAction extends BaseAction { Object.assign(result, { projectKey: this.projectKey, - account: this.account ? this.account : null, issueType: this.issueType, summary: this.summary, jira: { @@ -73,7 +71,6 @@ export class JiraAction extends BaseAction { }, summary: this.summary, }, - account: this.account ? this.account : null, } }); diff --git a/x-pack/plugins/watcher/public/models/action/logging_action.js b/x-pack/plugins/watcher/public/models/action/logging_action.js index 18a4f4a1c8940..07aae9470a5df 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -18,7 +18,7 @@ export class LoggingAction extends BaseAction { context: '{{ctx.metadata.name}}', } }); - this.text = get(props, 'text', defaultText); + this.text = get(props, 'text', props.ignoreDefaults ? null : defaultText); } validate() { diff --git a/x-pack/plugins/watcher/public/models/action/pagerduty_action.js b/x-pack/plugins/watcher/public/models/action/pagerduty_action.js index 370c5310c5ea6..5500899720984 100644 --- a/x-pack/plugins/watcher/public/models/action/pagerduty_action.js +++ b/x-pack/plugins/watcher/public/models/action/pagerduty_action.js @@ -19,7 +19,7 @@ export class PagerDutyAction extends BaseAction { context: '{{ctx.metadata.name}}', } }); - this.description = get(props, 'description', defaultDescription); + this.description = get(props, 'description', props.ignoreDefaults ? null : defaultDescription); } validate() { diff --git a/x-pack/plugins/watcher/public/models/action/slack_action.js b/x-pack/plugins/watcher/public/models/action/slack_action.js index b0ad097ac3f01..ec11e5c10c818 100644 --- a/x-pack/plugins/watcher/public/models/action/slack_action.js +++ b/x-pack/plugins/watcher/public/models/action/slack_action.js @@ -21,7 +21,7 @@ export class SlackAction extends BaseAction { context: '{{ctx.metadata.name}}', } }); - this.text = props.text || defaultText; + this.text = get(props, 'text', props.ignoreDefaults ? null : defaultText); } validate() { diff --git a/x-pack/plugins/watcher/public/models/action/webhook_action.js b/x-pack/plugins/watcher/public/models/action/webhook_action.js index d1fba0700f6f6..c675cbb847cde 100644 --- a/x-pack/plugins/watcher/public/models/action/webhook_action.js +++ b/x-pack/plugins/watcher/public/models/action/webhook_action.js @@ -14,14 +14,15 @@ export class WebhookAction extends BaseAction { super(props); const defaultJson = JSON.stringify({ message: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' }, null, 2); - this.body = get(props, 'body', defaultJson); + this.body = get(props, 'body', props.ignoreDefaults ? null : defaultJson); this.method = get(props, 'method'); this.host = get(props, 'host'); this.port = get(props, 'port'); this.path = get(props, 'path'); + this.contentType = get(props, 'contentType'); - this.fullPath = `${this.host}${this.port}${this.path}`; + this.fullPath = `${this.host}:${this.port}${this.path}`; } validate() { @@ -45,7 +46,7 @@ export class WebhookAction extends BaseAction { }) ); } - if (typeof this.body === 'string' && this.body !== '') { + if (this.contentType === 'application/json' && typeof this.body === 'string' && this.body !== '') { try { const parsedJson = JSON.parse(this.body); if (parsedJson && typeof parsedJson !== 'object') { diff --git a/x-pack/plugins/watcher/public/models/watch/base_watch.js b/x-pack/plugins/watcher/public/models/watch/base_watch.js index 14c269b197599..5596e50a137fc 100644 --- a/x-pack/plugins/watcher/public/models/watch/base_watch.js +++ b/x-pack/plugins/watcher/public/models/watch/base_watch.js @@ -41,7 +41,7 @@ export class BaseWatch { this.watchStatus = watchStatus; } - createAction = (type) => { + createAction = (type, defaults) => { const ActionTypes = Action.getActionTypes(); const ActionType = ActionTypes[type]; @@ -55,7 +55,7 @@ export class BaseWatch { } const id = createActionId(this.actions, type); - const props = { id, type }; + const props = { id, type, ...defaults }; const action = new ActionType(props); this.addAction(action); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx index 21befcfa59104..dbf929c796779 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx @@ -111,7 +111,10 @@ const JsonWatchEditUi = ({ pageTitle }: { pageTitle: string }) => { onClick={() => { setSelectedTab(tab.id); setExecuteDetails( - new ExecuteDetails({ ...executeDetails, actionModes: getActionModes(watchActions) }) + new ExecuteDetails({ + ...executeDetails, + actionModes: getActionModes(watchActions), + }) ); }} isSelected={tab.id === selectedTab} diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx index 6cc7f27869368..b4e65609efffc 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.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 } from 'react'; +import React from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; @@ -24,34 +24,32 @@ export const IndexActionFields: React.FunctionComponent = ({ }) => { const { index } = action; return ( - - + ) => { + editAction({ key: 'index', value: e.target.value }); + }} + onBlur={() => { + if (!index) { + editAction({ key: 'index', value: '' }); } - )} - > - ) => { - editAction({ key: 'index', value: e.target.value }); - }} - onBlur={() => { - if (!index) { - editAction({ key: 'index', value: '' }); - } - }} - /> - - + }} + /> +
); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx index 09b70fe26a313..ec746d0cb2ecb 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx @@ -5,7 +5,7 @@ */ import React, { Fragment } from 'react'; -import { EuiFieldText, EuiFormRow } from '@elastic/eui'; +import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; import { JiraAction } from '../../../../../../common/types/action_types'; @@ -25,29 +25,11 @@ export const JiraActionFields: React.FunctionComponent = ({ hasErrors, children, }) => { - const { account, projectKey, issueType, summary } = action; + const { projectKey, issueType, summary } = action; return ( {children} - - { - editAction({ key: 'account', value: e.target.value }); - }} - /> - = ({ }) => { const { text } = action; return ( - - + ) => { + editAction({ key: 'text', value: e.target.value }); + }} + onBlur={() => { + if (!text) { + editAction({ key: 'text', value: '' }); } - )} - > - ) => { - editAction({ key: 'text', value: e.target.value }); - }} - onBlur={() => { - if (!text) { - editAction({ key: 'text', value: '' }); - } - }} - /> - - + }} + /> + ); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx index 919dd73d3a03d..905071f142194 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.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 } from 'react'; +import React, { Fragment, useEffect } from 'react'; import { EuiCodeEditor, EuiFieldNumber, EuiFieldText, EuiFormRow, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -17,6 +17,8 @@ interface Props { hasErrors: boolean; } +const HTTP_VERBS = ['head', 'get', 'post', 'put', 'delete']; + export const WebhookActionFields: React.FunctionComponent = ({ action, editAction, @@ -25,6 +27,10 @@ export const WebhookActionFields: React.FunctionComponent = ({ }) => { const { method, host, port, path, body } = action; + useEffect(() => { + editAction({ key: 'contentType', value: 'application/json' }); // set content-type for threshold watch to json by default + }, []); + return ( = ({ fullWidth name="method" value={method || 'get'} - options={[ - { - text: 'HEAD', - value: 'head', - }, - { - text: 'GET', - value: 'get', - }, - { - text: 'POST', - value: 'post', - }, - { - text: 'PUT', - value: 'put', - }, - { - text: 'DELETE', - value: 'delete', - }, - ]} + options={HTTP_VERBS.map(verb => ({ text: verb.toUpperCase(), value: verb }))} onChange={e => { editAction({ key: 'method', value: e.target.value }); }} diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx index 61f6ceb7fb516..0aebf7200847f 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx @@ -25,6 +25,7 @@ import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_d import { Action } from 'plugins/watcher/models/action'; import { toastNotifications } from 'ui/notify'; import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; +import { ThresholdWatch } from 'plugins/watcher/models/watch/threshold_watch'; import { ActionType } from '../../../../../common/types/action_types'; import { ACTION_TYPES, ACTION_MODES } from '../../../../../common/constants'; import { WatchContext } from '../../watch_context'; @@ -40,7 +41,7 @@ import { import { executeWatch } from '../../../../lib/api'; import { watchActionsConfigurationMap } from '../../../../lib/documentation_links'; -const ActionFieldsComponentMap = { +const actionFieldsComponentMap = { [ACTION_TYPES.LOGGING]: LoggingActionFields, [ACTION_TYPES.SLACK]: SlackActionFields, [ACTION_TYPES.EMAIL]: EmailActionFields, @@ -58,65 +59,57 @@ interface Props { }; }; } | null; + actionErrors: { + [key: string]: { + [key: string]: string[]; + }; + }; } -export const WatchActionsAccordion: React.FunctionComponent = ({ settings }) => { +export const WatchActionsAccordion: React.FunctionComponent = ({ + settings, + actionErrors, +}) => { const { watch, setWatchProperty } = useContext(WatchContext); const { actions } = watch; - const ButtonContent = ({ - name, - iconClass, - }: { - name: string; - iconClass: 'loggingApp' | 'logoWebhook' | 'logoSlack' | 'apps' | 'email' | 'indexOpen'; - }) => ( - - - - - - - -
{name}
-
-
-
-
- ); - - const DeleteIcon = ({ onDelete }: { onDelete: () => void }) => ( - onDelete()} - /> - ); - if (actions && actions.length >= 1) { return actions.map((action: any) => { - const FieldsComponent = ActionFieldsComponentMap[action.type]; - const errors = action.validate(); + const FieldsComponent = actionFieldsComponentMap[action.type]; + const errors = actionErrors[action.id]; const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); return ( } + buttonContent={ + + + + + + +
{action.typeName}
+
+
+
+ } extraAction={ - { + { const updatedActions = actions.filter( (actionItem: ActionType) => actionItem.id !== action.id ); @@ -188,18 +181,22 @@ export const WatchActionsAccordion: React.FunctionComponent = ({ settings type="submit" isDisabled={hasErrors} onClick={async () => { - const actionModes = watch.actions.reduce((acc: any, actionItem: ActionType) => { - acc[action.id] = - action === actionItem ? ACTION_MODES.FORCE_EXECUTE : ACTION_MODES.SKIP; - return acc; - }, {}); + const selectedWatchAction = watch.actions.filter( + (watchAction: any) => watchAction.id === action.id + ); const executeDetails = new ExecuteDetails({ ignoreCondition: true, - actionModes, recordExecution: false, + actionModes: { + [action.id]: ACTION_MODES.FORCE_EXECUTE, + }, + }); + const newExecuteWatch = new ThresholdWatch({ + ...watch, + actions: selectedWatchAction, }); try { - const executedWatch = await executeWatch(executeDetails, watch); + const executedWatch = await executeWatch(executeDetails, newExecuteWatch); const executeResults = WatchHistoryItem.fromUpstreamJson( executedWatch.watchHistoryItem ); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx index 4fe01be933d0e..a9f1849945d6d 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx @@ -41,23 +41,23 @@ interface Props { }; }; } | null; + isLoading: boolean; } -export const WatchActionsDropdown: React.FunctionComponent = ({ settings }) => { +export const WatchActionsDropdown: React.FunctionComponent = ({ settings, isLoading }) => { const { addAction } = useContext(WatchContext); - const allActionTypes = Action.getActionTypes(); + const allActionTypes = Action.getActionTypes() as Record; - const actions = Object.keys(allActionTypes).map(actionKey => { - const { typeName, iconClass, selectMessage } = allActionTypes[actionKey]; - return { - type: actionKey, + const actions = Object.entries(allActionTypes).map( + ([type, { typeName, iconClass, selectMessage }]) => ({ + type, typeName, iconClass, selectMessage, - isEnabled: settings ? settings.actionTypes[actionKey].enabled : true, - }; - }); + isEnabled: settings ? settings.actionTypes[type].enabled : true, + }) + ); const actionOptions = actions ? actions.map((action: any) => { @@ -90,11 +90,11 @@ export const WatchActionsDropdown: React.FunctionComponent = ({ settings options={actionOptionsWithEmptyValue} valueOfSelected={EMPTY_FIRST_OPTION_VALUE} onChange={(value: string) => { - addAction({ type: value }); + addAction({ type: value, defaults: { isNew: true } }); }} itemLayoutAlign="top" hasDividers - isLoading={!actions} + isLoading={isLoading} /> ); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx index 35936680d3ce2..b9cec2313d4de 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx @@ -5,31 +5,24 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { Fragment, useContext, useEffect, useState } from 'react'; -import { fetchSettings } from '../../../../lib/api'; +import React, { Fragment, useContext } from 'react'; +import { loadSettings } from '../../../../lib/api'; import { WatchActionsDropdown } from './threshold_watch_action_dropdown'; import { WatchActionsAccordion } from './threshold_watch_action_accordion'; import { WatchContext } from '../../watch_context'; -export const WatchActionsPanel = () => { - const { watch } = useContext(WatchContext); - - const [settings, setSettings] = useState<{ - actionTypes: { - [key: string]: { - enabled: boolean; - }; +interface Props { + actionErrors: { + [key: string]: { + [key: string]: string[]; }; - } | null>(null); - - const getSettings = async () => { - const actionSettings = await fetchSettings(); - setSettings(actionSettings); }; +} + +export const WatchActionsPanel: React.FunctionComponent = ({ actionErrors }) => { + const { watch } = useContext(WatchContext); - useEffect(() => { - getSettings(); - }, []); + const { data: settings, isLoading } = loadSettings(); return ( @@ -48,11 +41,11 @@ export const WatchActionsPanel = () => { - +
- + ); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx index 6bdf029e2e921..f85c53192b60f 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx @@ -151,6 +151,16 @@ const ThresholdWatchEditUi = ({ intl, pageTitle }: { intl: InjectedIntl; pageTit }, []); const { errors } = watch.validate(); const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + const actionErrors = watch.actions.reduce((acc: any, action: any) => { + const actionValidationErrors = action.validate(); + acc[action.id] = actionValidationErrors; + return acc; + }, {}); + const hasActionErrors = !!Object.keys(actionErrors).find(actionError => { + return !!Object.keys(actionErrors[actionError]).find((actionErrorKey: string) => { + return actionErrors[actionError][actionErrorKey].length >= 1; + }); + }); const expressionErrorMessage = i18n.translate( 'xpack.watcher.thresholdWatchExpression.fixErrorInExpressionBelowValidationMessage', { @@ -792,7 +802,7 @@ const ThresholdWatchEditUi = ({ intl, pageTitle }: { intl: InjectedIntl; pageTit {hasErrors ? null : } - + ) : null} @@ -801,7 +811,7 @@ const ThresholdWatchEditUi = ({ intl, pageTitle }: { intl: InjectedIntl; pageTit { const savedWatch = await onWatchSave(watch, licenseService); if (savedWatch && savedWatch.validationError) { diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx index bf2799db12838..57370de0aefc4 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx @@ -66,9 +66,9 @@ const watchReducer = (state: any, action: any) => { } case 'addAction': - const { type } = payload; + const { type, defaults } = payload; const newWatch = new (Watch.getWatchTypes())[watch.type](watch); - newWatch.createAction(type); + newWatch.createAction(type, defaults); return { ...state, watch: newWatch, diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts b/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts index 08cb13edb01ed..a36c1c2717970 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts +++ b/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; +import { get } from 'lodash'; import { ACTION_TYPES, WATCH_TYPES } from '../../../common/constants'; import { BaseWatch } from '../../../common/types/watch_types'; import { createWatch, loadWatch } from '../../lib/api'; @@ -32,6 +33,15 @@ function getPropsFromAction(type: string, action: { [key: string]: any }) { // Slack action has its props inside the "message" object return action[type].message; } + + if (type === ACTION_TYPES.JIRA) { + // Jira action has its required props inside the "fields" object + const jiraAction: { projectKey?: string; issueType?: string; summary?: string } = {}; + jiraAction.projectKey = get(action[type], 'fields.project.key'); + jiraAction.issueType = get(action[type], 'fields.issuetype.name'); + jiraAction.summary = get(action[type], 'fields.summary'); + return jiraAction; + } return action[type]; } @@ -49,7 +59,7 @@ function createActionsForWatch(watchInstance: BaseWatch) { Object.keys(watchInstance.watch.actions).forEach(k => { action = watchInstance.watch.actions[k]; type = getTypeFromAction(action); - actionProps = getPropsFromAction(type, action); + actionProps = { ...getPropsFromAction(type, action), ignoreDefaults: true }; watchInstance.createAction(type, actionProps); }); return watchInstance; diff --git a/x-pack/plugins/watcher/server/models/action/jira_action.js b/x-pack/plugins/watcher/server/models/action/jira_action.js index ae150242dfa9d..4e8d80599cea7 100644 --- a/x-pack/plugins/watcher/server/models/action/jira_action.js +++ b/x-pack/plugins/watcher/server/models/action/jira_action.js @@ -13,7 +13,6 @@ export class JiraAction extends BaseAction { props.type = ACTION_TYPES.JIRA; super(props, errors); - this.account = props.account; this.projectKey = props.projectKey; this.issueType = props.issueType; this.summary = props.summary; @@ -23,7 +22,6 @@ export class JiraAction extends BaseAction { get downstreamJson() { const result = super.downstreamJson; Object.assign(result, { - account: this.account, projectKey: this.projectKey, issueType: this.issueType, summary: this.summary, @@ -38,7 +36,6 @@ export class JiraAction extends BaseAction { const { errors } = this.validateJson(json); Object.assign(props, { - account: json.account, projectKey: json.projectKey, issueType: json.issueType, summary: json.summary, @@ -52,12 +49,6 @@ export class JiraAction extends BaseAction { get upstreamJson() { const result = super.upstreamJson; - const optionalFields = {}; - - if (this.account) { - optionalFields.account = this.account; - } - result[this.id] = { jira: { fields: { @@ -69,7 +60,6 @@ export class JiraAction extends BaseAction { }, summary: this.summary, }, - ...optionalFields, } }; @@ -81,17 +71,10 @@ export class JiraAction extends BaseAction { const props = super.getPropsFromUpstreamJson(json); const { errors } = this.validateJson(json.actionJson); - const optionalFields = {}; - - if (json.actionJson.account) { - optionalFields.account = json.actionJson.account; - } - Object.assign(props, { projectKey: json.actionJson.jira.fields.project.key, issueType: json.actionJson.jira.fields.issuetype.name, summary: json.actionJson.jira.fields.summary, - ...optionalFields, }); const action = new JiraAction(props, errors); diff --git a/x-pack/plugins/watcher/server/models/action/webhook_action.js b/x-pack/plugins/watcher/server/models/action/webhook_action.js index 1b1567f6b66d8..c9463b0d64725 100644 --- a/x-pack/plugins/watcher/server/models/action/webhook_action.js +++ b/x-pack/plugins/watcher/server/models/action/webhook_action.js @@ -18,6 +18,7 @@ export class WebhookAction extends BaseAction { this.port = props.port; this.path = props.path; this.body = props.body; + this.contentType = props.contentType; } // To Kibana @@ -29,6 +30,7 @@ export class WebhookAction extends BaseAction { port: this.port, path: this.path, body: this.body, + contentType: this.contentType, }); return result; } @@ -44,6 +46,7 @@ export class WebhookAction extends BaseAction { port: json.port, path: json.path, body: json.body, + contentType: json.contentType, }); const action = new WebhookAction(props, errors); @@ -64,17 +67,20 @@ export class WebhookAction extends BaseAction { if (this.body) { optionalFields.body = this.body; } + if (this.contentType) { + optionalFields.headers = { + 'Content-Type': this.contentType, + }; + } result[this.id] = { webhook: { host: this.host, port: this.port, ...optionalFields, - headers: { - 'Content-Type': 'application/json', - }, } }; + return result; } @@ -94,6 +100,9 @@ export class WebhookAction extends BaseAction { if (json.actionJson.webhook.body) { optionalFields.body = json.actionJson.webhook.body; } + if (json.actionJson.webhook.headers['Content-Type']) { + optionalFields.contentType = json.actionJson.webhook.headers['Content-Type']; + } Object.assign(props, { host: json.actionJson.webhook.host, diff --git a/x-pack/plugins/watcher/server/models/settings/__tests__/settings.js b/x-pack/plugins/watcher/server/models/settings/__tests__/settings.js index 9c03e4d58d721..9757bc84bc888 100644 --- a/x-pack/plugins/watcher/server/models/settings/__tests__/settings.js +++ b/x-pack/plugins/watcher/server/models/settings/__tests__/settings.js @@ -19,7 +19,7 @@ describe('settings module', () => { expect(actionTypes.webhook.enabled).to.be(true); expect(actionTypes.index.enabled).to.be(true); expect(actionTypes.logging.enabled).to.be(true); - expect(actionTypes.slack.enabled).to.be(true); + expect(actionTypes.slack.enabled).to.be(false); expect(actionTypes.jira.enabled).to.be(false); expect(actionTypes.pagerduty.enabled).to.be(false); }); @@ -93,7 +93,7 @@ describe('settings module', () => { expect(json.action_types.webhook.enabled).to.be(true); expect(json.action_types.index.enabled).to.be(true); expect(json.action_types.logging.enabled).to.be(true); - expect(json.action_types.slack.enabled).to.be(true); + expect(json.action_types.slack.enabled).to.be(false); expect(json.action_types.jira.enabled).to.be(false); expect(json.action_types.pagerduty.enabled).to.be(false); }); From 3aca3204b35696ec976a582804864afafdb591ab Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Wed, 1 May 2019 10:41:29 -0400 Subject: [PATCH 13/13] fix linting error --- .../action_fields/logging_action_fields.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx index 48c1888e2d16f..923d3e8f627d5 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.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 } from 'react'; +import React from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors';