diff --git a/x-pack/legacy/plugins/watcher/common/constants/watch_types.ts b/x-pack/legacy/plugins/watcher/common/constants/watch_types.ts index f0ebd3efc4078..b7f9519e89309 100644 --- a/x-pack/legacy/plugins/watcher/common/constants/watch_types.ts +++ b/x-pack/legacy/plugins/watcher/common/constants/watch_types.ts @@ -6,8 +6,6 @@ export const WATCH_TYPES: { [key: string]: string } = { JSON: 'json', - THRESHOLD: 'threshold', - MONITORING: 'monitoring', }; diff --git a/x-pack/legacy/plugins/watcher/common/lib/serialization/index.d.ts b/x-pack/legacy/plugins/watcher/common/lib/serialization/index.d.ts new file mode 100644 index 0000000000000..7ef13f606b535 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/index.d.ts @@ -0,0 +1,9 @@ +/* + * 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 declare function serializeJsonWatch(name: string, json: any): any; +export declare function serializeThresholdWatch(config: any): any; +export declare function buildInput(config: any): any; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/lib/single_line_script/index.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/index.js similarity index 56% rename from x-pack/legacy/plugins/watcher/server/models/watch/lib/single_line_script/index.js rename to x-pack/legacy/plugins/watcher/common/lib/serialization/index.js index 1c88b33e6ddf6..53f31665e59ca 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/lib/single_line_script/index.js +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/index.js @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { singleLineScript } from './single_line_script'; +export { serializeJsonWatch } from './serialize_json_watch'; +export { serializeThresholdWatch } from './serialize_threshold_watch'; +export { buildInput } from './serialization_helpers'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_actions.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_actions.js similarity index 65% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_actions.js rename to x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_actions.js index 9d0ea0d84e9a6..8b2186922c86f 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_actions.js +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_actions.js @@ -5,15 +5,17 @@ */ import { forEach } from 'lodash'; +import { Action } from '../../../models/action'; /* watch.actions */ -export function buildActions({ actions }) { +export function buildActions(actions) { const result = {}; forEach(actions, (action) => { - Object.assign(result, action.upstreamJson); + const actionModel = Action.fromDownstreamJson(action); + Object.assign(result, actionModel.upstreamJson); }); return result; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_condition.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_condition.js similarity index 89% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_condition.js rename to x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_condition.js index 64ee0f44c3104..780e840de5077 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_condition.js +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_condition.js @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { singleLineScript } from '../lib/single_line_script'; +import { singleLineScript } from './single_line_script'; import { COMPARATORS } from '../../../../common/constants'; const { BETWEEN } = COMPARATORS; /* watch.condition.script.inline */ -function buildInline({ aggType, thresholdComparator, hasTermsAgg }) { +function buildInline(aggType, thresholdComparator, hasTermsAgg) { let script = ''; if (aggType === 'count' && !hasTermsAgg) { @@ -113,7 +113,7 @@ function buildInline({ aggType, thresholdComparator, hasTermsAgg }) { /* watch.condition.script.params */ -function buildParams({ threshold }) { +function buildParams(threshold) { return { threshold }; @@ -122,11 +122,11 @@ function buildParams({ threshold }) { /* watch.condition */ -export function buildCondition(watch) { +export function buildCondition({ aggType, thresholdComparator, hasTermsAgg, threshold }) { return { script: { - source: buildInline(watch), - params: buildParams(watch) + source: buildInline(aggType, thresholdComparator, hasTermsAgg), + params: buildParams(threshold) } }; } diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_input.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_input.js similarity index 70% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_input.js rename to x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_input.js index d6f173f23162e..8e7b7c296d752 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_input.js +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_input.js @@ -9,7 +9,7 @@ import { set } from 'lodash'; /* watch.input.search.request.indices */ -function buildIndices({ index }) { +function buildIndices(index) { if (Array.isArray(index)) { return index; } @@ -22,7 +22,7 @@ function buildIndices({ index }) { /* watch.input.search.request.body.query.bool.filter.range */ -function buildRange({ timeWindowSize, timeWindowUnit, timeField }) { +function buildRange(timeWindowSize, timeWindowUnit, timeField) { return { [timeField]: { gte: `{{ctx.trigger.scheduled_time}}||-${timeWindowSize}${timeWindowUnit}`, @@ -35,12 +35,12 @@ function buildRange({ timeWindowSize, timeWindowUnit, timeField }) { /* watch.input.search.request.body.query */ -function buildQuery(watch) { +function buildQuery(timeWindowSize, timeWindowUnit, timeField) { //TODO: This is where a saved search would be applied return { bool: { filter: { - range: buildRange(watch) + range: buildRange(timeWindowSize, timeWindowUnit, timeField) } } }; @@ -49,7 +49,7 @@ function buildQuery(watch) { /* watch.input.search.request.body.aggs */ -function buildAggs({ aggType, aggField, termField, termSize, termOrder }) { +function buildAggs(aggType, aggField, termField, termSize, termOrder) { if (aggType === 'count' && !termField) { return null; } @@ -107,13 +107,13 @@ function buildAggs({ aggType, aggField, termField, termSize, termOrder }) { /* watch.input.search.request.body */ -function buildBody(watch) { +function buildBody(timeWindowSize, timeWindowUnit, timeField, aggType, aggField, termField, termSize, termOrder) { const result = { size: 0, - query: buildQuery(watch) + query: buildQuery(timeWindowSize, timeWindowUnit, timeField) }; - const aggs = buildAggs(watch); + const aggs = buildAggs(aggType, aggField, termField, termSize, termOrder); if (Boolean(aggs)) { result.aggs = aggs; } @@ -124,12 +124,12 @@ function buildBody(watch) { /* watch.input */ -export function buildInput(watch) { +export function buildInput({ index, timeWindowSize, timeWindowUnit, timeField, aggType, aggField, termField, termSize, termOrder }) { return { search: { request: { - body: buildBody(watch), - indices: buildIndices(watch) + body: buildBody(timeWindowSize, timeWindowUnit, timeField, aggType, aggField, termField, termSize, termOrder), + indices: buildIndices(index) } } }; diff --git a/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_metadata.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_metadata.js new file mode 100644 index 0000000000000..5e2e99ece48e4 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_metadata.js @@ -0,0 +1,41 @@ +/* + * 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. + */ + +/* +watch.metadata + */ + +export function buildMetadata({ + index, + timeField, + triggerIntervalSize, + triggerIntervalUnit, + aggType, + aggField, + termSize, + termField, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + threshold, +}) { + return { + watcherui: { + index, + time_field: timeField, + trigger_interval_size: triggerIntervalSize, + trigger_interval_unit: triggerIntervalUnit, + agg_type: aggType, + agg_field: aggField, + term_size: termSize, + term_field: termField, + threshold_comparator: thresholdComparator, + time_window_size: timeWindowSize, + time_window_unit: timeWindowUnit, + threshold, + } + }; +} diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_transform.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_transform.js similarity index 90% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_transform.js rename to x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_transform.js index a302dde245634..4115742642bc4 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_transform.js +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_transform.js @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { singleLineScript } from '../lib/single_line_script'; +import { singleLineScript } from './single_line_script'; import { COMPARATORS } from '../../../../common/constants'; const { BETWEEN } = COMPARATORS; /* watch.transform.script.inline */ -function buildInline({ aggType, hasTermsAgg, thresholdComparator }) { +function buildInline(aggType, thresholdComparator, hasTermsAgg) { let script = ''; if (aggType === 'count' && !hasTermsAgg) { @@ -119,7 +119,7 @@ function buildInline({ aggType, hasTermsAgg, thresholdComparator }) { /* watch.transform.script.params */ -function buildParams({ threshold }) { +function buildParams(threshold) { return { threshold }; @@ -128,11 +128,11 @@ function buildParams({ threshold }) { /* watch.transform */ -export function buildTransform(watch) { +export function buildTransform({ aggType, thresholdComparator, hasTermsAgg, threshold }) { return { script: { - source: buildInline(watch), - params: buildParams(watch) + source: buildInline(aggType, thresholdComparator, hasTermsAgg), + params: buildParams(threshold) } }; } diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_trigger.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_trigger.js similarity index 56% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_trigger.js rename to x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_trigger.js index 17da73a9d39e8..bedd10c2791bb 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_trigger.js +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/build_trigger.js @@ -7,14 +7,10 @@ /* watch.trigger.schedule */ -function buildSchedule({ triggerIntervalSize, triggerIntervalUnit }) { +export function buildTrigger(triggerIntervalSize, triggerIntervalUnit) { return { - interval: `${triggerIntervalSize}${triggerIntervalUnit}` - }; -} - -export function buildTrigger(watch) { - return { - schedule: buildSchedule(watch) + schedule: { + interval: `${triggerIntervalSize}${triggerIntervalUnit}` + }, }; } diff --git a/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/index.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/index.js new file mode 100644 index 0000000000000..b797ccd96b092 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/index.js @@ -0,0 +1,12 @@ +/* + * 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 { buildActions } from './build_actions'; +export { buildCondition } from './build_condition'; +export { buildInput } from './build_input'; +export { buildMetadata } from './build_metadata'; +export { buildTransform } from './build_transform'; +export { buildTrigger } from './build_trigger'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/lib/single_line_script/single_line_script.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/single_line_script.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/lib/single_line_script/single_line_script.js rename to x-pack/legacy/plugins/watcher/common/lib/serialization/serialization_helpers/single_line_script.js diff --git a/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_json_watch.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_json_watch.js new file mode 100644 index 0000000000000..70b00070447a4 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_json_watch.js @@ -0,0 +1,25 @@ +/* + * 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 { set } from 'lodash'; +import { WATCH_TYPES } from '../../constants'; + +export function serializeJsonWatch(name, json) { + // We don't want to overwrite any metadata provided by the consumer. + const { metadata = {} } = json; + set(metadata, 'xpack.type', WATCH_TYPES.JSON); + + const serializedWatch = { + ...json, + metadata, + }; + + if (name) { + serializedWatch.metadata.name = name; + } + + return serializedWatch; +} diff --git a/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_json_watch.test.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_json_watch.test.js new file mode 100644 index 0000000000000..85d35be3d3e78 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_json_watch.test.js @@ -0,0 +1,43 @@ +/* + * 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 { serializeJsonWatch } from './serialize_json_watch'; + +describe('serializeJsonWatch', () => { + it('serializes with name', () => { + expect(serializeJsonWatch('test', { foo: 'bar' })).toEqual({ + foo: 'bar', + metadata: { + name: 'test', + xpack: { + type: 'json', + }, + }, + }); + }); + + it('serializes without name', () => { + expect(serializeJsonWatch(undefined, { foo: 'bar' })).toEqual({ + foo: 'bar', + metadata: { + xpack: { + type: 'json', + }, + }, + }); + }); + + it('respects provided metadata', () => { + expect(serializeJsonWatch(undefined, { metadata: { foo: 'bar' } })).toEqual({ + metadata: { + foo: 'bar', + xpack: { + type: 'json', + }, + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_threshold_watch.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_threshold_watch.js new file mode 100644 index 0000000000000..0065c60fa7fa2 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_threshold_watch.js @@ -0,0 +1,72 @@ +/* + * 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 { WATCH_TYPES } from '../../constants'; +import { + buildActions, + buildCondition, + buildInput, + buildMetadata, + buildTransform, + buildTrigger, +} from './serialization_helpers'; + +export function serializeThresholdWatch({ + name, + triggerIntervalSize, + triggerIntervalUnit, + index, + timeWindowSize, + timeWindowUnit, + timeField, + aggType, + aggField, + termField, + termSize, + termOrder, + thresholdComparator, + hasTermsAgg, + threshold, + actions, + includeMetadata = true, +}) { + + const serializedWatch = { + trigger: buildTrigger(triggerIntervalSize, triggerIntervalUnit), + input: buildInput({ index, timeWindowSize, timeWindowUnit, timeField, aggType, aggField, termField, termSize, termOrder }), + condition: buildCondition({ aggType, thresholdComparator, hasTermsAgg, threshold }), + transform: buildTransform({ aggType, thresholdComparator, hasTermsAgg, threshold }), + actions: buildActions(actions), + }; + + if (includeMetadata) { + serializedWatch.metadata = { + xpack: { + type: WATCH_TYPES.THRESHOLD, + }, + ...buildMetadata({ + index, + timeField, + triggerIntervalSize, + triggerIntervalUnit, + aggType, + aggField, + termSize, + termField, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + threshold, + }), + }; + + if (name) { + serializedWatch.metadata.name = name; + } + } + + return serializedWatch; +} diff --git a/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_threshold_watch.test.js b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_threshold_watch.test.js new file mode 100644 index 0000000000000..7a18433256db1 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/common/lib/serialization/serialize_threshold_watch.test.js @@ -0,0 +1,314 @@ +/* + * 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 { serializeThresholdWatch } from './serialize_threshold_watch'; + +describe('serializeThresholdWatch', () => { + it('serializes with name', () => { + expect(serializeThresholdWatch({ + name: 'test', + triggerIntervalSize: 10, + triggerIntervalUnit: 's', + index: 'myIndex', + timeWindowSize: 20, + timeWindowUnit: 's', + timeField: 'myTimeField', + aggType: 'myAggType', + aggField: 'myAggField', + termField: 'myTermField', + termSize: 30, + termOrder: 40, + thresholdComparator: 'between', + hasTermsAgg: true, + threshold: 50, + actions: [], + })).toEqual({ + trigger: { + schedule: { + interval: '10s', + }, + }, + input: { + search: { + request: { + body: { + size: 0, + query: { + bool: { + filter: { + range: { + myTimeField: { + gte: '{{ctx.trigger.scheduled_time}}||-20s', + lte: '{{ctx.trigger.scheduled_time}}', + format: 'strict_date_optional_time||epoch_millis', + }, + }, + }, + }, + }, + aggs: { + bucketAgg: { + terms: { + field: 'myTermField', + size: 30, + order: { + metricAgg: 40, + }, + }, + aggs: { + metricAgg: { + myAggType: { + field: 'myAggField', + }, + }, + }, + }, + }, + }, + indices: [ + 'myIndex', + ], + }, + }, + }, + condition: { + script: { + // eslint-disable-next-line max-len + source: 'ArrayList arr = ctx.payload.aggregations.bucketAgg.buckets; for (int i = 0; i < arr.length; i++) { if (arr[i][\'metricAgg\'].value >= params.threshold[0] && arr[i][\'metricAgg\'].value <= params.threshold[1]) { return true; } } return false;', + params: { + threshold: 50, + }, + }, + }, + transform: { + script: { + // eslint-disable-next-line max-len + source: 'HashMap result = new HashMap(); ArrayList arr = ctx.payload.aggregations.bucketAgg.buckets; ArrayList filteredHits = new ArrayList(); for (int i = 0; i < arr.length; i++) { HashMap filteredHit = new HashMap(); filteredHit.key = arr[i].key; filteredHit.value = arr[i][\'metricAgg\'].value; if (filteredHit.value >= params.threshold[0] && filteredHit.value <= params.threshold[1]) { filteredHits.add(filteredHit); } } result.results = filteredHits; return result;', + params: { + threshold: 50, + }, + }, + }, + actions: {}, + metadata: { + xpack: { + type: 'threshold', + }, + watcherui: { + index: 'myIndex', + time_field: 'myTimeField', + trigger_interval_size: 10, + trigger_interval_unit: 's', + agg_type: 'myAggType', + agg_field: 'myAggField', + term_size: 30, + term_field: 'myTermField', + threshold_comparator: 'between', + time_window_size: 20, + time_window_unit: 's', + threshold: 50, + }, + name: 'test', + }, + }); + }); + + it('serializes without name', () => { + expect(serializeThresholdWatch({ + triggerIntervalSize: 10, + triggerIntervalUnit: 's', + index: 'myIndex', + timeWindowSize: 20, + timeWindowUnit: 's', + timeField: 'myTimeField', + aggType: 'myAggType', + aggField: 'myAggField', + termField: 'myTermField', + termSize: 30, + termOrder: 40, + thresholdComparator: 'between', + hasTermsAgg: true, + threshold: 50, + actions: [], + })).toEqual({ + trigger: { + schedule: { + interval: '10s', + }, + }, + input: { + search: { + request: { + body: { + size: 0, + query: { + bool: { + filter: { + range: { + myTimeField: { + gte: '{{ctx.trigger.scheduled_time}}||-20s', + lte: '{{ctx.trigger.scheduled_time}}', + format: 'strict_date_optional_time||epoch_millis', + }, + }, + }, + }, + }, + aggs: { + bucketAgg: { + terms: { + field: 'myTermField', + size: 30, + order: { + metricAgg: 40, + }, + }, + aggs: { + metricAgg: { + myAggType: { + field: 'myAggField', + }, + }, + }, + }, + }, + }, + indices: [ + 'myIndex', + ], + }, + }, + }, + condition: { + script: { + // eslint-disable-next-line max-len + source: 'ArrayList arr = ctx.payload.aggregations.bucketAgg.buckets; for (int i = 0; i < arr.length; i++) { if (arr[i][\'metricAgg\'].value >= params.threshold[0] && arr[i][\'metricAgg\'].value <= params.threshold[1]) { return true; } } return false;', + params: { + threshold: 50, + }, + }, + }, + transform: { + script: { + // eslint-disable-next-line max-len + source: 'HashMap result = new HashMap(); ArrayList arr = ctx.payload.aggregations.bucketAgg.buckets; ArrayList filteredHits = new ArrayList(); for (int i = 0; i < arr.length; i++) { HashMap filteredHit = new HashMap(); filteredHit.key = arr[i].key; filteredHit.value = arr[i][\'metricAgg\'].value; if (filteredHit.value >= params.threshold[0] && filteredHit.value <= params.threshold[1]) { filteredHits.add(filteredHit); } } result.results = filteredHits; return result;', + params: { + threshold: 50, + }, + }, + }, + actions: {}, + metadata: { + xpack: { + type: 'threshold', + }, + watcherui: { + index: 'myIndex', + time_field: 'myTimeField', + trigger_interval_size: 10, + trigger_interval_unit: 's', + agg_type: 'myAggType', + agg_field: 'myAggField', + term_size: 30, + term_field: 'myTermField', + threshold_comparator: 'between', + time_window_size: 20, + time_window_unit: 's', + threshold: 50, + }, + }, + }); + }); + + it('excludes metadata when includeMetadata is false', () => { + expect(serializeThresholdWatch({ + triggerIntervalSize: 10, + triggerIntervalUnit: 's', + index: 'myIndex', + timeWindowSize: 20, + timeWindowUnit: 's', + timeField: 'myTimeField', + aggType: 'myAggType', + aggField: 'myAggField', + termField: 'myTermField', + termSize: 30, + termOrder: 40, + thresholdComparator: 'between', + hasTermsAgg: true, + threshold: 50, + actions: [], + includeMetadata: false, + })).toEqual({ + trigger: { + schedule: { + interval: '10s', + }, + }, + input: { + search: { + request: { + body: { + size: 0, + query: { + bool: { + filter: { + range: { + myTimeField: { + gte: '{{ctx.trigger.scheduled_time}}||-20s', + lte: '{{ctx.trigger.scheduled_time}}', + format: 'strict_date_optional_time||epoch_millis', + }, + }, + }, + }, + }, + aggs: { + bucketAgg: { + terms: { + field: 'myTermField', + size: 30, + order: { + metricAgg: 40, + }, + }, + aggs: { + metricAgg: { + myAggType: { + field: 'myAggField', + }, + }, + }, + }, + }, + }, + indices: [ + 'myIndex', + ], + }, + }, + }, + condition: { + script: { + // eslint-disable-next-line max-len + source: 'ArrayList arr = ctx.payload.aggregations.bucketAgg.buckets; for (int i = 0; i < arr.length; i++) { if (arr[i][\'metricAgg\'].value >= params.threshold[0] && arr[i][\'metricAgg\'].value <= params.threshold[1]) { return true; } } return false;', + params: { + threshold: 50, + }, + }, + }, + transform: { + script: { + // eslint-disable-next-line max-len + source: 'HashMap result = new HashMap(); ArrayList arr = ctx.payload.aggregations.bucketAgg.buckets; ArrayList filteredHits = new ArrayList(); for (int i = 0; i < arr.length; i++) { HashMap filteredHit = new HashMap(); filteredHit.key = arr[i].key; filteredHit.value = arr[i][\'metricAgg\'].value; if (filteredHit.value >= params.threshold[0] && filteredHit.value <= params.threshold[1]) { filteredHits.add(filteredHit); } } result.results = filteredHits; return result;', + params: { + threshold: 50, + }, + }, + }, + actions: {}, + }); + }); +}); diff --git a/x-pack/legacy/plugins/watcher/server/models/action/action.js b/x-pack/legacy/plugins/watcher/common/models/action/action.js similarity index 50% rename from x-pack/legacy/plugins/watcher/server/models/action/action.js rename to x-pack/legacy/plugins/watcher/common/models/action/action.js index 09bc3316dcb36..0d0192e3a1ab2 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/action.js @@ -5,7 +5,6 @@ */ import { set } from 'lodash'; -import { badRequest } from 'boom'; import { getActionType } from '../../../common/lib/get_action_type'; import { ACTION_TYPES } from '../../../common/constants'; import { LoggingAction } from './logging_action'; @@ -16,7 +15,6 @@ 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'; const ActionTypes = {}; set(ActionTypes, ACTION_TYPES.LOGGING, LoggingAction); @@ -34,63 +32,17 @@ export class Action { } // From Elasticsearch - static fromUpstreamJson(json, options = { throwExceptions: {} }) { - if (!json.id) { - throw badRequest( - i18n.translate('xpack.watcher.models.actionStatus.idPropertyMissingBadRequestMessage', { - defaultMessage: 'JSON argument must contain an {id} property', - values: { - id: 'id' - } - }), - ); - } - - if (!json.actionJson) { - throw badRequest( - i18n.translate('xpack.watcher.models.action.actionJsonPropertyMissingBadRequestMessage', { - defaultMessage: 'JSON argument must contain an {actionJson} property', - values: { - actionJson: 'actionJson' - } - }), - ); - } - + static fromUpstreamJson(json) { const type = getActionType(json.actionJson); const ActionType = ActionTypes[type] || UnknownAction; - - const { action, errors } = ActionType.fromUpstreamJson(json, options); - const doThrowException = options.throwExceptions.Action !== false; - - if (errors && doThrowException) { - this.throwErrors(errors); - } - + const { action } = ActionType.fromUpstreamJson(json); return action; } // From Kibana - static fromDownstreamJson(json, options = { throwExceptions: {} }) { + static fromDownstreamJson(json) { const ActionType = ActionTypes[json.type] || UnknownAction; - - const { action, errors } = ActionType.fromDownstreamJson(json); - const doThrowException = options.throwExceptions.Action !== false; - - if (errors && doThrowException) { - this.throwErrors(errors); - } - + const { action } = ActionType.fromDownstreamJson(json); 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/legacy/plugins/watcher/server/models/action/action.test.js b/x-pack/legacy/plugins/watcher/common/models/action/action.test.js similarity index 74% rename from x-pack/legacy/plugins/watcher/server/models/action/action.test.js rename to x-pack/legacy/plugins/watcher/common/models/action/action.test.js index d41844017e298..78db59a0ad1e0 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/action.test.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/action.test.js @@ -5,7 +5,6 @@ */ import { Action } from './action'; -import { LoggingAction } from './logging_action'; import { ACTION_TYPES } from '../../../common/constants'; jest.mock('./logging_action', () => ({ @@ -18,11 +17,8 @@ jest.mock('./logging_action', () => ({ })); describe('action', () => { - describe('Action', () => { - describe('fromUpstreamJson factory method', () => { - let upstreamJson; beforeEach(() => { upstreamJson = { @@ -35,43 +31,13 @@ describe('action', () => { }; }); - it(`throws an error if no 'id' property in json`, () => { - delete upstreamJson.id; - expect(() => { - Action.fromUpstreamJson(upstreamJson); - }).toThrowError('JSON argument must contain an id property'); - }); - - it(`throws an error if no 'actionJson' property in json`, () => { - delete upstreamJson.actionJson; - expect(() => { - Action.fromUpstreamJson(upstreamJson); - }).toThrowError('JSON argument must contain an actionJson property'); - }); - - 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 } }; @@ -81,7 +47,7 @@ describe('action', () => { const upstreamEmailJson = { id: 'action2', actionJson: { email: {} } }; const emailAction = Action.fromUpstreamJson(upstreamEmailJson, options); - const upstreamSlackJson = { id: 'action3', actionJson: { slack: {} } }; + const upstreamSlackJson = { id: 'action3', actionJson: { slack: { message: {} } } }; const slackAction = Action.fromUpstreamJson(upstreamSlackJson, options); expect(loggingAction.type).toBe(ACTION_TYPES.LOGGING); @@ -102,11 +68,9 @@ describe('action', () => { expect(action.type).toBe(ACTION_TYPES.UNKNOWN); }); - }); describe('downstreamJson getter method', () => { - let upstreamJson; beforeEach(() => { upstreamJson = { @@ -120,17 +84,12 @@ describe('action', () => { }); 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/legacy/plugins/watcher/server/models/action/base_action.js b/x-pack/legacy/plugins/watcher/common/models/action/base_action.js similarity index 67% rename from x-pack/legacy/plugins/watcher/server/models/action/base_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/base_action.js index 31d8b39b5b1c0..82189e34d79c0 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/base_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/base_action.js @@ -4,9 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { badRequest } from 'boom'; -import { i18n } from '@kbn/i18n'; - export class BaseAction { constructor(props, errors) { this.id = props.id; @@ -35,17 +32,6 @@ export class BaseAction { } static getPropsFromUpstreamJson(json) { - if (!json.id) { - throw badRequest( - i18n.translate('xpack.watcher.models.baseAction.idPropertyMissingBadRequestMessage', { - defaultMessage: 'JSON argument must contain an {id} property', - values: { - id: 'id' - } - }), - ); - } - return { id: json.id }; diff --git a/x-pack/legacy/plugins/watcher/server/models/action/email_action.js b/x-pack/legacy/plugins/watcher/common/models/action/email_action.js similarity index 98% rename from x-pack/legacy/plugins/watcher/server/models/action/email_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/email_action.js index 42428ddfc6908..d5939297c85bf 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/email_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/email_action.js @@ -108,11 +108,9 @@ export class EmailAction extends BaseAction { code: ERROR_CODES.ERR_PROP_MISSING, message }); - - json.email = {}; } - if (!json.email.to) { + if (json.email && !json.email.to) { const message = i18n.translate('xpack.watcher.models.emailAction.actionJsonEmailToPropertyMissingBadRequestMessage', { defaultMessage: 'JSON argument must contain an {actionJsonEmailTo} property', values: { diff --git a/x-pack/legacy/plugins/watcher/server/models/action/index.js b/x-pack/legacy/plugins/watcher/common/models/action/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/action/index.js rename to x-pack/legacy/plugins/watcher/common/models/action/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/action/index_action.js b/x-pack/legacy/plugins/watcher/common/models/action/index_action.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/action/index_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/index_action.js index 5bfd4335067ec..d6f11fe33aedf 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/index_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/index_action.js @@ -75,11 +75,9 @@ export class IndexAction extends BaseAction { } }), }); - - json.index = {}; } - if (!json.index.index) { + if (json.index && !json.index.index) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonIndexNamePropertyMissingBadRequestMessage', { diff --git a/x-pack/legacy/plugins/watcher/server/models/action/jira_action.js b/x-pack/legacy/plugins/watcher/common/models/action/jira_action.js similarity index 98% rename from x-pack/legacy/plugins/watcher/server/models/action/jira_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/jira_action.js index 0a53258fe1bb6..1992c2b59ecfa 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/jira_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/jira_action.js @@ -95,8 +95,6 @@ export class JiraAction extends BaseAction { } }), }); - - json.jira = {}; } if (!get(json, 'jira.fields.project.key')) { @@ -123,7 +121,7 @@ export class JiraAction extends BaseAction { }); } - if (!json.jira.fields.summary) { + if (!get(json, 'jira.fields.summary')) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, message: i18n.translate('xpack.watcher.models.jiraAction.actionJsonJiraSummaryPropertyMissingBadRequestMessage', { diff --git a/x-pack/legacy/plugins/watcher/server/models/action/logging_action.js b/x-pack/legacy/plugins/watcher/common/models/action/logging_action.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/action/logging_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/logging_action.js index 92ba94dca84ff..134d952c1049d 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/logging_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/logging_action.js @@ -78,11 +78,9 @@ export class LoggingAction extends BaseAction { } }), }); - - json.logging = {}; } - if (!json.logging.text) { + if (json.logging && !json.logging.text) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonLoggingTextPropertyMissingBadRequestMessage', { diff --git a/x-pack/legacy/plugins/watcher/server/models/action/pagerduty_action.js b/x-pack/legacy/plugins/watcher/common/models/action/pagerduty_action.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/action/pagerduty_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/pagerduty_action.js index edfdb33584170..39e4c4fa50c4d 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/pagerduty_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/pagerduty_action.js @@ -78,11 +78,9 @@ export class PagerDutyAction extends BaseAction { } }), }); - - json.pagerduty = {}; } - if (!json.pagerduty.description) { + if (json.pagerduty && !json.pagerduty.description) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, message: i18n.translate('xpack.watcher.models.pagerDutyAction.actionJsonPagerDutyDescriptionPropertyMissingBadRequestMessage', { diff --git a/x-pack/legacy/plugins/watcher/server/models/action/slack_action.js b/x-pack/legacy/plugins/watcher/common/models/action/slack_action.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/action/slack_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/slack_action.js index 4441a71eacae9..5839da107ba2d 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/slack_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/slack_action.js @@ -87,11 +87,9 @@ export class SlackAction extends BaseAction { } }) }); - - json.slack = {}; } - if (!json.slack.message) { + if (json.slack && !json.slack.message) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, message: i18n.translate('xpack.watcher.models.slackAction.actionJsonSlackMessagePropertyMissingBadRequestMessage', { @@ -101,8 +99,6 @@ export class SlackAction extends BaseAction { } }), }); - - json.slack.message = {}; } return { errors: errors.length ? errors : null }; diff --git a/x-pack/legacy/plugins/watcher/server/models/action/unknown_action.js b/x-pack/legacy/plugins/watcher/common/models/action/unknown_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/action/unknown_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/unknown_action.js diff --git a/x-pack/legacy/plugins/watcher/server/models/action/webhook_action.js b/x-pack/legacy/plugins/watcher/common/models/action/webhook_action.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/action/webhook_action.js rename to x-pack/legacy/plugins/watcher/common/models/action/webhook_action.js index 766ccd5371533..d6101b695ebde 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action/webhook_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/webhook_action.js @@ -145,7 +145,7 @@ export class WebhookAction extends BaseAction { static validateJson(json) { const errors = []; - if (!json.webhook.host) { + if (json.webhook && !json.webhook.host) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonWebhookHostPropertyMissingBadRequestMessage', { @@ -155,10 +155,9 @@ export class WebhookAction extends BaseAction { } }), }); - json.webhook = {}; } - if (!json.webhook.port) { + if (json.webhook && !json.webhook.port) { errors.push({ code: ERROR_CODES.ERR_PROP_MISSING, message: i18n.translate('xpack.watcher.models.loggingAction.actionJsonWebhookPortPropertyMissingBadRequestMessage', { diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx index 97cd10032dead..02a54fc9b9279 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx @@ -20,11 +20,13 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { serializeJsonWatch } from '../../../../../common/lib/serialization'; import { ErrableFormRow, SectionError } from '../../../../components'; import { putWatchApiUrl } from '../../../../lib/documentation_links'; import { onWatchSave } from '../../watch_edit_actions'; import { WatchContext } from '../../watch_context'; import { goToWatchList } from '../../../../lib/navigation'; +import { RequestFlyout } from '../request_flyout'; export const JsonWatchEditForm = () => { const { watch, setWatchProperty } = useContext(WatchContext); @@ -33,6 +35,7 @@ export const JsonWatchEditForm = () => { const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); const [validationError, setValidationError] = useState(null); + const [isRequestVisible, setIsRequestVisible] = useState(false); const [serverError, setServerError] = useState<{ data: { nessage: string; error: string }; @@ -130,7 +133,7 @@ export const JsonWatchEditForm = () => { /> - + { - - - { - setIsSaving(true); - const savedWatch = await onWatchSave(watch); - if (savedWatch && savedWatch.error) { - const { data } = savedWatch.error; - - setIsSaving(false); - - if (data && data.error === 'validation') { - return setValidationError(data.message); + + + + { + setIsSaving(true); + const savedWatch = await onWatchSave(watch); + if (savedWatch && savedWatch.error) { + const { data } = savedWatch.error; + setIsSaving(false); + + if (data && data.error === 'validation') { + return setValidationError(data.message); + } + + return setServerError(savedWatch.error); } + }} + > + {watch.isNew ? ( + + ) : ( + + )} + + + + + goToWatchList()}> + {i18n.translate('xpack.watcher.sections.watchEdit.json.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + - return setServerError(savedWatch.error); - } - }} - > - {watch.isNew ? ( + + setIsRequestVisible(!isRequestVisible)}> + {isRequestVisible ? ( ) : ( )} - - - - - goToWatchList()}> - {i18n.translate('xpack.watcher.sections.watchEdit.json.cancelButtonLabel', { - defaultMessage: 'Cancel', - })} + + {isRequestVisible ? ( + setIsRequestVisible(false)} + /> + ) : null} ); }; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/request_flyout.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/request_flyout.tsx new file mode 100644 index 0000000000000..f60692b7d7676 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/request_flyout.tsx @@ -0,0 +1,87 @@ +/* + * 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, { PureComponent } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiButtonEmpty, + EuiCodeBlock, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +interface Props { + id: any; + close: any; + payload: any; +} + +export class RequestFlyout extends PureComponent { + getEsJson(payload: any): string { + return JSON.stringify(payload, null, 2); + } + + render() { + const { id, payload, close } = this.props; + const endpoint = `PUT _watcher/watch/${id || ''}`; + const request = `${endpoint}\n${this.getEsJson(payload)}`; + + return ( + + + +

+ {id ? ( + + ) : ( + + )} +

+
+
+ + + +

+ +

+
+ + + + + {request} + +
+ + + + + + +
+ ); + } +} diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx index 34c3e66e542b1..c479cad1bc825 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx @@ -27,6 +27,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { TIME_UNITS } from '../../../../../common/constants'; +import { serializeThresholdWatch } from '../../../../../common/lib/serialization'; import { ErrableFormRow, SectionError } from '../../../../components'; import { fetchFields, getMatchingIndices, loadIndexPatterns } from '../../../../lib/api'; import { aggTypes } from '../../../../models/watch/agg_types'; @@ -38,6 +39,7 @@ import { WatchVisualization } from './watch_visualization'; import { WatchActionsPanel } from './threshold_watch_action_panel'; import { getTimeUnitLabel } from '../../../../lib/get_time_unit_label'; import { goToWatchList } from '../../../../lib/navigation'; +import { RequestFlyout } from '../request_flyout'; const expressionFieldsWithValidation = [ 'aggField', @@ -168,6 +170,7 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { } | null>(null); const [isSaving, setIsSaving] = useState(false); const [isIndiciesLoading, setIsIndiciesLoading] = useState(false); + const [isRequestVisible, setIsRequestVisible] = useState(false); const { watch, setWatchProperty } = useContext(WatchContext); @@ -218,6 +221,14 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { defaultMessage: 'AND', }); + // Users might edit the request for use outside of the Watcher app. If they do make changes to it, + // we have no guarantee it will still be compatible with the threshold alert form, so we strip + // the metadata to avoid potential conflicts. + const requestPreviewWatchData = { + ...watch.upstreamJson, + includeMetadata: false, + }; + return ( @@ -870,47 +881,77 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { ) : null} - + + - { - setIsSaving(true); - const savedWatch = await onWatchSave(watch); - if (savedWatch && savedWatch.error) { - setIsSaving(false); - return setServerError(savedWatch.error); - } - }} - > - {watch.isNew ? ( + + + { + setIsSaving(true); + const savedWatch = await onWatchSave(watch); + if (savedWatch && savedWatch.error) { + setIsSaving(false); + return setServerError(savedWatch.error); + } + }} + > + {watch.isNew ? ( + + ) : ( + + )} + + + + + goToWatchList()}> + {i18n.translate('xpack.watcher.sections.watchEdit.threshold.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + + + + + setIsRequestVisible(!isRequestVisible)}> + {isRequestVisible ? ( ) : ( )} - - - - goToWatchList()}> - {i18n.translate('xpack.watcher.sections.watchEdit.threshold.cancelButtonLabel', { - defaultMessage: 'Cancel', - })} + + {isRequestVisible ? ( + setIsRequestVisible(false)} + /> + ) : null} ); }; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts index 9d6a999db60e3..320ba59e0589e 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts @@ -47,21 +47,18 @@ function getPropsFromAction(type: string, action: { [key: string]: any }) { /** * 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. + * when we add them in the JSON watch editor. This method takes takes care of it. */ function createActionsForWatch(watchInstance: BaseWatch) { watchInstance.resetActions(); - let action; - let type; - let actionProps; - Object.keys(watchInstance.watch.actions).forEach(k => { - action = watchInstance.watch.actions[k]; - type = getTypeFromAction(action); - actionProps = { ...getPropsFromAction(type, action), ignoreDefaults: true }; + const action = watchInstance.watch.actions[k]; + const type = getTypeFromAction(action); + const actionProps = { ...getPropsFromAction(type, action), ignoreDefaults: true }; watchInstance.createAction(type, actionProps); }); + return watchInstance; } diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/json_watch.js b/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/json_watch.js deleted file mode 100644 index a38b10afcd28f..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/json_watch.js +++ /dev/null @@ -1,223 +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 { pick } from 'lodash'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import proxyquire from 'proxyquire'; - -const constructorMock = sinon.stub(); -const upstreamJsonMock = sinon.stub(); -const downstreamJsonMock = sinon.stub(); -const getPropsFromUpstreamJsonMock = sinon.stub(); -const getPropsFromDownstreamJsonMock = sinon.stub(); -class BaseWatchStub { - constructor(props) { - constructorMock(props); - } - - get upstreamJson() { - upstreamJsonMock(); - - return { - baseCalled: true - }; - } - - get downstreamJson() { - downstreamJsonMock(); - - return { - baseCalled: true - }; - } - - static getPropsFromUpstreamJson(json) { - getPropsFromUpstreamJsonMock(); - return pick(json, 'watchJson'); - } - - static getPropsFromDownstreamJson(json) { - getPropsFromDownstreamJsonMock(); - return pick(json, 'watchJson'); - } -} - -const { JsonWatch } = proxyquire('../json_watch', { - './base_watch': { BaseWatch: BaseWatchStub } -}); - -describe('JsonWatch', () => { - - describe('Constructor', () => { - - let props; - beforeEach(() => { - constructorMock.resetHistory(); - - props = { - watch: 'foo' - }; - }); - - it('should call the BaseWatch constructor', () => { - new JsonWatch(props); - expect(constructorMock.called).to.be(true); - }); - - it('should populate all expected fields', () => { - const actual = new JsonWatch(props); - const expected = { - watch: 'foo' - }; - - expect(actual).to.eql(expected); - }); - - }); - - describe('watchJson getter method', () => { - - let props; - beforeEach(() => { - props = { - watch: { foo: 'bar' } - }; - }); - - it('should return the correct result', () => { - const watch = new JsonWatch(props); - - expect(watch.watchJson).to.eql(props.watch); - }); - - }); - - describe('upstreamJson getter method', () => { - - beforeEach(() => { - upstreamJsonMock.resetHistory(); - }); - - it('should call the getter from WatchBase and return the correct result', () => { - const watch = new JsonWatch({ watch: 'foo' }); - const actual = watch.upstreamJson; - const expected = { - baseCalled: true - }; - - expect(upstreamJsonMock.called).to.be(true); - expect(actual).to.eql(expected); - }); - - }); - - describe('downstreamJson getter method', () => { - - let props; - beforeEach(() => { - downstreamJsonMock.resetHistory(); - - props = { - watch: 'foo', - watchJson: 'bar' - }; - }); - - it('should call the getter from WatchBase and return the correct result', () => { - const watch = new JsonWatch(props); - const actual = watch.downstreamJson; - const expected = { - baseCalled: true, - watch: 'foo' - }; - - expect(downstreamJsonMock.called).to.be(true); - expect(actual).to.eql(expected); - }); - - }); - - describe('fromUpstreamJson factory method', () => { - - let upstreamJson; - beforeEach(() => { - getPropsFromUpstreamJsonMock.resetHistory(); - - upstreamJson = { - watchJson: { foo: { bar: 'baz' } } - }; - }); - - it('should call the getPropsFromUpstreamJson method of BaseWatch', () => { - JsonWatch.fromUpstreamJson(upstreamJson); - - expect(getPropsFromUpstreamJsonMock.called).to.be(true); - }); - - it('should clone the watchJson property into a watch property', () => { - const jsonWatch = JsonWatch.fromUpstreamJson(upstreamJson); - - expect(jsonWatch.watch).to.eql(upstreamJson.watchJson); - expect(jsonWatch.watch).to.not.be(upstreamJson.watchJson); - }); - - it('should remove the metadata.name property from the watch property', () => { - upstreamJson.watchJson.metadata = { name: 'foobar', foo: 'bar' }; - - const jsonWatch = JsonWatch.fromUpstreamJson(upstreamJson); - - expect(jsonWatch.watch.metadata.name).to.be(undefined); - }); - - it('should remove the metadata.xpack property from the watch property', () => { - upstreamJson.watchJson.metadata = { - name: 'foobar', - xpack: { prop: 'val' }, - foo: 'bar' - }; - - const jsonWatch = JsonWatch.fromUpstreamJson(upstreamJson); - - expect(jsonWatch.watch.metadata.xpack).to.be(undefined); - }); - - it('should remove an empty metadata property from the watch property', () => { - upstreamJson.watchJson.metadata = { name: 'foobar' }; - - const jsonWatch = JsonWatch.fromUpstreamJson(upstreamJson); - - expect(jsonWatch.watch.metadata).to.be(undefined); - }); - - }); - - describe('fromDownstreamJson factory method', () => { - - let downstreamJson; - beforeEach(() => { - getPropsFromDownstreamJsonMock.resetHistory(); - - downstreamJson = { - watch: { foo: { bar: 'baz' } } - }; - }); - - it('should call the getPropsFromDownstreamJson method of BaseWatch', () => { - JsonWatch.fromDownstreamJson(downstreamJson); - - expect(getPropsFromDownstreamJsonMock.called).to.be(true); - }); - - it('should copy the watch property', () => { - const jsonWatch = JsonWatch.fromDownstreamJson(downstreamJson); - - expect(jsonWatch.watch).to.eql(downstreamJson.watch); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/monitoring_watch.js b/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/monitoring_watch.js deleted file mode 100644 index 44e457476815d..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/monitoring_watch.js +++ /dev/null @@ -1,159 +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 { pick } from 'lodash'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import proxyquire from 'proxyquire'; - -const constructorMock = sinon.stub(); -const downstreamJsonMock = sinon.stub(); -const getPropsFromUpstreamJsonMock = sinon.stub(); -class BaseWatchStub { - constructor(props) { - constructorMock(props); - } - - get downstreamJson() { - downstreamJsonMock(); - - return { - baseCalled: true - }; - } - - static getPropsFromUpstreamJson(json) { - getPropsFromUpstreamJsonMock(); - return pick(json, 'watchJson'); - } -} - -const { MonitoringWatch } = proxyquire('../monitoring_watch', { - './base_watch': { BaseWatch: BaseWatchStub } -}); - -describe('MonitoringWatch', () => { - - describe('Constructor', () => { - - let props; - beforeEach(() => { - constructorMock.resetHistory(); - - props = {}; - }); - - it('should call the BaseWatch constructor', () => { - new MonitoringWatch(props); - expect(constructorMock.called).to.be(true); - }); - - it('should populate all expected fields', () => { - const actual = new MonitoringWatch(props); - const expected = { - isSystemWatch: true - }; - - expect(actual).to.eql(expected); - }); - - }); - - describe('watchJson getter method', () => { - - it('should return an empty object', () => { - const watch = new MonitoringWatch({}); - const actual = watch.watchJson; - const expected = {}; - - expect(actual).to.eql(expected); - }); - - }); - - describe('getVisualizeQuery method', () => { - - it(`throws an error`, () => { - const watch = new MonitoringWatch({}); - - expect(watch.getVisualizeQuery).to.throwError(/getVisualizeQuery called for monitoring watch/i); - }); - - }); - - describe('formatVisualizeData method', () => { - - it(`throws an error`, () => { - const watch = new MonitoringWatch({}); - - expect(watch.formatVisualizeData).to.throwError(/formatVisualizeData called for monitoring watch/i); - }); - - }); - - describe('upstreamJson getter method', () => { - - it(`throws an error`, () => { - const watch = new MonitoringWatch({}); - - expect(() => watch.upstreamJson).to.throwError(/upstreamJson called for monitoring watch/i); - }); - - }); - - describe('downstreamJson getter method', () => { - - let props; - beforeEach(() => { - downstreamJsonMock.resetHistory(); - - props = {}; - }); - - it('should call the getter from WatchBase and return the correct result', () => { - const watch = new MonitoringWatch(props); - const actual = watch.downstreamJson; - const expected = { - baseCalled: true - }; - - expect(downstreamJsonMock.called).to.be(true); - expect(actual).to.eql(expected); - }); - - }); - - describe('fromUpstreamJson factory method', () => { - - beforeEach(() => { - getPropsFromUpstreamJsonMock.resetHistory(); - }); - - it('should call the getPropsFromUpstreamJson method of BaseWatch', () => { - MonitoringWatch.fromUpstreamJson({}); - - expect(getPropsFromUpstreamJsonMock.called).to.be(true); - }); - - it('should generate a valid MonitoringWatch object', () => { - const actual = MonitoringWatch.fromUpstreamJson({}); - const expected = { isSystemWatch: true }; - - expect(actual).to.eql(expected); - }); - - }); - - describe('fromDownstreamJson factory method', () => { - - it(`throws an error`, () => { - expect(MonitoringWatch.fromDownstreamJson).withArgs({}) - .to.throwError(/fromDownstreamJson called for monitoring watch/i); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/threshold_watch.js b/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/threshold_watch.js deleted file mode 100644 index cdbe311a8154a..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/threshold_watch.js +++ /dev/null @@ -1,448 +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 { pick } from 'lodash'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import proxyquire from 'proxyquire'; -import { COMPARATORS, SORT_ORDERS } from '../../../../common/constants'; - -const constructorMock = sinon.stub(); -const upstreamJsonMock = sinon.stub(); -const downstreamJsonMock = sinon.stub(); -const getPropsFromUpstreamJsonMock = sinon.stub(); -const getPropsFromDownstreamJsonMock = sinon.stub(); -const buildTriggerMock = sinon.stub(); -const buildInputMock = sinon.stub(); -const buildConditionMock = sinon.stub(); -const buildTransformMock = sinon.stub(); -const buildActionsMock = sinon.stub(); -const buildMetadataMock = sinon.stub(); -const buildVisualizeQueryMock = sinon.stub(); -const formatVisualizeDataMock = sinon.stub(); -class BaseWatchStub { - constructor(props) { - constructorMock(props); - } - - get upstreamJson() { - upstreamJsonMock(); - - return { - baseCalled: true - }; - } - - get downstreamJson() { - downstreamJsonMock(); - - return { - baseCalled: true - }; - } - - static getPropsFromUpstreamJson(json) { - getPropsFromUpstreamJsonMock(); - return pick(json, 'watchJson'); - } - - static getPropsFromDownstreamJson(json) { - getPropsFromDownstreamJsonMock(); - return pick(json, 'watchJson'); - } -} - -const { ThresholdWatch } = proxyquire('../threshold_watch/threshold_watch', { - '../base_watch': { BaseWatch: BaseWatchStub }, - './build_actions': { - buildActions: (...args) => { - buildActionsMock(...args); - return 'buildActionsResult'; - } - }, - './build_condition': { - buildCondition: (...args) => { - buildConditionMock(...args); - return 'buildConditionResult'; - } - }, - './build_input': { - buildInput: (...args) => { - buildInputMock(...args); - return 'buildInputResult'; - } - }, - './build_metadata': { - buildMetadata: (...args) => { - buildMetadataMock(...args); - return 'buildMetadataResult'; - } - }, - './build_transform': { - buildTransform: (...args) => { - buildTransformMock(...args); - return 'buildTransformResult'; - } - }, - './build_trigger': { - buildTrigger: (...args) => { - buildTriggerMock(...args); - return 'buildTriggerResult'; - } - }, - './build_visualize_query': { - buildVisualizeQuery: (...args) => { - buildVisualizeQueryMock(...args); - } - }, - './format_visualize_data': { - formatVisualizeData: (...args) => { - formatVisualizeDataMock(...args); - } - } -}); - -describe('ThresholdWatch', () => { - - describe('Constructor', () => { - - let props; - beforeEach(() => { - constructorMock.resetHistory(); - - props = { - index: 'index', - timeField: 'timeField', - triggerIntervalSize: 'triggerIntervalSize', - triggerIntervalUnit: 'triggerIntervalUnit', - aggType: 'aggType', - aggField: 'aggField', - termSize: 'termSize', - termField: 'termField', - thresholdComparator: 'thresholdComparator', - timeWindowSize: 'timeWindowSize', - timeWindowUnit: 'timeWindowUnit', - threshold: 'threshold' - }; - }); - - it('should call the BaseWatch constructor', () => { - new ThresholdWatch(props); - expect(constructorMock.called).to.be(true); - }); - - it('should populate all expected fields', () => { - const actual = new ThresholdWatch(props); - const expected = { - index: 'index', - timeField: 'timeField', - triggerIntervalSize: 'triggerIntervalSize', - triggerIntervalUnit: 'triggerIntervalUnit', - aggType: 'aggType', - aggField: 'aggField', - termSize: 'termSize', - termField: 'termField', - thresholdComparator: 'thresholdComparator', - timeWindowSize: 'timeWindowSize', - timeWindowUnit: 'timeWindowUnit', - threshold: 'threshold' - }; - - expect(actual).to.eql(expected); - }); - - }); - - describe('hasTermAgg getter method', () => { - - it('should return true if termField is defined', () => { - const downstreamJson = { termField: 'foobar' }; - const thresholdWatch = ThresholdWatch.fromDownstreamJson(downstreamJson); - - expect(thresholdWatch.hasTermsAgg).to.be(true); - }); - - it('should return false if termField is undefined', () => { - const downstreamJson = { termField: undefined }; - const thresholdWatch = ThresholdWatch.fromDownstreamJson(downstreamJson); - - expect(thresholdWatch.hasTermsAgg).to.be(false); - }); - - }); - - describe('termOrder getter method', () => { - - it('should return SORT_ORDERS.DESCENDING if thresholdComparator is COMPARATORS.GREATER_THAN', () => { - const downstreamJson = { thresholdComparator: COMPARATORS.GREATER_THAN }; - const thresholdWatch = ThresholdWatch.fromDownstreamJson(downstreamJson); - - expect(thresholdWatch.termOrder).to.be(SORT_ORDERS.DESCENDING); - }); - - it('should return SORT_ORDERS.ASCENDING if thresholdComparator is not COMPARATORS.GREATER_THAN', () => { - const downstreamJson = { thresholdComparator: 'foo' }; - const thresholdWatch = ThresholdWatch.fromDownstreamJson(downstreamJson); - - expect(thresholdWatch.termOrder).to.be(SORT_ORDERS.ASCENDING); - }); - - }); - - describe('watchJson getter method', () => { - - beforeEach(() => { - buildActionsMock.resetHistory(); - buildConditionMock.resetHistory(); - buildInputMock.resetHistory(); - buildMetadataMock.resetHistory(); - buildTransformMock.resetHistory(); - buildTriggerMock.resetHistory(); - }); - - it('should return the correct result', () => { - const watch = new ThresholdWatch({}); - const actual = watch.watchJson; - const expected = { - trigger: 'buildTriggerResult', - input: 'buildInputResult', - condition: 'buildConditionResult', - transform: 'buildTransformResult', - actions: 'buildActionsResult', - metadata: 'buildMetadataResult' - }; - - expect(actual).to.eql(expected); - expect(buildActionsMock.calledWith(watch)).to.be(true); - expect(buildConditionMock.calledWith(watch)).to.be(true); - expect(buildInputMock.calledWith(watch)).to.be(true); - expect(buildMetadataMock.calledWith(watch)).to.be(true); - expect(buildTransformMock.calledWith(watch)).to.be(true); - expect(buildTriggerMock.calledWith(watch)).to.be(true); - }); - - }); - - describe('getVisualizeQuery method', () => { - - beforeEach(() => { - buildVisualizeQueryMock.resetHistory(); - }); - - it('should call the external buildVisualizeQuery method', () => { - const watch = new ThresholdWatch({}); - const options = { foo: 'bar' }; - watch.getVisualizeQuery(options); - - expect(buildVisualizeQueryMock.calledWith(watch, options)).to.be(true); - }); - - }); - - describe('formatVisualizeData method', () => { - - beforeEach(() => { - formatVisualizeDataMock.resetHistory(); - }); - - it('should call the external formatVisualizeData method', () => { - const watch = new ThresholdWatch({}); - const results = { foo: 'bar' }; - watch.formatVisualizeData(results); - - expect(formatVisualizeDataMock.calledWith(watch, results)).to.be(true); - }); - - }); - - describe('upstreamJson getter method', () => { - - let props; - beforeEach(() => { - upstreamJsonMock.resetHistory(); - - props = { - index: 'index', - timeField: 'timeField', - triggerIntervalSize: 'triggerIntervalSize', - triggerIntervalUnit: 'triggerIntervalUnit', - aggType: 'aggType', - aggField: 'aggField', - termSize: 'termSize', - termField: 'termField', - thresholdComparator: 'thresholdComparator', - timeWindowSize: 'timeWindowSize', - timeWindowUnit: 'timeWindowUnit', - threshold: 'threshold' - }; - }); - - it('should call the getter from WatchBase and return the correct result', () => { - const watch = new ThresholdWatch(props); - const actual = watch.upstreamJson; - const expected = { baseCalled: true }; - - expect(upstreamJsonMock.called).to.be(true); - expect(actual).to.eql(expected); - }); - - }); - - describe('downstreamJson getter method', () => { - - let props; - beforeEach(() => { - downstreamJsonMock.resetHistory(); - - props = { - index: 'index', - timeField: 'timeField', - triggerIntervalSize: 'triggerIntervalSize', - triggerIntervalUnit: 'triggerIntervalUnit', - aggType: 'aggType', - aggField: 'aggField', - termSize: 'termSize', - termField: 'termField', - thresholdComparator: 'thresholdComparator', - timeWindowSize: 'timeWindowSize', - timeWindowUnit: 'timeWindowUnit', - threshold: 'threshold' - }; - }); - - it('should call the getter from WatchBase and return the correct result', () => { - const watch = new ThresholdWatch(props); - const actual = watch.downstreamJson; - const expected = { - baseCalled: true, - index: 'index', - timeField: 'timeField', - triggerIntervalSize: 'triggerIntervalSize', - triggerIntervalUnit: 'triggerIntervalUnit', - aggType: 'aggType', - aggField: 'aggField', - termSize: 'termSize', - termField: 'termField', - thresholdComparator: 'thresholdComparator', - timeWindowSize: 'timeWindowSize', - timeWindowUnit: 'timeWindowUnit', - threshold: 'threshold' - }; - - expect(downstreamJsonMock.called).to.be(true); - expect(actual).to.eql(expected); - }); - - }); - - describe('fromUpstreamJson factory method', () => { - - let upstreamJson; - beforeEach(() => { - getPropsFromUpstreamJsonMock.resetHistory(); - - upstreamJson = { - watchJson: { - foo: { bar: 'baz' }, - metadata: { - watcherui: { - index: 'index', - time_field: 'timeField', - trigger_interval_size: 'triggerIntervalSize', - trigger_interval_unit: 'triggerIntervalUnit', - agg_type: 'aggType', - agg_field: 'aggField', - term_size: 'termSize', - term_field: 'termField', - threshold_comparator: 'thresholdComparator', - time_window_size: 'timeWindowSize', - time_window_unit: 'timeWindowUnit', - threshold: 'threshold' - } - } - } - }; - }); - - it('should call the getPropsFromUpstreamJson method of BaseWatch', () => { - ThresholdWatch.fromUpstreamJson(upstreamJson); - - expect(getPropsFromUpstreamJsonMock.called).to.be(true); - }); - - it('should generate a valid ThresholdWatch object', () => { - const actual = ThresholdWatch.fromUpstreamJson(upstreamJson); - const expected = { - index: 'index', - timeField: 'timeField', - triggerIntervalSize: 'triggerIntervalSize', - triggerIntervalUnit: 'triggerIntervalUnit', - aggType: 'aggType', - aggField: 'aggField', - termSize: 'termSize', - termField: 'termField', - thresholdComparator: 'thresholdComparator', - timeWindowSize: 'timeWindowSize', - timeWindowUnit: 'timeWindowUnit', - threshold: ['threshold'] - }; - - expect(actual).to.eql(expected); - }); - - }); - - describe('fromDownstreamJson factory method', () => { - - let downstreamJson; - beforeEach(() => { - getPropsFromDownstreamJsonMock.resetHistory(); - - downstreamJson = { - index: 'index', - timeField: 'timeField', - triggerIntervalSize: 'triggerIntervalSize', - triggerIntervalUnit: 'triggerIntervalUnit', - aggType: 'aggType', - aggField: 'aggField', - termSize: 'termSize', - termField: 'termField', - thresholdComparator: 'thresholdComparator', - timeWindowSize: 'timeWindowSize', - timeWindowUnit: 'timeWindowUnit', - threshold: 'threshold' - }; - }); - - it('should call the getPropsFromDownstreamJson method of BaseWatch', () => { - ThresholdWatch.fromDownstreamJson(downstreamJson); - - expect(getPropsFromDownstreamJsonMock.called).to.be(true); - }); - - it('should generate a valid ThresholdWatch object', () => { - const actual = ThresholdWatch.fromDownstreamJson(downstreamJson); - const expected = { - index: 'index', - timeField: 'timeField', - triggerIntervalSize: 'triggerIntervalSize', - triggerIntervalUnit: 'triggerIntervalUnit', - aggType: 'aggType', - aggField: 'aggField', - termSize: 'termSize', - termField: 'termField', - thresholdComparator: 'thresholdComparator', - timeWindowSize: 'timeWindowSize', - timeWindowUnit: 'timeWindowUnit', - threshold: 'threshold' - }; - - expect(actual).to.eql(expected); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/watch.js b/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/watch.js deleted file mode 100644 index 14d5005b13e04..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/watch.js +++ /dev/null @@ -1,130 +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 '@kbn/expect'; -import sinon from 'sinon'; -import proxyquire from 'proxyquire'; -import { WATCH_TYPES } from '../../../../common/constants'; - -const watchTypeMocks = {}; -function buildMock(watchType) { - const fromDownstreamJsonMock = sinon.stub(); - const fromUpstreamJsonMock = sinon.stub(); - - watchTypeMocks[watchType] = { - fromDownstreamJsonMock, - fromUpstreamJsonMock, - Class: class WatchStub { - static fromDownstreamJson(...args) { - fromDownstreamJsonMock(...args); - } - static fromUpstreamJson(...args) { - fromUpstreamJsonMock(...args); - } - } - }; -} - -buildMock(WATCH_TYPES.JSON); -buildMock(WATCH_TYPES.THRESHOLD); -buildMock(WATCH_TYPES.MONITORING); - -const { Watch } = proxyquire('../watch', { - './json_watch': { JsonWatch: watchTypeMocks[WATCH_TYPES.JSON].Class }, - './monitoring_watch': { MonitoringWatch: watchTypeMocks[WATCH_TYPES.MONITORING].Class }, - './threshold_watch': { ThresholdWatch: watchTypeMocks[WATCH_TYPES.THRESHOLD].Class } -}); - -describe('Watch', () => { - - describe('getWatchTypes factory method', () => { - - it(`There should be a property for each watch type`, () => { - // NOTE: If this test is failing because a new watch type was added - // make sure you add a 'returns an instance of' test for the new type - // as well. - - const watchTypes = Watch.getWatchTypes(); - const expected = Object.values(WATCH_TYPES).sort(); - const actual = Object.keys(watchTypes).sort(); - - expect(actual).to.eql(expected); - }); - - }); - - describe('fromDownstreamJson factory method', () => { - - beforeEach(() => { - Object.keys(watchTypeMocks).forEach(key => { - watchTypeMocks[key].fromDownstreamJsonMock.resetHistory(); - }); - }); - - it(`throws an error if no 'type' property in json`, () => { - expect(Watch.fromDownstreamJson).withArgs({}) - .to.throwError(/must contain an type property/i); - }); - - it(`throws an error if the type does not correspond to a WATCH_TYPES value`, () => { - expect(Watch.fromDownstreamJson).withArgs({ type: 'foo' }) - .to.throwError(/Attempted to load unknown type foo/i); - }); - - it('fromDownstreamJson of JsonWatch to be called when type is WATCH_TYPES.JSON', () => { - Watch.fromDownstreamJson({ type: WATCH_TYPES.JSON }); - expect(watchTypeMocks[WATCH_TYPES.JSON].fromDownstreamJsonMock.called).to.be(true); - }); - - it('fromDownstreamJson of ThresholdWatch to be called when type is WATCH_TYPES.THRESHOLD', () => { - Watch.fromDownstreamJson({ type: WATCH_TYPES.THRESHOLD }); - expect(watchTypeMocks[WATCH_TYPES.THRESHOLD].fromDownstreamJsonMock.called).to.be(true); - }); - - it('fromDownstreamJson of MonitoringWatch to be called when type is WATCH_TYPES.MONITORING', () => { - Watch.fromDownstreamJson({ type: WATCH_TYPES.MONITORING }); - expect(watchTypeMocks[WATCH_TYPES.MONITORING].fromDownstreamJsonMock.called).to.be(true); - }); - - }); - - describe('fromUpstreamJson factory method', () => { - - beforeEach(() => { - Object.keys(watchTypeMocks).forEach(key => { - watchTypeMocks[key].fromUpstreamJsonMock.resetHistory(); - }); - }); - - it(`throws an error if no 'watchJson' property in json`, () => { - expect(Watch.fromUpstreamJson).withArgs({}) - .to.throwError(/must contain a watchJson property/i); - }); - - it('fromUpstreamJson of JsonWatch to be called when type is WATCH_TYPES.JSON', () => { - Watch.fromUpstreamJson({ - watchJson: { metadata: { xpack: { type: WATCH_TYPES.JSON } } } - }); - expect(watchTypeMocks[WATCH_TYPES.JSON].fromUpstreamJsonMock.called).to.be(true); - }); - - it('fromUpstreamJson of ThresholdWatch to be called when type is WATCH_TYPES.THRESHOLD', () => { - Watch.fromUpstreamJson({ - watchJson: { metadata: { xpack: { type: WATCH_TYPES.THRESHOLD } } } - }); - expect(watchTypeMocks[WATCH_TYPES.THRESHOLD].fromUpstreamJsonMock.called).to.be(true); - }); - - it('fromUpstreamJson of MonitoringWatch to be called when type is WATCH_TYPES.MONITORING', () => { - Watch.fromUpstreamJson({ - watchJson: { metadata: { xpack: { type: WATCH_TYPES.MONITORING } } } - }); - expect(watchTypeMocks[WATCH_TYPES.MONITORING].fromUpstreamJsonMock.called).to.be(true); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js b/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js index ada4cbc2dd2b2..f96274594872a 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js +++ b/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js @@ -6,7 +6,7 @@ import { get, map, pick } from 'lodash'; import { badRequest } from 'boom'; -import { Action } from '../action'; +import { Action } from '../../../common/models/action'; import { WatchStatus } from '../watch_status'; import { i18n } from '@kbn/i18n'; import { WatchErrors } from '../watch_errors'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/base_watch.js b/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.test.js similarity index 64% rename from x-pack/legacy/plugins/watcher/server/models/watch/__tests__/base_watch.js rename to x-pack/legacy/plugins/watcher/server/models/watch/base_watch.test.js index fbbbd52c7987b..a19049e07fd9e 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/__tests__/base_watch.js +++ b/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.test.js @@ -4,49 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import proxyquire from 'proxyquire'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -const actionFromUpstreamJSONMock = sinon.stub(); -const actionFromDownstreamJSONMock = sinon.stub(); -const watchStatusFromUpstreamJSONMock = sinon.stub(); -const watchErrorsFromUpstreamJSONMock = sinon.stub(); - -class ActionStub { - static fromUpstreamJson(...args) { - actionFromUpstreamJSONMock(...args); - return { foo: 'bar' }; - } - - static fromDownstreamJson(...args) { - actionFromDownstreamJSONMock(...args); - return { foo: 'bar' }; - } -} - -class WatchStatusStub { - static fromUpstreamJson(...args) { - watchStatusFromUpstreamJSONMock(...args); - return { foo: 'bar' }; - } -} - -class WatchErrorsStub { - static fromUpstreamJson(...args) { - watchErrorsFromUpstreamJSONMock(...args); - return { foo: 'bar' }; - } -} - -const { BaseWatch } = proxyquire('../base_watch', { - '../action': { Action: ActionStub }, - '../watch_status': { WatchStatus: WatchStatusStub }, - '../watch_errors': { WatchErrors: WatchErrorsStub }, -}); +import { BaseWatch } from './base_watch'; describe('BaseWatch', () => { - describe('Constructor', () => { let props; @@ -71,13 +31,13 @@ describe('BaseWatch', () => { 'actions' ]; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); it('should default isSystemWatch to false', () => { const watch = new BaseWatch(props); - expect(watch.isSystemWatch).to.be(false); + expect(watch.isSystemWatch).toBe(false); }); it('populates all expected fields', () => { @@ -96,7 +56,7 @@ describe('BaseWatch', () => { actions: 'baz' }; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); }); @@ -124,7 +84,7 @@ describe('BaseWatch', () => { } }; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); it('should only populate the name metadata if a name is defined', () => { @@ -139,7 +99,7 @@ describe('BaseWatch', () => { } }; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); }); @@ -151,7 +111,7 @@ describe('BaseWatch', () => { const actual = watch.getVisualizeQuery(); const expected = {}; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); }); @@ -163,7 +123,7 @@ describe('BaseWatch', () => { const actual = watch.formatVisualizeData(); const expected = []; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); }); @@ -211,7 +171,7 @@ describe('BaseWatch', () => { actions: props.actions.map(a => a.downstreamJson) }; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); it('should respect an undefined watchStatus & watchErrors prop', () => { @@ -231,7 +191,7 @@ describe('BaseWatch', () => { actions: props.actions.map(a => a.downstreamJson) }; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); }); @@ -275,7 +235,7 @@ describe('BaseWatch', () => { } }; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); }); @@ -284,8 +244,6 @@ describe('BaseWatch', () => { let downstreamJson; beforeEach(() => { - actionFromDownstreamJSONMock.resetHistory(); - downstreamJson = { id: 'my-watch', name: 'foo', @@ -302,45 +260,27 @@ describe('BaseWatch', () => { 'actions' ]; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); it('should properly map id and name', () => { const props = BaseWatch.getPropsFromDownstreamJson(downstreamJson); - expect(props.id).to.be('my-watch'); - expect(props.name).to.be('foo'); + expect(props.id).toBe('my-watch'); + expect(props.name).toBe('foo'); }); it('should return an actions property that is an array', () => { const props = BaseWatch.getPropsFromDownstreamJson(downstreamJson); - expect(Array.isArray(props.actions)).to.be(true); - expect(props.actions.length).to.be(0); - }); - - it('should call Action.fromUDownstreamJSON for each action', () => { - const action0 = { type: 'email', id: 'email1' }; - const action1 = { type: 'logging', id: 'logging1' }; - - downstreamJson.actions.push(action0); - downstreamJson.actions.push(action1); - - const props = BaseWatch.getPropsFromDownstreamJson(downstreamJson); - - expect(props.actions.length).to.be(2); - expect(actionFromDownstreamJSONMock.calledWith(action0)).to.be(true); - expect(actionFromDownstreamJSONMock.calledWith(action1)).to.be(true); + expect(Array.isArray(props.actions)).toBe(true); + expect(props.actions.length).toBe(0); }); - }); describe('getPropsFromUpstreamJson method', () => { let upstreamJson; beforeEach(() => { - actionFromUpstreamJSONMock.resetHistory(); - watchStatusFromUpstreamJSONMock.resetHistory(); - upstreamJson = { id: 'my-watch', type: 'json', @@ -363,22 +303,25 @@ describe('BaseWatch', () => { it(`throws an error if no 'id' property in json`, () => { delete upstreamJson.id; - expect(BaseWatch.getPropsFromUpstreamJson).withArgs(upstreamJson) - .to.throwError(/must contain an id property/i); + expect(() => { + BaseWatch.getPropsFromUpstreamJson(upstreamJson); + }).toThrow(/must contain an id property/i); }); it(`throws an error if no 'watchJson' property in json`, () => { delete upstreamJson.watchJson; - expect(BaseWatch.getPropsFromUpstreamJson).withArgs(upstreamJson) - .to.throwError(/must contain a watchJson property/i); + expect(() => { + BaseWatch.getPropsFromUpstreamJson(upstreamJson); + }).toThrow(/must contain a watchJson property/i); }); it(`throws an error if no 'watchStatusJson' property in json`, () => { delete upstreamJson.watchStatusJson; - expect(BaseWatch.getPropsFromUpstreamJson).withArgs(upstreamJson) - .to.throwError(/must contain a watchStatusJson property/i); + expect(() => { + BaseWatch.getPropsFromUpstreamJson(upstreamJson); + }).toThrow(/must contain a watchStatusJson property/i); }); it(`should ignore unknown watchJson properties`, () => { @@ -408,7 +351,7 @@ describe('BaseWatch', () => { 'throttle_period_in_millis' ]; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); it('should return a valid props object', () => { @@ -423,69 +366,20 @@ describe('BaseWatch', () => { 'actions' ]; - expect(actual).to.eql(expected); + expect(actual).toEqual(expected); }); it('should pull name out of metadata', () => { const props = BaseWatch.getPropsFromUpstreamJson(upstreamJson); - expect(props.name).to.be('foo'); + expect(props.name).toBe('foo'); }); it('should return an actions property that is an array', () => { const props = BaseWatch.getPropsFromUpstreamJson(upstreamJson); - expect(Array.isArray(props.actions)).to.be(true); - expect(props.actions.length).to.be(0); - }); - - it('should call Action.fromUpstreamJson for each action', () => { - upstreamJson.watchJson.actions = { - 'my-logging-action': { - 'logging': { - 'text': 'foo' - } - }, - 'my-unknown-action': { - 'foobar': {} - } - }; - - const props = BaseWatch.getPropsFromUpstreamJson(upstreamJson); - - expect(props.actions.length).to.be(2); - expect(actionFromUpstreamJSONMock.calledWith({ - id: 'my-logging-action', - actionJson: { - 'logging': { - 'text': 'foo' - } - } - })).to.be(true); - expect(actionFromUpstreamJSONMock.calledWith({ - id: 'my-unknown-action', - actionJson: { - 'foobar': {} - } - })).to.be(true); - }); - - it('should call WatchStatus.fromUpstreamJson for the watch status', () => { - BaseWatch.getPropsFromUpstreamJson(upstreamJson); - - expect(watchStatusFromUpstreamJSONMock.calledWith({ - id: 'my-watch', - watchStatusJson: { - state: { - active: true - } - }, - watchErrors: { - foo: 'bar' - } - })).to.be(true); + expect(Array.isArray(props.actions)).toBe(true); + expect(props.actions.length).toBe(0); }); - }); - }); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js b/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js index 343c76054d2e0..e319cc1bc277b 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js +++ b/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js @@ -7,6 +7,7 @@ import { isEmpty, cloneDeep, has, merge } from 'lodash'; import { BaseWatch } from './base_watch'; import { WATCH_TYPES } from '../../../common/constants'; +import { serializeJsonWatch } from '../../../common/lib/serialization'; export class JsonWatch extends BaseWatch { // This constructor should not be used directly. @@ -19,13 +20,7 @@ export class JsonWatch extends BaseWatch { } get watchJson() { - const result = merge( - {}, - super.watchJson, - this.watch - ); - - return result; + return serializeJsonWatch(this.name, this.watch); } // To Elasticsearch diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.test.js b/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.test.js new file mode 100644 index 0000000000000..383c7e082d28a --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.test.js @@ -0,0 +1,166 @@ +/* + * 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 { JsonWatch } from './json_watch'; + +describe('JsonWatch', () => { + describe('Constructor', () => { + let props; + beforeEach(() => { + props = { + watch: 'foo' + }; + }); + + it('should populate all expected fields', () => { + const actual = new JsonWatch(props); + const expected = { + watch: 'foo' + }; + + expect(actual).toMatchObject(expected); + }); + }); + + describe('watchJson getter method', () => { + let props; + beforeEach(() => { + props = { + watch: { foo: 'bar' }, + metadata: { + xpack: { + type: 'json', + }, + }, + }; + }); + + it('should return the correct result', () => { + const watch = new JsonWatch(props); + const expected = { + foo: 'bar', + metadata: { + xpack: { + type: 'json', + }, + }, + }; + expect(watch.watchJson).toEqual(expected); + }); + }); + + describe('upstreamJson getter method', () => { + it('should return the correct result', () => { + const watch = new JsonWatch({ watch: { foo: 'bar' } }); + const actual = watch.upstreamJson; + const expected = { + id: undefined, + watch: { + foo: 'bar', + metadata: { + xpack: { + type: 'json', + }, + }, + }, + }; + + expect(actual).toEqual(expected); + }); + }); + + describe('downstreamJson getter method', () => { + let props; + beforeEach(() => { + props = { + watch: 'foo', + watchJson: 'bar' + }; + }); + + it('should return the correct result', () => { + const watch = new JsonWatch(props); + const actual = watch.downstreamJson; + const expected = { + watch: 'foo', + isSystemWatch: false, + actions: [], + }; + + expect(actual).toEqual(expected); + }); + }); + + describe('fromUpstreamJson factory method', () => { + let upstreamJson; + beforeEach(() => { + upstreamJson = { + id: 'id', + watchStatusJson: {}, + watchJson: { + trigger: 'trigger', + input: 'input', + condition: 'condition', + actions: 'actions', + metadata: 'metadata', + transform: 'transform', + throttle_period: 'throttle_period', + throttle_period_in_millis: 'throttle_period_in_millis', + } + }; + }); + + it('should clone the watchJson property into a watch property', () => { + const jsonWatch = JsonWatch.fromUpstreamJson(upstreamJson); + + expect(jsonWatch.watch).toEqual(upstreamJson.watchJson); + expect(jsonWatch.watch).not.toBe(upstreamJson.watchJson); + }); + + it('should remove the metadata.name property from the watch property', () => { + upstreamJson.watchJson.metadata = { name: 'foobar', foo: 'bar' }; + + const jsonWatch = JsonWatch.fromUpstreamJson(upstreamJson); + + expect(jsonWatch.watch.metadata.name).toBe(undefined); + }); + + it('should remove the metadata.xpack property from the watch property', () => { + upstreamJson.watchJson.metadata = { + name: 'foobar', + xpack: { prop: 'val' }, + foo: 'bar' + }; + + const jsonWatch = JsonWatch.fromUpstreamJson(upstreamJson); + + expect(jsonWatch.watch.metadata.xpack).toBe(undefined); + }); + + it('should remove an empty metadata property from the watch property', () => { + upstreamJson.watchJson.metadata = { name: 'foobar' }; + + const jsonWatch = JsonWatch.fromUpstreamJson(upstreamJson); + + expect(jsonWatch.watch.metadata).toBe(undefined); + }); + }); + + describe('fromDownstreamJson factory method', () => { + let downstreamJson; + beforeEach(() => { + downstreamJson = { + watch: { foo: { bar: 'baz' } } + }; + }); + + it('should copy the watch property', () => { + const jsonWatch = JsonWatch.fromDownstreamJson(downstreamJson); + + expect(jsonWatch.watch).toEqual(downstreamJson.watch); + }); + }); +}); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.test.js b/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.test.js new file mode 100644 index 0000000000000..f93d94bc23f80 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.test.js @@ -0,0 +1,107 @@ +/* + * 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 { MonitoringWatch } from './monitoring_watch'; + +describe('MonitoringWatch', () => { + describe('Constructor', () => { + let props; + beforeEach(() => { + props = {}; + }); + + it('should populate all expected fields', () => { + const actual = new MonitoringWatch(props); + const expected = { + isSystemWatch: true + }; + + expect(actual).toEqual(expected); + }); + }); + + describe('watchJson getter method', () => { + it('should return an empty object', () => { + const watch = new MonitoringWatch({}); + const actual = watch.watchJson; + const expected = { + metadata: { + xpack: {}, + }, + }; + + expect(actual).toEqual(expected); + }); + }); + + describe('getVisualizeQuery method', () => { + it(`throws an error`, () => { + const watch = new MonitoringWatch({}); + + expect(() => watch.getVisualizeQuery()).toThrow(/getVisualizeQuery called for monitoring watch/i); + }); + }); + + describe('formatVisualizeData method', () => { + it(`throws an error`, () => { + const watch = new MonitoringWatch({}); + + expect(() => watch.formatVisualizeData()).toThrow(/formatVisualizeData called for monitoring watch/i); + }); + }); + + describe('upstreamJson getter method', () => { + it(`throws an error`, () => { + const watch = new MonitoringWatch({}); + + expect(() => watch.upstreamJson).toThrow(/upstreamJson called for monitoring watch/i); + }); + }); + + describe('downstreamJson getter method', () => { + let props; + beforeEach(() => { + props = {}; + }); + + it('should return the correct result', () => { + const watch = new MonitoringWatch(props); + const actual = watch.downstreamJson; + const expected = { + actions: [], + isSystemWatch: true, + }; + + expect(actual).toEqual(expected); + }); + }); + + describe('fromUpstreamJson factory method', () => { + it('should generate a valid MonitoringWatch object', () => { + const actual = MonitoringWatch.fromUpstreamJson({ + id: 'id', + watchJson: {}, + watchStatusJson: {}, + }); + + const expected = { + id: 'id', + isSystemWatch: true, + actions: [], + type: 'monitoring', + }; + + expect(actual).toMatchObject(expected); + }); + }); + + describe('fromDownstreamJson factory method', () => { + it(`throws an error`, () => { + expect(() => MonitoringWatch.fromDownstreamJson({})) + .toThrow(/fromDownstreamJson called for monitoring watch/i); + }); + }); +}); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_metadata.js b/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_metadata.js deleted file mode 100644 index dc8dc8fed84fd..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_metadata.js +++ /dev/null @@ -1,28 +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. - */ - -/* -watch.metadata - */ - -export function buildMetadata(watch) { - return { - watcherui: { - index: watch.index, - time_field: watch.timeField, - trigger_interval_size: watch.triggerIntervalSize, - trigger_interval_unit: watch.triggerIntervalUnit, - agg_type: watch.aggType, - agg_field: watch.aggField, - term_size: watch.termSize, - term_field: watch.termField, - threshold_comparator: watch.thresholdComparator, - time_window_size: watch.timeWindowSize, - time_window_unit: watch.timeWindowUnit, - threshold: watch.threshold - } - }; -} diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js b/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js index e3de2a5744e35..ab9daf6f636a1 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js +++ b/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js @@ -5,7 +5,7 @@ */ import { cloneDeep } from 'lodash'; -import { buildInput } from './build_input'; +import { buildInput } from '../../../../common/lib/serialization'; import { AGG_TYPES } from '../../../../common/constants'; /* @@ -93,7 +93,8 @@ function buildAggs(body, { aggType, termField }, dateAgg) { } export function buildVisualizeQuery(watch, visualizeOptions) { - const watchInput = buildInput(watch); + const { index, timeWindowSize, timeWindowUnit, timeField, aggType, aggField, termField, termSize, termOrder } = watch; + const watchInput = buildInput({ index, timeWindowSize, timeWindowUnit, timeField, aggType, aggField, termField, termSize, termOrder }); const body = watchInput.search.request.body; const dateAgg = buildDateAgg({ field: watch.timeField, diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js b/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js index 4fbc0111f57dd..cb40c46ac6435 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js +++ b/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js @@ -7,12 +7,8 @@ import { merge } from 'lodash'; import { BaseWatch } from '../base_watch'; import { WATCH_TYPES, COMPARATORS, SORT_ORDERS } from '../../../../common/constants'; -import { buildActions } from './build_actions'; -import { buildCondition } from './build_condition'; -import { buildInput } from './build_input'; -import { buildMetadata } from './build_metadata'; -import { buildTransform } from './build_transform'; -import { buildTrigger } from './build_trigger'; +import { serializeThresholdWatch } from '../../../../common/lib/serialization'; + import { buildVisualizeQuery } from './build_visualize_query'; import { formatVisualizeData } from './format_visualize_data'; @@ -46,20 +42,7 @@ export class ThresholdWatch extends BaseWatch { } get watchJson() { - const result = merge( - {}, - super.watchJson, - { - trigger: buildTrigger(this), - input: buildInput(this), - condition: buildCondition(this), - transform: buildTransform(this), - actions: buildActions(this), - metadata: buildMetadata(this) - } - ); - - return result; + return serializeThresholdWatch(this); } getVisualizeQuery(visualizeOptions) { @@ -128,26 +111,41 @@ export class ThresholdWatch extends BaseWatch { } // from Kibana - static fromDownstreamJson(json) { - const props = merge( - {}, - super.getPropsFromDownstreamJson(json), - { - type: WATCH_TYPES.THRESHOLD, - index: json.index, - timeField: json.timeField, - triggerIntervalSize: json.triggerIntervalSize, - triggerIntervalUnit: json.triggerIntervalUnit, - aggType: json.aggType, - aggField: json.aggField, - termSize: json.termSize, - termField: json.termField, - thresholdComparator: json.thresholdComparator, - timeWindowSize: json.timeWindowSize, - timeWindowUnit: json.timeWindowUnit, - threshold: json.threshold - } - ); + static fromDownstreamJson({ + id, + name, + actions, + index, + timeField, + triggerIntervalSize, + triggerIntervalUnit, + aggType, + aggField, + termSize, + termField, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + threshold, + }) { + const props = { + type: WATCH_TYPES.THRESHOLD, + id, + name, + actions, + index, + timeField, + triggerIntervalSize, + triggerIntervalUnit, + aggType, + aggField, + termSize, + termField, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + threshold, + }; return new ThresholdWatch(props); } diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js b/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js new file mode 100644 index 0000000000000..4a0b7b657bbc6 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js @@ -0,0 +1,241 @@ +/* + * 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 { COMPARATORS, SORT_ORDERS } from '../../../../common/constants'; +import { WatchErrors } from '../../watch_errors'; +import { ThresholdWatch } from './threshold_watch'; + +describe('ThresholdWatch', () => { + describe('Constructor', () => { + let props; + beforeEach(() => { + props = { + index: 'index', + timeField: 'timeField', + triggerIntervalSize: 'triggerIntervalSize', + triggerIntervalUnit: 'triggerIntervalUnit', + aggType: 'aggType', + aggField: 'aggField', + termSize: 'termSize', + termField: 'termField', + thresholdComparator: 'thresholdComparator', + timeWindowSize: 'timeWindowSize', + timeWindowUnit: 'timeWindowUnit', + threshold: 'threshold' + }; + }); + + it('should populate all expected fields', () => { + const actual = new ThresholdWatch(props); + const expected = { + id: undefined, + name: undefined, + type: undefined, + isSystemWatch: false, + watchStatus: undefined, + watchErrors: undefined, + actions: undefined, + index: 'index', + timeField: 'timeField', + triggerIntervalSize: 'triggerIntervalSize', + triggerIntervalUnit: 'triggerIntervalUnit', + aggType: 'aggType', + aggField: 'aggField', + termSize: 'termSize', + termField: 'termField', + thresholdComparator: 'thresholdComparator', + timeWindowSize: 'timeWindowSize', + timeWindowUnit: 'timeWindowUnit', + threshold: 'threshold' + }; + + expect(actual).toEqual(expected); + }); + }); + + describe('hasTermAgg getter method', () => { + + it('should return true if termField is defined', () => { + const downstreamJson = { termField: 'foobar' }; + const thresholdWatch = ThresholdWatch.fromDownstreamJson(downstreamJson); + + expect(thresholdWatch.hasTermsAgg).toBe(true); + }); + + it('should return false if termField is undefined', () => { + const downstreamJson = { termField: undefined }; + const thresholdWatch = ThresholdWatch.fromDownstreamJson(downstreamJson); + + expect(thresholdWatch.hasTermsAgg).toBe(false); + }); + + }); + + describe('termOrder getter method', () => { + + it('should return SORT_ORDERS.DESCENDING if thresholdComparator is COMPARATORS.GREATER_THAN', () => { + const downstreamJson = { thresholdComparator: COMPARATORS.GREATER_THAN }; + const thresholdWatch = ThresholdWatch.fromDownstreamJson(downstreamJson); + + expect(thresholdWatch.termOrder).toBe(SORT_ORDERS.DESCENDING); + }); + + it('should return SORT_ORDERS.ASCENDING if thresholdComparator is not COMPARATORS.GREATER_THAN', () => { + const downstreamJson = { thresholdComparator: 'foo' }; + const thresholdWatch = ThresholdWatch.fromDownstreamJson(downstreamJson); + + expect(thresholdWatch.termOrder).toBe(SORT_ORDERS.ASCENDING); + }); + }); + + describe('downstreamJson getter method', () => { + let props; + beforeEach(() => { + props = { + index: 'index', + timeField: 'timeField', + triggerIntervalSize: 'triggerIntervalSize', + triggerIntervalUnit: 'triggerIntervalUnit', + aggType: 'aggType', + aggField: 'aggField', + termSize: 'termSize', + termField: 'termField', + thresholdComparator: 'thresholdComparator', + timeWindowSize: 'timeWindowSize', + timeWindowUnit: 'timeWindowUnit', + threshold: 'threshold' + }; + }); + + it('should return the correct result', () => { + const watch = new ThresholdWatch(props); + const actual = watch.downstreamJson; + const expected = { + actions: [], + isSystemWatch: false, + index: 'index', + timeField: 'timeField', + triggerIntervalSize: 'triggerIntervalSize', + triggerIntervalUnit: 'triggerIntervalUnit', + aggType: 'aggType', + aggField: 'aggField', + termSize: 'termSize', + termField: 'termField', + thresholdComparator: 'thresholdComparator', + timeWindowSize: 'timeWindowSize', + timeWindowUnit: 'timeWindowUnit', + threshold: 'threshold' + }; + + expect(actual).toEqual(expected); + }); + }); + + describe('fromUpstreamJson factory method', () => { + let upstreamJson; + beforeEach(() => { + upstreamJson = { + id: 'id', + watchStatusJson: {}, + watchJson: { + foo: { bar: 'baz' }, + metadata: { + name: 'name', + watcherui: { + index: 'index', + time_field: 'timeField', + trigger_interval_size: 'triggerIntervalSize', + trigger_interval_unit: 'triggerIntervalUnit', + agg_type: 'aggType', + agg_field: 'aggField', + term_size: 'termSize', + term_field: 'termField', + threshold_comparator: 'thresholdComparator', + time_window_size: 'timeWindowSize', + time_window_unit: 'timeWindowUnit', + threshold: 'threshold' + } + } + } + }; + }); + + it('should generate a valid ThresholdWatch object', () => { + const actual = ThresholdWatch.fromUpstreamJson(upstreamJson); + const expected = { + id: 'id', + name: 'name', + isSystemWatch: false, + type: 'threshold', + actions: [], + index: 'index', + timeField: 'timeField', + triggerIntervalSize: 'triggerIntervalSize', + triggerIntervalUnit: 'triggerIntervalUnit', + aggType: 'aggType', + aggField: 'aggField', + termSize: 'termSize', + termField: 'termField', + thresholdComparator: 'thresholdComparator', + timeWindowSize: 'timeWindowSize', + timeWindowUnit: 'timeWindowUnit', + threshold: ['threshold'], + watchErrors: new WatchErrors(), + }; + + expect(actual).toMatchObject(expected); + }); + }); + + describe('fromDownstreamJson factory method', () => { + let downstreamJson; + beforeEach(() => { + downstreamJson = { + id: 'id', + name: 'name', + index: 'index', + timeField: 'timeField', + triggerIntervalSize: 'triggerIntervalSize', + triggerIntervalUnit: 'triggerIntervalUnit', + aggType: 'aggType', + aggField: 'aggField', + termSize: 'termSize', + termField: 'termField', + thresholdComparator: 'thresholdComparator', + timeWindowSize: 'timeWindowSize', + timeWindowUnit: 'timeWindowUnit', + threshold: 'threshold', + }; + }); + + it('should generate a valid ThresholdWatch object', () => { + const actual = ThresholdWatch.fromDownstreamJson(downstreamJson); + const expected = { + id: 'id', + name: 'name', + isSystemWatch: false, + type: 'threshold', + index: 'index', + actions: undefined, + timeField: 'timeField', + triggerIntervalSize: 'triggerIntervalSize', + triggerIntervalUnit: 'triggerIntervalUnit', + aggType: 'aggType', + aggField: 'aggField', + termSize: 'termSize', + termField: 'termField', + thresholdComparator: 'thresholdComparator', + timeWindowSize: 'timeWindowSize', + timeWindowUnit: 'timeWindowUnit', + threshold: 'threshold', + watchErrors: undefined, + watchStatus: undefined, + }; + + expect(actual).toEqual(expected); + }); + }); +}); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js b/x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js new file mode 100644 index 0000000000000..2895c23083def --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js @@ -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. + */ + +import { WATCH_TYPES } from '../../../common/constants'; +import { Watch } from './watch'; +import { JsonWatch } from './json_watch'; +import { MonitoringWatch } from './monitoring_watch'; +import { ThresholdWatch } from './threshold_watch'; + +describe('Watch', () => { + describe('getWatchTypes factory method', () => { + it(`There should be a property for each watch type`, () => { + const watchTypes = Watch.getWatchTypes(); + const expected = Object.values(WATCH_TYPES).sort(); + const actual = Object.keys(watchTypes).sort(); + + expect(actual).toEqual(expected); + }); + }); + + describe('fromDownstreamJson factory method', () => { + it(`throws an error if no 'type' property in json`, () => { + expect(() => Watch.fromDownstreamJson({})) + .toThrow(/must contain an type property/i); + }); + + it(`throws an error if the type does not correspond to a WATCH_TYPES value`, () => { + expect(() => Watch.fromDownstreamJson({ type: 'foo' })) + .toThrow(/Attempted to load unknown type foo/i); + }); + + it('JsonWatch to be used when type is WATCH_TYPES.JSON', () => { + const config = { type: WATCH_TYPES.JSON }; + expect(Watch.fromDownstreamJson(config)).toEqual(JsonWatch.fromDownstreamJson(config)); + }); + + it('ThresholdWatch to be used when type is WATCH_TYPES.THRESHOLD', () => { + const config = { type: WATCH_TYPES.THRESHOLD }; + expect(Watch.fromDownstreamJson(config)).toEqual(ThresholdWatch.fromDownstreamJson(config)); + }); + + it('MonitoringWatch to be used when type is WATCH_TYPES.MONITORING', () => { + const config = { type: WATCH_TYPES.MONITORING }; + expect(() => Watch.fromDownstreamJson(config)).toThrowError(); + }); + }); + + describe('fromUpstreamJson factory method', () => { + it(`throws an error if no 'watchJson' property in json`, () => { + expect(() => Watch.fromUpstreamJson({})) + .toThrow(/must contain a watchJson property/i); + }); + + it('JsonWatch to be used when type is WATCH_TYPES.JSON', () => { + const config = { + id: 'id', + watchStatusJson: {}, + watchJson: { metadata: { xpack: { type: WATCH_TYPES.JSON } } } + }; + expect(Watch.fromUpstreamJson(config)).toEqual(JsonWatch.fromUpstreamJson(config)); + }); + + it('ThresholdWatch to be used when type is WATCH_TYPES.THRESHOLD', () => { + const config = { + id: 'id', + watchStatusJson: {}, + watchJson: { metadata: { watcherui: {}, xpack: { type: WATCH_TYPES.THRESHOLD } } } + }; + expect(Watch.fromUpstreamJson(config)).toEqual(ThresholdWatch.fromUpstreamJson(config)); + }); + + it('MonitoringWatch to be used when type is WATCH_TYPES.MONITORING', () => { + const config = { + id: 'id', + watchStatusJson: {}, + watchJson: { metadata: { xpack: { type: WATCH_TYPES.MONITORING } } } + }; + expect(Watch.fromUpstreamJson(config)).toEqual(MonitoringWatch.fromUpstreamJson(config)); + }); + }); +}); diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js index ffc70e709907d..3cbb0a4e1cc47 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js +++ b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { WATCH_TYPES } from '../../../../common/constants'; +import { serializeJsonWatch, serializeThresholdWatch } from '../../../../common/lib/serialization'; import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Watch } from '../../../models/watch'; import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; import { wrapEsError, wrapUnknownError, wrapCustomError } from '../../../lib/error_wrappers'; import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; @@ -17,15 +18,14 @@ function fetchWatch(callWithRequest, watchId) { }); } -function saveWatch(callWithRequest, watch) { +function saveWatch(callWithRequest, id, body) { return callWithRequest('watcher.putWatch', { - id: watch.id, - body: watch.watch + id, + body, }); } export function registerSaveRoute(server) { - const isEsError = isEsErrorFactory(server); const licensePreRouting = licensePreRoutingFactory(server); @@ -34,22 +34,22 @@ export function registerSaveRoute(server) { method: 'PUT', handler: async (request) => { const callWithRequest = callWithRequestFactory(server, request); - const watchPayload = request.payload; + const { id, type, isNew, ...watchConfig } = request.payload; // For new watches, verify watch with the same ID doesn't already exist - if (watchPayload.isNew) { + if (isNew) { const conflictError = wrapCustomError( new Error(i18n.translate('xpack.watcher.saveRoute.duplicateWatchIdErrorMessage', { defaultMessage: 'There is already a watch with ID \'{watchId}\'.', values: { - watchId: watchPayload.id, + watchId: id, } })), 409 ); try { - const existingWatch = await fetchWatch(callWithRequest, watchPayload.id); + const existingWatch = await fetchWatch(callWithRequest, id); if (existingWatch.found) { throw conflictError; @@ -62,10 +62,21 @@ export function registerSaveRoute(server) { } } - const watchFromDownstream = Watch.fromDownstreamJson(watchPayload); + let serializedWatch; + + switch (type) { + case WATCH_TYPES.JSON: + const { name, watch } = watchConfig; + serializedWatch = serializeJsonWatch(name, watch); + break; + + case WATCH_TYPES.THRESHOLD: + serializedWatch = serializeThresholdWatch(watchConfig); + break; + } // Create new watch - return saveWatch(callWithRequest, watchFromDownstream.upstreamJson) + return saveWatch(callWithRequest, id, serializedWatch) .catch(err => { // Case: Error from Elasticsearch JS client if (isEsError(err)) { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b48f0412e8e91..5af2b55ae3dbd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11399,10 +11399,7 @@ "xpack.watcher.constants.watchStates.errorStateText": "エラー", "xpack.watcher.constants.watchStates.firingStateText": "実行中", "xpack.watcher.constants.watchStates.okStateText": "OK", - "xpack.watcher.models.action.actionJsonPropertyMissingBadRequestMessage": "json 引数には {actionJson} プロパティが含まれている必要があります", - "xpack.watcher.models.actionStatus.idPropertyMissingBadRequestMessage": "json 引数には {id} プロパティが含まれている必要があります", "xpack.watcher.models.actionStatus.notDetermineActionStatusBadImplementationMessage": "アクションステータスを把握できませんでした; action = {actionStatusJson}", - "xpack.watcher.models.baseAction.idPropertyMissingBadRequestMessage": "json 引数には {id} プロパティが含まれている必要があります", "xpack.watcher.models.baseAction.selectMessageText": "アクションを実行します。", "xpack.watcher.models.baseAction.simulateButtonLabel": "今すぐこのアクションをシミュレート", "xpack.watcher.models.baseAction.simulateMessage": "アクション {id} のシミュレーションが完了しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 83e9a49de55c6..859df25d25a8b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11401,10 +11401,7 @@ "xpack.watcher.constants.watchStates.errorStateText": "错误!", "xpack.watcher.constants.watchStates.firingStateText": "正在发送", "xpack.watcher.constants.watchStates.okStateText": "确定", - "xpack.watcher.models.action.actionJsonPropertyMissingBadRequestMessage": "json 参数必须包含 {actionJson} 属性", - "xpack.watcher.models.actionStatus.idPropertyMissingBadRequestMessage": "json 参数必须包含 {id} 属性", "xpack.watcher.models.actionStatus.notDetermineActionStatusBadImplementationMessage": "无法确定操作状态;操作 = {actionStatusJson}", - "xpack.watcher.models.baseAction.idPropertyMissingBadRequestMessage": "json 参数必须包含 {id} 属性", "xpack.watcher.models.baseAction.selectMessageText": "执行操作。", "xpack.watcher.models.baseAction.simulateButtonLabel": "立即模拟此操作", "xpack.watcher.models.baseAction.simulateMessage": "已成功模拟操作 {id}",