diff --git a/x-pack/plugins/watcher/common/constants/error_codes.js b/x-pack/plugins/watcher/common/constants/error_codes.js new file mode 100644 index 0000000000000..2fa875549358f --- /dev/null +++ b/x-pack/plugins/watcher/common/constants/error_codes.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ERROR_CODES = { + + // Property missing on object + ERR_PROP_MISSING: 'ERR_PROP_MISSING', +}; diff --git a/x-pack/plugins/watcher/common/constants/index.js b/x-pack/plugins/watcher/common/constants/index.js index 3324b9f3cf427..4260688c6eb45 100644 --- a/x-pack/plugins/watcher/common/constants/index.js +++ b/x-pack/plugins/watcher/common/constants/index.js @@ -22,3 +22,4 @@ export { WATCH_STATE_COMMENTS } from './watch_state_comments'; export { WATCH_HISTORY } from './watch_history'; export { WATCH_STATES } from './watch_states'; export { WATCH_TYPES } from './watch_types'; +export { ERROR_CODES } from './error_codes'; 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 fbcfdaa4123e8..31fe6a9405e0b 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,10 @@ export class EmailAction extends BaseAction { Object.assign(result, { to: this.to, subject: this.subject, - body: this.body + body: this.body, + email: { + to: this.to.length ? this.to : undefined, + } }); return result; 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 41fd86d668979..603784053e717 100644 --- a/x-pack/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/models/action/logging_action.js @@ -17,9 +17,13 @@ export class LoggingAction extends BaseAction { get upstreamJson() { const result = super.upstreamJson; + const text = !!this.text.trim() ? this.text : undefined; Object.assign(result, { - text: this.text + text, + logging: { + text, + } }); return result; 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 6f8146c24d354..c7552628dba3d 100644 --- a/x-pack/plugins/watcher/public/models/action/slack_action.js +++ b/x-pack/plugins/watcher/public/models/action/slack_action.js @@ -17,12 +17,36 @@ export class SlackAction extends BaseAction { this.text = props.text; } + validate() { + const errors = []; + + if (!this.to.length) { + errors.push({ + message: i18n.translate('xpack.watcher.sections.watchEdit.json.warningPossibleInvalidSlackAction.description', { + defaultMessage: `You are saving a watch with a "Slack" Action but you haven't defined a "to" field. + Unless you have specified a Slack message_default "to" property in your Elasticsearch settings, + this will result in an invalid watch.` + }) + }); + } + + return { errors: errors.length ? errors : null }; + } + get upstreamJson() { const result = super.upstreamJson; - + const message = this.text || this.to.length + ? { + text: this.text, + to: this.to.length ? this.to : undefined + } + : undefined; Object.assign(result, { to: this.to, - text: this.text + text: this.text, + slack: { + message + } }); return result; 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 9f1bcecb3f911..ca8149f58e73b 100644 --- a/x-pack/plugins/watcher/public/models/watch/base_watch.js +++ b/x-pack/plugins/watcher/public/models/watch/base_watch.js @@ -78,6 +78,10 @@ export class BaseWatch { remove(this.actions, action); } + resetActions = () => { + this.actions = []; + }; + get displayName() { if (this.isNew) { return i18n.translate('xpack.watcher.models.baseWatch.displayName', { @@ -132,6 +136,49 @@ 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 errorActionsFragment = actionsErrors.reduce((message, error) => ( + !!message + ? `${message}, ${error.message}` + : error.message + ), ''); + + // We are not doing any *blocking* validation in the client, + // so for now we return the errors as a warning + return { + warning: { + message: i18n.translate('xpack.watcher.models.baseWatch.invalidWatchWarningMessageText', { + defaultMessage: 'Warning: {errorActionsFragment} Are you sure you want to save the watch in its current state?', + values: { + errorActionsFragment, + } + }) + } + }; + } + static typeName = i18n.translate('xpack.watcher.models.baseWatch.typeName', { defaultMessage: 'Watch', }); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.js b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.js index 9ff9382f8a999..4696838706694 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.js +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.js @@ -18,6 +18,7 @@ import '../watch_edit_execute_detail'; import '../watch_edit_actions_execute_summary'; import '../watch_edit_watch_execute_summary'; import 'plugins/watcher/services/license'; +import { ACTION_TYPES } from '../../../../../common/constants'; const app = uiModules.get('xpack/watcher'); @@ -100,14 +101,16 @@ app.directive('jsonWatchEdit', function ($injector, i18n) { } onWatchSave = () => { + this.createActionsForWatch(this.watch); + if (!this.watch.isNew) { - return this.saveWatch(); + return this.validateWatch(); } return this.isExistingWatch() .then(existingWatch => { if (!existingWatch) { - return this.saveWatch(); + return this.validateWatch(); } const confirmModalOptions = { @@ -152,6 +155,23 @@ app.directive('jsonWatchEdit', function ($injector, i18n) { }); } + validateWatch = () => { + const { warning } = this.watch.validate(); + + if (!warning) { + return this.saveWatch(); + } + + const confirmModalOptions = { + onConfirm: this.saveWatch, + confirmButtonText: i18n('xpack.watcher.sections.watchEdit.json.watchErrorsWarning.confirmSaveWatch', { + defaultMessage: 'Yes', + }), + }; + + return confirmModal(warning.message, confirmModalOptions); + } + saveWatch = () => { return watchService.saveWatch(this.watch) .then(() => { @@ -211,6 +231,60 @@ app.directive('jsonWatchEdit', function ($injector, i18n) { // dirtyPrompt.deregister(); kbnUrl.change('/management/elasticsearch/watcher/watches', {}); } + + /** + * Actions instances are not automatically added to the Watch _actions_ Array + * when we add them in the Json editor. + * This method takes takes care of it. + * + * @param watchModel Watch instance + * @return Watch instance + */ + createActionsForWatch(watchInstance) { + watchInstance.resetActions(); + + let action; + let type; + let actionProps; + + Object.keys(watchInstance.watch.actions).forEach((k) => { + action = watchInstance.watch.actions[k]; + type = this.getTypeFromAction(action); + actionProps = this.getPropsFromAction(type, action); + + watchInstance.createAction(type, actionProps); + }); + + return watchInstance; + } + + /** + * Get the type from an action where a key defines its type. + * eg: { email: { ... } } | { slack: { ... } } + * + * @param action A raw action object + * @return {string} The action type + */ + getTypeFromAction(action) { + const actionKeys = Object.keys(action); + let type; + + Object.keys(ACTION_TYPES).forEach((k) => { + if (actionKeys.includes(ACTION_TYPES[k])) { + type = ACTION_TYPES[k]; + } + }); + + return type ? type : ACTION_TYPES.UNKNOWN; + } + + getPropsFromAction(type, action) { + if (type === ACTION_TYPES.SLACK) { + // Slack action has its props inside the "message" object + return action[type].message; + } + return action[type]; + } } }; }); diff --git a/x-pack/plugins/watcher/server/models/action/__tests__/action.js b/x-pack/plugins/watcher/server/models/action/__tests__/action.js deleted file mode 100644 index 747f4feac7b0f..0000000000000 --- a/x-pack/plugins/watcher/server/models/action/__tests__/action.js +++ /dev/null @@ -1,108 +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 expect from 'expect.js'; -import { Action } from '../action'; -import { ACTION_TYPES } from '../../../../common/constants'; - -describe('action', () => { - - describe('Action', () => { - - describe('fromUpstreamJson factory method', () => { - - let upstreamJson; - beforeEach(() => { - upstreamJson = { - id: 'my-action', - actionJson: { - "logging": { - "text": "foo" - } - } - }; - }); - - it(`throws an error if no 'id' property in json`, () => { - delete upstreamJson.id; - expect(Action.fromUpstreamJson).withArgs(upstreamJson) - .to.throwError(/must contain an id property/i); - }); - - it(`throws an error if no 'actionJson' property in json`, () => { - delete upstreamJson.actionJson; - expect(Action.fromUpstreamJson).withArgs(upstreamJson) - .to.throwError(/must contain an actionJson property/i); - }); - - it('returns correct Action instance', () => { - const action = Action.fromUpstreamJson(upstreamJson); - - expect(action.id).to.be(upstreamJson.id); - }); - - }); - - describe('type getter method', () => { - - it(`returns a value from ACTION_TYPES when there is a valid model class`, () => { - const upstreamJson = { - id: 'my-action', - actionJson: { - logging: { - 'text': 'foo' - } - } - }; - const action = Action.fromUpstreamJson(upstreamJson); - - expect(action.type).to.be(ACTION_TYPES.LOGGING); - }); - - it(`returns ACTION_TYPES.UNKNOWN when there is no valid model class`, () => { - const upstreamJson = { - id: 'my-action', - actionJson: { - unknown_action_type: { - 'foo': 'bar' - } - } - }; - const action = Action.fromUpstreamJson(upstreamJson); - - expect(action.type).to.be(ACTION_TYPES.UNKNOWN); - }); - - }); - - describe('downstreamJson getter method', () => { - - let upstreamJson; - beforeEach(() => { - upstreamJson = { - id: 'my-action', - actionJson: { - "logging": { - "text": "foo" - } - } - }; - }); - - it('returns correct JSON for client', () => { - const action = Action.fromUpstreamJson(upstreamJson); - - const json = action.downstreamJson; - - expect(json.id).to.be(action.id); - expect(json.type).to.be(action.type); - }); - - }); - - }); - -}); diff --git a/x-pack/plugins/watcher/server/models/action/action.js b/x-pack/plugins/watcher/server/models/action/action.js index e69a278873dd6..a5d677518da26 100644 --- a/x-pack/plugins/watcher/server/models/action/action.js +++ b/x-pack/plugins/watcher/server/models/action/action.js @@ -26,7 +26,18 @@ export class Action { } // From Elasticsearch - static fromUpstreamJson(json, options) { + static fromUpstreamJson(json, options = { throwExceptions: {} }) { + if (!json.id) { + throw badRequest( + i18n.translate('xpack.watcher.models.actionStatus.absenceOfIdPropertyBadRequestMessage', { + defaultMessage: 'json argument must contain an {id} property', + values: { + id: 'id' + } + }), + ); + } + if (!json.actionJson) { throw badRequest( i18n.translate('xpack.watcher.models.action.absenceOfActionJsonPropertyBadRequestMessage', { @@ -40,13 +51,38 @@ export class Action { const type = getActionType(json.actionJson); const ActionType = ActionTypes[type] || UnknownAction; - return ActionType.fromUpstreamJson(json, options); + + const { action, errors } = ActionType.fromUpstreamJson(json, options); + const doThrowException = options.throwExceptions.Action !== false; + + if (errors && doThrowException) { + this.throwErrors(errors); + } + + return action; } // From Kibana - static fromDownstreamJson(json) { + static fromDownstreamJson(json, options = { throwExceptions: {} }) { const ActionType = ActionTypes[json.type] || UnknownAction; - return ActionType.fromDownstreamJson(json); + const { action, errors } = ActionType.fromDownstreamJson(json); + const doThrowException = options.throwExceptions.Action !== false; + + if (errors && doThrowException) { + this.throwErrors(errors); + } + + return action; + } + + static throwErrors(errors) { + const allMessages = errors.reduce((message, error) => { + if (message) { + return `${message}, ${error.message}`; + } + return error.message; + }, ''); + throw badRequest(allMessages); } } diff --git a/x-pack/plugins/watcher/server/models/action/action.test.js b/x-pack/plugins/watcher/server/models/action/action.test.js new file mode 100644 index 0000000000000..8771c053b574d --- /dev/null +++ b/x-pack/plugins/watcher/server/models/action/action.test.js @@ -0,0 +1,136 @@ +/* + * 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 } from './action'; +import { LoggingAction } from './logging_action'; +import { ACTION_TYPES } from '../../../common/constants'; + +jest.mock('./logging_action', () => ({ + LoggingAction: { + fromUpstreamJson: jest.fn(({ id }) => ({ + errors: null, + action: { id, type: 'logging' }, + })), + } +})); + +describe('action', () => { + + describe('Action', () => { + + describe('fromUpstreamJson factory method', () => { + + let upstreamJson; + beforeEach(() => { + upstreamJson = { + id: 'my-action', + actionJson: { + "logging": { + "text": "foo" + } + } + }; + }); + + it(`throws an error if no 'id' property in json`, () => { + delete upstreamJson.id; + expect(() => { + Action.fromUpstreamJson(upstreamJson); + }).toThrowError(/must contain an id property/i); + }); + + it(`throws an error if no 'actionJson' property in json`, () => { + delete upstreamJson.actionJson; + expect(() => { + Action.fromUpstreamJson(upstreamJson); + }).toThrowError(/must contain an actionJson property/i); + }); + + it(`throws an error if an Action is invalid`, () => { + const message = 'Missing prop in Logging Action!'; + + LoggingAction.fromUpstreamJson.mockReturnValueOnce({ + errors: [{ message }], + action: {}, + }); + + expect(() => { + Action.fromUpstreamJson(upstreamJson); + }).toThrowError(message); + }); + + it('returns correct Action instance', () => { + const action = Action.fromUpstreamJson(upstreamJson); + + expect(action.id).toBe(upstreamJson.id); + }); + + }); + + describe('type getter method', () => { + + it(`returns the correct known Action type`, () => { + const options = { throwExceptions: { Action: false } }; + + const upstreamLoggingJson = { id: 'action1', actionJson: { logging: {} } }; + const loggingAction = Action.fromUpstreamJson(upstreamLoggingJson, options); + + const upstreamEmailJson = { id: 'action2', actionJson: { email: {} } }; + const emailAction = Action.fromUpstreamJson(upstreamEmailJson, options); + + const upstreamSlackJson = { id: 'action3', actionJson: { slack: {} } }; + const slackAction = Action.fromUpstreamJson(upstreamSlackJson, options); + + expect(loggingAction.type).toBe(ACTION_TYPES.LOGGING); + expect(emailAction.type).toBe(ACTION_TYPES.EMAIL); + expect(slackAction.type).toBe(ACTION_TYPES.SLACK); + }); + + it(`returns ACTION_TYPES.UNKNOWN when there is no valid model class`, () => { + const upstreamJson = { + id: 'my-action', + actionJson: { + unknown_action_type: { + 'foo': 'bar' + } + } + }; + const action = Action.fromUpstreamJson(upstreamJson); + + expect(action.type).toBe(ACTION_TYPES.UNKNOWN); + }); + + }); + + describe('downstreamJson getter method', () => { + + let upstreamJson; + beforeEach(() => { + upstreamJson = { + id: 'my-action', + actionJson: { + "email": { + "to": "elastic@elastic.co" + } + } + }; + }); + + it('returns correct JSON for client', () => { + + const action = Action.fromUpstreamJson(upstreamJson); + + const json = action.downstreamJson; + + expect(json.id).toBe(action.id); + expect(json.type).toBe(action.type); + }); + + }); + + }); + +}); diff --git a/x-pack/plugins/watcher/server/models/action/email_action.js b/x-pack/plugins/watcher/server/models/action/email_action.js index bd9566fe70ce0..5f36b88eb4be1 100644 --- a/x-pack/plugins/watcher/server/models/action/email_action.js +++ b/x-pack/plugins/watcher/server/models/action/email_action.js @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { badRequest } from 'boom'; import { BaseAction } from './base_action'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES, ERROR_CODES } from '../../../common/constants'; import { i18n } from '@kbn/i18n'; export class EmailAction extends BaseAction { @@ -34,6 +33,7 @@ export class EmailAction extends BaseAction { // From Kibana static fromDownstreamJson(json) { const props = super.getPropsFromDownstreamJson(json); + const { errors } = this.validateJson(json); Object.assign(props, { to: json.to, @@ -41,7 +41,8 @@ export class EmailAction extends BaseAction { body: json.body, }); - return new EmailAction(props); + const action = new EmailAction(props, errors); + return { action, errors }; } // To Elasticsearch @@ -68,10 +69,9 @@ export class EmailAction extends BaseAction { } // From Elasticsearch - static fromUpstreamJson(json, options = { throwExceptions: {} }) { + static fromUpstreamJson(json) { const props = super.getPropsFromUpstreamJson(json); - const doThrowException = options.throwExceptions.Action !== false; - const { errors } = this.validateJson(json, doThrowException); + const { errors } = this.validateJson(json.actionJson); const optionalFields = {}; if (json.actionJson.email.subject) { @@ -87,13 +87,15 @@ export class EmailAction extends BaseAction { ...optionalFields, }); - return new EmailAction(props, errors); + const action = new EmailAction(props, errors); + + return { action, errors }; } - static validateJson(json, doThrowException) { + static validateJson(json) { const errors = []; - if (!json.actionJson.email) { + if (!json.email) { const message = i18n.translate('xpack.watcher.models.emailAction.absenceOfActionJsonEmailPropertyBadRequestMessage', { defaultMessage: 'json argument must contain an {actionJsonEmail} property', values: { @@ -101,17 +103,15 @@ export class EmailAction extends BaseAction { } }); - if (doThrowException) { - throw badRequest(message); - } - errors.push({ - code: 'ERR_PROP_MISSING', + code: ERROR_CODES.ERR_PROP_MISSING, message }); + + json.email = {}; } - if (!json.actionJson.email.to) { + if (!json.email.to) { const message = i18n.translate('xpack.watcher.models.emailAction.absenceOfActionJsonEmailToPropertyBadRequestMessage', { defaultMessage: 'json argument must contain an {actionJsonEmailTo} property', values: { @@ -119,11 +119,8 @@ export class EmailAction extends BaseAction { } }); - if (doThrowException) { - throw badRequest(message); - } errors.push({ - code: 'ERR_PROP_MISSING', + code: ERROR_CODES.ERR_PROP_MISSING, message }); } diff --git a/x-pack/plugins/watcher/server/models/action/logging_action.js b/x-pack/plugins/watcher/server/models/action/logging_action.js index fc1f02ad73931..62496d2f85f7b 100644 --- a/x-pack/plugins/watcher/server/models/action/logging_action.js +++ b/x-pack/plugins/watcher/server/models/action/logging_action.js @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { badRequest } from 'boom'; import { BaseAction } from './base_action'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES, ERROR_CODES } from '../../../common/constants'; import { i18n } from '@kbn/i18n'; export class LoggingAction extends BaseAction { - constructor(props) { + constructor(props, errors) { props.type = ACTION_TYPES.LOGGING; - super(props); + super(props, errors); this.text = props.text; } @@ -30,12 +29,14 @@ export class LoggingAction extends BaseAction { // From Kibana static fromDownstreamJson(json) { const props = super.getPropsFromDownstreamJson(json); + const { errors } = this.validateJson(json); Object.assign(props, { text: json.text }); - return new LoggingAction(props); + const action = new LoggingAction(props, errors); + return { action, errors }; } // To Elasticsearch @@ -54,33 +55,46 @@ export class LoggingAction extends BaseAction { // From Elasticsearch static fromUpstreamJson(json) { const props = super.getPropsFromUpstreamJson(json); + const { errors } = this.validateJson(json.actionJson); - if (!json.actionJson.logging) { - throw badRequest( - i18n.translate('xpack.watcher.models.loggingAction.absenceOfActionJsonLoggingPropertyBadRequestMessage', { + Object.assign(props, { + text: json.actionJson.logging.text + }); + + const action = new LoggingAction(props, errors); + return { action, errors }; + } + + static validateJson(json) { + const errors = []; + + if (!json.logging) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.loggingAction.absenceOfActionJsonLoggingPropertyBadRequestMessage', { defaultMessage: 'json argument must contain an {actionJsonLogging} property', values: { actionJsonLogging: 'actionJson.logging' } }), - ); + }); + + json.logging = {}; } - if (!json.actionJson.logging.text) { - throw badRequest( - i18n.translate('xpack.watcher.models.loggingAction.absenceOfActionJsonLoggingTextPropertyBadRequestMessage', { + + if (!json.logging.text) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.loggingAction.absenceOfActionJsonLoggingTextPropertyBadRequestMessage', { defaultMessage: 'json argument must contain an {actionJsonLoggingText} property', values: { actionJsonLoggingText: 'actionJson.logging.text' } }), - ); + }); } - Object.assign(props, { - text: json.actionJson.logging.text - }); - - return new LoggingAction(props); + return { errors: errors.length ? errors : null }; } /* diff --git a/x-pack/plugins/watcher/server/models/action/slack_action.js b/x-pack/plugins/watcher/server/models/action/slack_action.js index 1852cfcde8c23..469aaae7b1368 100644 --- a/x-pack/plugins/watcher/server/models/action/slack_action.js +++ b/x-pack/plugins/watcher/server/models/action/slack_action.js @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { badRequest } from 'boom'; import { BaseAction } from './base_action'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES, ERROR_CODES } from '../../../common/constants'; import { i18n } from '@kbn/i18n'; export class SlackAction extends BaseAction { - constructor(props) { + constructor(props, errors) { props.type = ACTION_TYPES.SLACK; - super(props); + super(props, errors); this.to = props.to; this.text = props.text; @@ -33,12 +32,15 @@ export class SlackAction extends BaseAction { static fromDownstreamJson(json) { const props = super.getPropsFromDownstreamJson(json); + const { errors } = this.validateJson(json); + Object.assign(props, { to: json.to, text: json.text }); - return new SlackAction(props); + const action = new SlackAction(props, errors); + return { action, errors }; } // To Elasticsearch @@ -60,45 +62,50 @@ export class SlackAction extends BaseAction { // From Elasticsearch static fromUpstreamJson(json) { const props = super.getPropsFromUpstreamJson(json); + const { errors } = this.validateJson(json.actionJson); + + Object.assign(props, { + to: json.actionJson.slack.message.to, + text: json.actionJson.slack.message.text + }); - if (!json.actionJson.slack) { - throw badRequest( - i18n.translate('xpack.watcher.models.slackAction.absenceOfActionJsonSlackPropertyBadRequestMessage', { + const action = new SlackAction(props, errors); + + return { action, errors }; + } + + static validateJson(json) { + const errors = []; + + if (!json.slack) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.slackAction.absenceOfActionJsonSlackPropertyBadRequestMessage', { defaultMessage: 'json argument must contain an {actionJsonSlack} property', values: { actionJsonSlack: 'actionJson.slack' } - }), - ); + }) + }); + + json.slack = {}; } - if (!json.actionJson.slack.message) { - throw badRequest( - i18n.translate('xpack.watcher.models.slackAction.absenceOfActionJsonSlackMessagePropertyBadRequestMessage', { + + if (!json.slack.message) { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.slackAction.absenceOfActionJsonSlackMessagePropertyBadRequestMessage', { defaultMessage: 'json argument must contain an {actionJsonSlackMessage} property', values: { actionJsonSlackMessage: 'actionJson.slack.message' } - - }), - ); - } - if (!json.actionJson.slack.message.to) { - throw badRequest( - i18n.translate('xpack.watcher.models.slackAction.absenceOfActionJsonSlackMessageToPropertyBadRequestMessage', { - defaultMessage: 'json argument must contain an {actionJsonSlackMessageTo} property', - values: { - actionJsonSlackMessageTo: 'actionJson.slack.message.to' - } }), - ); - } + }); - Object.assign(props, { - to: json.actionJson.slack.message.to, - text: json.actionJson.slack.message.text - }); + json.slack.message = {}; + } - return new SlackAction(props); + return { errors: errors.length ? errors : null }; } /* diff --git a/x-pack/plugins/watcher/server/models/action/unknown_action.js b/x-pack/plugins/watcher/server/models/action/unknown_action.js index 7dfe83a1e4000..af8b827f770aa 100644 --- a/x-pack/plugins/watcher/server/models/action/unknown_action.js +++ b/x-pack/plugins/watcher/server/models/action/unknown_action.js @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { badRequest } from 'boom'; import { BaseAction } from './base_action'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES, ERROR_CODES } from '../../../common/constants'; import { i18n } from '@kbn/i18n'; export class UnknownAction extends BaseAction { - constructor(props) { + constructor(props, errors) { props.type = ACTION_TYPES.UNKNOWN; - super(props); + super(props, errors); this.actionJson = props.actionJson; } @@ -51,23 +50,33 @@ export class UnknownAction extends BaseAction { // From Elasticsearch static fromUpstreamJson(json) { const props = super.getPropsFromUpstreamJson(json); + const { errors } = this.validateJson(json); + + Object.assign(props, { + actionJson: json.actionJson + }); + + const action = new UnknownAction(props, errors); + + return { action, errors }; + } + + static validateJson(json) { + const errors = []; if (!json.actionJson) { - throw badRequest( - i18n.translate('xpack.watcher.models.unknownAction.absenceOfActionJsonPropertyBadRequestMessage', { + errors.push({ + code: ERROR_CODES.ERR_PROP_MISSING, + message: i18n.translate('xpack.watcher.models.unknownAction.absenceOfActionJsonPropertyBadRequestMessage', { defaultMessage: 'json argument must contain an {actionJson} property', values: { actionJson: 'actionJson' } }), - ); + }); } - Object.assign(props, { - actionJson: json.actionJson - }); - - return new UnknownAction(props); + return { errors: errors.length ? errors : null }; } /*