From b0522e34692dd5ecf54ae19da24594994939b8b2 Mon Sep 17 00:00:00 2001 From: Mihir Soni Date: Wed, 17 Apr 2019 16:57:43 -0700 Subject: [PATCH 1/3] Support Where clause in visual graph monitor --- .../MonitorExpressions/MonitorExpressions.js | 13 +- .../MonitorExpressions.test.js.snap | 28 ++ .../expressions/OfExpression.js | 5 +- .../expressions/WhereExpression.js | 228 ++++++++++++++++ .../expressions/WhereExpression.test.js | 91 +++++++ .../WhereExpression.test.js.snap | 28 ++ .../MonitorExpressions/expressions/index.js | 3 +- .../expressions/utils/constants.js | 55 ++++ .../expressions/utils/dataTypes.js | 14 +- .../expressions/utils/helpers.js | 6 + .../expressions/utils/whereHelpers.js | 63 +++++ .../expressions/utils/whereHelpers.test.js | 249 ++++++++++++++++++ .../containers/CreateMonitor/CreateMonitor.js | 4 +- .../__snapshots__/CreateMonitor.test.js.snap | 7 + .../formikToMonitor.test.js.snap | 102 +++++-- .../CreateMonitor/utils/constants.js | 9 + .../CreateMonitor/utils/formikToMonitor.js | 59 +++-- .../utils/formikToMonitor.test.js | 66 +++++ .../CreateMonitor/utils/whereFilters.js | 110 ++++++++ .../containers/DefineMonitor/DefineMonitor.js | 32 ++- .../__snapshots__/MonitorIndex.test.js.snap | 49 ++++ .../containers/Monitors/Monitors.test.js | 10 +- public/utils/constants.js | 7 + 23 files changed, 1171 insertions(+), 67 deletions(-) create mode 100644 public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.js create mode 100644 public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.test.js create mode 100644 public/pages/CreateMonitor/components/MonitorExpressions/expressions/__snapshots__/WhereExpression.test.js.snap create mode 100644 public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.js create mode 100644 public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.test.js create mode 100644 public/pages/CreateMonitor/containers/CreateMonitor/utils/whereFilters.js diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/MonitorExpressions.js b/public/pages/CreateMonitor/components/MonitorExpressions/MonitorExpressions.js index 12d95021..bf9bcd12 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/MonitorExpressions.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/MonitorExpressions.js @@ -16,7 +16,13 @@ import React, { Component } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ForExpression, OfExpression, OverExpression, WhenExpression } from './expressions'; +import { + ForExpression, + OfExpression, + OverExpression, + WhenExpression, + WhereExpression, +} from './expressions'; export const DEFAULT_CLOSED_STATES = { WHEN: false, @@ -24,6 +30,7 @@ export const DEFAULT_CLOSED_STATES = { THRESHOLD: false, OVER: false, FOR_THE_LAST: false, + WHERE: false, }; export default class MonitorExpressions extends Component { @@ -83,6 +90,10 @@ export default class MonitorExpressions extends Component { + + + + ); } diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/__snapshots__/MonitorExpressions.test.js.snap b/public/pages/CreateMonitor/components/MonitorExpressions/__snapshots__/MonitorExpressions.test.js.snap index 8f23cd7c..a72cf740 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/__snapshots__/MonitorExpressions.test.js.snap +++ b/public/pages/CreateMonitor/components/MonitorExpressions/__snapshots__/MonitorExpressions.test.js.snap @@ -88,5 +88,33 @@ exports[`MonitorExpressions renders 1`] = ` +
+
+
+ +
+
+
`; diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/OfExpression.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/OfExpression.js index 38f17f9a..08ed8ba0 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/OfExpression.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/OfExpression.js @@ -20,7 +20,8 @@ import { EuiPopover, EuiExpression } from '@elastic/eui'; import { FormikComboBox } from '../../../../../components/FormControls'; import { POPOVER_STYLE, EXPRESSION_STYLE, Expressions } from './utils/constants'; -import { getOptions } from './utils/dataTypes'; +import { getOfExpressionAllowedTypes } from './utils/helpers'; +import { getIndexFields } from './utils/dataTypes'; // TODO: EuiComboBox has an internal setState issue, waiting for EUI to fix it, remove this TODO when it is fixed @@ -60,7 +61,7 @@ class OfExpression extends Component { openExpression, dataTypes, } = this.props; - const options = getOptions(dataTypes, values); + const options = getIndexFields(dataTypes, getOfExpressionAllowedTypes(values)); const expressionWidth = Math.max( ...options.map(({ options }) => diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.js new file mode 100644 index 00000000..1e669219 --- /dev/null +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.js @@ -0,0 +1,228 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React, { PureComponent, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'formik'; +import { EuiFlexGroup, EuiFlexItem, EuiPopover, EuiExpression, EuiText } from '@elastic/eui'; +import _ from 'lodash'; +import { + Expressions, + POPOVER_STYLE, + EXPRESSION_STYLE, + WHERE_BOOLEAN_FILTERS, +} from './utils/constants'; +import { + getOperators, + displayText, + validateRange, + isNullOperator, + isRangeOperator, +} from './utils/whereHelpers'; +import { isInvalid, required } from '../../../../../utils/validate'; +import { + FormikComboBox, + FormikSelect, + FormikFieldNumber, + FormikFieldText, +} from '../../../../../components/FormControls'; +import { getIndexFields } from './utils/dataTypes'; +import { FORMIK_INITIAL_VALUES } from '../../../containers/CreateMonitor/utils/constants'; +import { DATA_TYPES } from '../../../../../utils/constants'; + +const propTypes = { + formik: PropTypes.object.isRequired, + dataTypes: PropTypes.object.isRequired, + onMadeChanges: PropTypes.func.isRequired, + openedStates: PropTypes.object.isRequired, + openExpression: PropTypes.func.isRequired, +}; + +class WhereExpression extends PureComponent { + constructor(props) { + super(props); + } + + handleFieldChange = (option, field, form) => { + this.props.onMadeChanges(); + this.resetValues(); + form.setFieldValue(field.name, option); + // User can remove where condition + if (option.length === 0) { + this.resetValues(); + form.setFieldError('where', undefined); + } + }; + + handleOperatorChange = (e, field) => { + this.props.onMadeChanges(); + field.onChange(e); + }; + + handleChangeWrapper = (e, field) => { + this.props.onMadeChanges(); + field.onChange(e); + }; + + handleClosePopOver = async () => { + const { + formik: { values }, + closeExpression, + } = this.props; + // Explicitly invoking validation, this component unmount after it closes. + if (values.where.fieldName.length > 0) { + await this.props.formik.validateForm(); + } + closeExpression(Expressions.WHERE); + }; + + resetValues = () => { + const { formik } = this.props; + formik.setValues({ + ...formik.values, + where: { ...FORMIK_INITIAL_VALUES.where }, + }); + }; + + renderBetweenAnd = () => { + const { + formik: { values }, + } = this.props; + return ( + + + validateRange(value, values.where), + }} + inputProps={{ onChange: this.handleChangeWrapper, isInvalid }} + /> + + + TO + + + validateRange(value, values.where), + }} + inputProps={{ onChange: this.handleChangeWrapper, isInvalid }} + /> + + + ); + }; + + renderValueField = (fieldType, fieldOperator) => { + if (fieldType == DATA_TYPES.NUMBER) { + return isRangeOperator(fieldOperator) ? ( + this.renderBetweenAnd() + ) : ( + + ); + } else if (fieldType == DATA_TYPES.BOOLEAN) { + return ( + + ); + } else { + return ( + + ); + } + }; + + render() { + const { + formik: { values }, + openedStates, + openExpression, + dataTypes, + } = this.props; + const indexFields = getIndexFields(dataTypes, ['number', 'text', 'keyword', 'boolean']); + const fieldType = _.get(values, 'where.fieldName[0].type', 'number'); + const fieldOperator = _.get(values, 'where.operator', 'is'); + + return ( + openExpression(Expressions.WHERE)} + /> + } + isOpen={openedStates.WHERE} + closePopover={this.handleClosePopOver} + panelPaddingSize="none" + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ + + + + + + + {!isNullOperator(fieldOperator) && ( + {this.renderValueField(fieldType, fieldOperator)} + )} + +
+
+ ); + } +} + +WhereExpression.propTypes = propTypes; + +export default connect(WhereExpression); diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.test.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.test.js new file mode 100644 index 00000000..7d98e756 --- /dev/null +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.test.js @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; +import { Formik } from 'formik'; +import { render, mount } from 'enzyme'; +import { EuiExpression, EuiSelect } from '@elastic/eui'; + +import { FORMIK_INITIAL_VALUES } from '../../../containers/CreateMonitor/utils/constants'; +import WhereExpression from './WhereExpression'; +import { FormikFieldText, FormikFieldNumber } from '../../../../../components/FormControls'; +import { OPERATORS_MAP } from './utils/constants'; + +const dataTypes = { + integer: new Set(['age']), + text: new Set(['cityName']), + keyword: new Set(['cityName.keyword']), +}; +const openExpression = jest.fn(); +const closeExpression = jest.fn(); +const getMountWrapper = (state = false) => ( + ( + + )} + /> +); + +describe('WhereExpression', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + test('renders', () => { + expect(render(getMountWrapper())).toMatchSnapshot(); + }); + test('calls openExpression when clicking expression', () => { + const wrapper = mount(getMountWrapper()); + const button = wrapper.find(EuiExpression); + button.simulate('click'); + wrapper.update(); + expect(openExpression).toHaveBeenCalled(); + }); + + test('calls closeExpression when closing popover', () => { + const wrapper = mount(getMountWrapper()); + const button = wrapper.find(EuiExpression); + button.simulate('click'); + expect(openExpression).toHaveBeenCalled(); + button.simulate('keyDown', { keyCode: 27 }); + expect(closeExpression).toHaveBeenCalled(); + }); + test('should render text input for the text data types', done => { + const wrapper = mount(getMountWrapper(true)); + wrapper + .find('[data-test-subj="comboBoxSearchInput"]') + .hostNodes() + .simulate('change', { target: { value: 'cityName' } }) + .simulate('keyDown', { keyCode: 40 }) + .simulate('keyDown', { keyCode: 13 }) + .simulate('blur'); + setTimeout(() => { + wrapper.update(); + const values = wrapper.find(WhereExpression).props().formik.values; + expect(values.where.fieldName).toEqual([{ label: 'cityName', type: 'text' }]); + expect(values.where.operator).toEqual(OPERATORS_MAP.IS); + expect(wrapper.find(FormikFieldText).length).toBe(1); + expect(wrapper.find(FormikFieldNumber).length).toBe(0); + done(); + }); + }); +}); diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/__snapshots__/WhereExpression.test.js.snap b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/__snapshots__/WhereExpression.test.js.snap new file mode 100644 index 00000000..d84826b4 --- /dev/null +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/__snapshots__/WhereExpression.test.js.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`WhereExpression renders 1`] = ` +
+
+ +
+
+`; diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/index.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/index.js index f10a654b..1821655b 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/index.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/index.js @@ -17,5 +17,6 @@ import ForExpression from './ForExpression'; import OfExpression from './OfExpression'; import OverExpression from './OverExpression'; import WhenExpression from './WhenExpression'; +import WhereExpression from './WhereExpression'; -export { ForExpression, OfExpression, OverExpression, WhenExpression }; +export { ForExpression, OfExpression, OverExpression, WhenExpression, WhereExpression }; diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants.js index 9d8b947f..758dcf56 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants.js @@ -21,6 +21,7 @@ export const Expressions = { OF_FIELD: 'OF_FIELD', OVER: 'OVER', FOR_THE_LAST: 'FOR_THE_LAST', + WHERE: 'WHERE', }; export const NUMBER_TYPES = [ 'long', @@ -37,7 +38,61 @@ export const UNITS_OF_TIME = [ { value: 'h', text: 'hour(s)' }, { value: 'd', text: 'day(s)' }, ]; + +export const WHERE_BOOLEAN_FILTERS = [ + { text: 'Select value', value: '' }, + { text: 'True', value: true }, + { text: 'False', value: false }, +]; + +export const OPERATORS_MAP = { + IS: 'is', + IS_NOT: 'is_not', + IS_NULL: 'is_null', + IS_NOT_NULL: 'is_not_null', + IS_GREATER: 'is_greater', + IS_GREATER_EQUAL: 'is_greater_equal', + IS_LESS: 'is_lesser', + IS_LESS_EQUAL: 'is_lesser_equal', + IS_LESS_EQUAL: 'is_lesser_equal', + STARTS_WITH: 'starts_with', + ENDS_WITH: 'ends_with', + CONTAINS: 'contains', + NOT_CONTAINS: 'does_not_contains', + IN_RANGE: 'in_range', + NOT_IN_RANGE: 'not_in_range', +}; + +export const COMPARISON_OPERATORS = [ + { text: 'is', value: OPERATORS_MAP.IS, dataTypes: ['number', 'text', 'keyword', 'boolean'] }, + { + text: 'is not', + value: OPERATORS_MAP.IS_NOT, + dataTypes: ['number', 'text', 'keyword', 'boolean'], + }, + { + text: 'is null', + value: OPERATORS_MAP.IS_NULL, + dataTypes: ['number', 'text', 'keyword', 'boolean'], + }, + { + text: 'is not null', + value: OPERATORS_MAP.IS_NOT_NULL, + dataTypes: ['number', 'text', 'keyword'], + }, + { text: 'is greater than', value: OPERATORS_MAP.IS_GREATER, dataTypes: ['number'] }, + { text: 'is greater than equal', value: OPERATORS_MAP.IS_GREATER_EQUAL, dataTypes: ['number'] }, + { text: 'is less than', value: OPERATORS_MAP.IS_LESS, dataTypes: ['number'] }, + { text: 'is less than equal', value: OPERATORS_MAP.IS_LESS_EQUAL, dataTypes: ['number'] }, + { text: 'is in range', value: OPERATORS_MAP.IN_RANGE, dataTypes: ['number'] }, + { text: 'is not in range', value: OPERATORS_MAP.NOT_IN_RANGE, dataTypes: ['number'] }, + { text: 'starts with', value: OPERATORS_MAP.STARTS_WITH, dataTypes: ['text', 'keyword'] }, + { text: 'ends with', value: OPERATORS_MAP.ENDS_WITH, dataTypes: ['text', 'keyword'] }, + { text: 'contains', value: OPERATORS_MAP.CONTAINS, dataTypes: ['text', 'keyword'] }, + { text: 'does not contains', value: OPERATORS_MAP.NOT_CONTAINS, dataTypes: ['text'] }, +]; export const OVER_TYPES = [{ value: 'all documents', text: 'all documents' }]; + export const AGGREGATION_TYPES = [ { value: 'avg', text: 'average()' }, { value: 'count', text: 'count()' }, diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/dataTypes.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/dataTypes.js index e1b7922d..e2bb8c02 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/dataTypes.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/dataTypes.js @@ -28,16 +28,8 @@ export function getFieldsForType(dataTypes, type) { return []; } -export function getOptions(dataTypes, formikValues) { - const types = formikToAllowedTypes(formikValues); - return types.map(type => ({ +export const getIndexFields = (dataTypes, allowedTypes) => + allowedTypes.map(type => ({ label: type, - options: getFieldsForType(dataTypes, type).map(field => ({ label: field })), + options: getFieldsForType(dataTypes, type).map(field => ({ label: field, type })), })); -} - -export function formikToAllowedTypes(values) { - const types = ['number']; - if (['min', 'max'].includes(values.aggregationType)) types.push('date'); - return types; -} diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/helpers.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/helpers.js index 7dca47e5..ba8bb39c 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/helpers.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/helpers.js @@ -16,3 +16,9 @@ export function selectOptionValueToText(optionValue, options) { return options.find(opt => opt.value === optionValue).text; } + +export function getOfExpressionAllowedTypes(values) { + const types = ['number']; + if (['min', 'max'].includes(values.aggregationType)) types.push('date'); + return types; +} diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.js new file mode 100644 index 00000000..97988f4d --- /dev/null +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.js @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import _ from 'lodash'; +import { COMPARISON_OPERATORS, OPERATORS_MAP } from './constants'; + +export const getOperators = fieldType => + COMPARISON_OPERATORS.reduce( + (acc, currentOperator) => + currentOperator.dataTypes.includes(fieldType) + ? [...acc, { text: currentOperator.text, value: currentOperator.value }] + : acc, + [] + ); + +export const isRangeOperator = selectedOperator => + [OPERATORS_MAP.IN_RANGE, OPERATORS_MAP.NOT_IN_RANGE].includes(selectedOperator); +export const isNullOperator = selectedOperator => + [OPERATORS_MAP.IS_NULL, OPERATORS_MAP.IS_NOT_NULL].includes(selectedOperator); + +export const displayText = whereValues => { + const whereFieldName = _.get(whereValues, 'fieldName[0].label', undefined); + if (!whereFieldName) { + return 'Select a field'; + } + const selectedOperator = _.get(whereValues, 'operator', 'is'); + const operatorObj = + COMPARISON_OPERATORS.find(operator => operator.value === selectedOperator) || {}; + const initialText = `${whereFieldName} ${operatorObj.text || ''}`; + + if (isRangeOperator(selectedOperator)) { + const startRange = _.get(whereValues, 'fieldRangeStart', 0); + const endRange = _.get(whereValues, 'fieldRangeEnd', 0); + return `${initialText} from ${startRange} to ${endRange}`; + } else if (isNullOperator(selectedOperator)) { + return `${initialText}`; + } else { + const value = _.get(whereValues, 'fieldValue', ''); + return `${initialText} ${value}`; + } +}; + +export const validateRange = (value, whereFilters) => { + if (value === '') return 'Required'; + if (whereFilters.fieldRangeEnd < value) { + return 'Start should be less than end range'; + } + if (value < whereFilters.fieldRangeStart) { + return 'End should be greater than start range'; + } +}; diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.test.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.test.js new file mode 100644 index 00000000..5a719674 --- /dev/null +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.test.js @@ -0,0 +1,249 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { + displayText, + isRangeOperator, + isNullOperator, + validateRange, + getOperators, +} from './whereHelpers'; +import { OPERATORS_MAP } from './constants'; + +describe('whereHelpers', () => { + describe('getOperators', () => { + test('should return all supported number operators', () => { + expect(getOperators('number')).toEqual([ + { + text: 'is', + value: 'is', + }, + { + text: 'is not', + value: 'is_not', + }, + { + text: 'is null', + value: 'is_null', + }, + { + text: 'is not null', + value: 'is_not_null', + }, + { + text: 'is greater than', + value: 'is_greater', + }, + { + text: 'is greater than equal', + value: 'is_greater_equal', + }, + { + text: 'is less than', + value: 'is_lesser', + }, + { + text: 'is less than equal', + value: 'is_lesser_equal', + }, + { + text: 'is in range', + value: 'in_range', + }, + { + text: 'is not in range', + value: 'not_in_range', + }, + ]); + }); + test('should return all supported text operators', () => { + expect(getOperators('text')).toEqual([ + { + text: 'is', + value: 'is', + }, + { + text: 'is not', + value: 'is_not', + }, + { + text: 'is null', + value: 'is_null', + }, + { + text: 'is not null', + value: 'is_not_null', + }, + { + text: 'starts with', + value: 'starts_with', + }, + { + text: 'ends with', + value: 'ends_with', + }, + { + text: 'contains', + value: 'contains', + }, + { + text: 'does not contains', + value: 'does_not_contains', + }, + ]); + }); + test('should return all supported keyword operators', () => { + expect(getOperators('keyword')).toEqual([ + { + text: 'is', + value: 'is', + }, + { + text: 'is not', + value: 'is_not', + }, + { + text: 'is null', + value: 'is_null', + }, + { + text: 'is not null', + value: 'is_not_null', + }, + { + text: 'starts with', + value: 'starts_with', + }, + { + text: 'ends with', + value: 'ends_with', + }, + { + text: 'contains', + value: 'contains', + }, + ]); + }); + test('should return all supported boolean operators', () => { + expect(getOperators('boolean')).toEqual([ + { + text: 'is', + value: 'is', + }, + { + text: 'is not', + value: 'is_not', + }, + { + text: 'is null', + value: 'is_null', + }, + ]); + }); + }); + describe('isRangeOperator', () => { + test('should return true for IN_RANGE operator', () => { + expect(isRangeOperator(OPERATORS_MAP.IN_RANGE)).toBe(true); + }); + test('should return true for NOT_IN_RANGE operator', () => { + expect(isRangeOperator(OPERATORS_MAP.NOT_IN_RANGE)).toBe(true); + }); + test('should return false for any other operators', () => { + expect(isRangeOperator(OPERATORS_MAP.IS)).toBe(false); + expect(isRangeOperator(OPERATORS_MAP.IS_GREATER_EQUAL)).toBe(false); + }); + }); + + describe('isNullOperator', () => { + test('should return true for IS_NULL operator', () => { + expect(isNullOperator(OPERATORS_MAP.IS_NULL)).toBe(true); + }); + test('should return true for IS_NOT_NULL operator', () => { + expect(isNullOperator(OPERATORS_MAP.IS_NOT_NULL)).toBe(true); + }); + test('should return false for any other operators', () => { + expect(isNullOperator(OPERATORS_MAP.IS)).toBe(false); + expect(isNullOperator(OPERATORS_MAP.IS_GREATER_EQUAL)).toBe(false); + }); + }); + + describe('validateRange', () => { + test('should return validation error invalid StartRange', () => { + expect(validateRange(100, { fieldRangeStart: 100, fieldRangeEnd: 50 })).toBe( + 'Start should be less than end range' + ); + }); + test('should return validation error invalid endRange', () => { + expect(validateRange(200, { fieldRangeStart: 300, fieldRangeEnd: 200 })).toBe( + 'End should be greater than start range' + ); + }); + test('should return Required for undefined/null values', () => { + expect(validateRange('', { fieldRangeStart: '', fieldRangeEnd: 200 })).toBe('Required'); + }); + + test('should return undefined for valid range', () => { + expect(validateRange(100, { fieldRangeStart: 100, fieldRangeEnd: 200 })).toBe(undefined); + }); + }); + + describe('displayText', () => { + test('should return between and text for range operator', () => { + expect( + displayText({ + fieldName: [{ label: 'age', type: 'number' }], + operator: OPERATORS_MAP.IN_RANGE, + fieldRangeStart: 20, + fieldRangeEnd: 40, + }) + ).toBe('age is in range from 20 to 40'); + }); + test('should return between and text for not in range operator', () => { + expect( + displayText({ + fieldName: [{ label: 'age', type: 'number' }], + operator: OPERATORS_MAP.NOT_IN_RANGE, + fieldRangeStart: 20, + fieldRangeEnd: 40, + }) + ).toBe('age is not in range from 20 to 40'); + }); + test('should return text for null operators', () => { + expect( + displayText({ + fieldName: [{ label: 'age', type: 'number' }], + operator: OPERATORS_MAP.IS_NULL, + }) + ).toBe('age is null'); + }); + test('should return text for not null operators', () => { + expect( + displayText({ + fieldName: [{ label: 'age', type: 'number' }], + operator: OPERATORS_MAP.IS_NOT_NULL, + }) + ).toBe('age is not null'); + }); + test('should return text based on operator', () => { + expect( + displayText({ + fieldName: [{ label: 'age', type: 'number' }], + operator: OPERATORS_MAP.IS_GREATER, + fieldValue: 20, + }) + ).toBe('age is greater than 20'); + }); + }); +}); diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/CreateMonitor.js b/public/pages/CreateMonitor/containers/CreateMonitor/CreateMonitor.js index 44a0c684..bb8c9755 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/CreateMonitor.js +++ b/public/pages/CreateMonitor/containers/CreateMonitor/CreateMonitor.js @@ -126,7 +126,7 @@ export default class CreateMonitor extends Component { initialValues={initialValues} onSubmit={this.onSubmit} validateOnChange={false} - render={({ values, handleSubmit, isSubmitting }) => ( + render={({ values, errors, handleSubmit, isSubmitting }) => (

{edit ? 'Edit' : 'Create'} Monitor

@@ -134,7 +134,7 @@ export default class CreateMonitor extends Component { - + diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/__snapshots__/CreateMonitor.test.js.snap b/public/pages/CreateMonitor/containers/CreateMonitor/__snapshots__/CreateMonitor.test.js.snap index 4a0caec4..771a7927 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/__snapshots__/CreateMonitor.test.js.snap +++ b/public/pages/CreateMonitor/containers/CreateMonitor/__snapshots__/CreateMonitor.test.js.snap @@ -51,6 +51,13 @@ exports[`CreateMonitor renders 1`] = ` "tue": false, "wed": false, }, + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, } } isInitialValid={false} diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/__snapshots__/formikToMonitor.test.js.snap b/public/pages/CreateMonitor/containers/CreateMonitor/utils/__snapshots__/formikToMonitor.test.js.snap index 6adab079..c4afc769 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/utils/__snapshots__/formikToMonitor.test.js.snap +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/__snapshots__/formikToMonitor.test.js.snap @@ -59,15 +59,17 @@ Object { "aggregations": Object {}, "query": Object { "bool": Object { - "filter": Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": "{{period_end}}||-1h", - "lte": "{{period_end}}", + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": "{{period_end}}||-1h", + "lte": "{{period_end}}", + }, }, }, - }, + ], }, }, "size": 0, @@ -95,15 +97,17 @@ Object { "aggregations": Object {}, "query": Object { "bool": Object { - "filter": Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": "{{period_end}}||-1h", - "lte": "{{period_end}}", + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": "{{period_end}}||-1h", + "lte": "{{period_end}}", + }, }, }, - }, + ], }, }, "size": 0, @@ -129,14 +133,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Object { - "range": Object { - "@timestamp": Object { - "gte": "now-5h", - "lte": "now", + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-5h", + "lte": "now", + }, }, }, - }, + ], }, }, "size": 0, @@ -198,6 +204,62 @@ Object { "overDocuments": "all documents", "searchType": "graph", "timeField": "@timestamp", + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, +} +`; + +exports[`formikToUiSearch can build ui search with range where field 1`] = ` +Object { + "aggregationType": "count", + "bucketUnitOfTime": "h", + "bucketValue": 1, + "fieldName": "bytes", + "groupedOverFieldName": "bytes", + "groupedOverTop": 5, + "overDocuments": "all documents", + "searchType": "graph", + "timeField": "@timestamp", + "where": Object { + "fieldName": Array [ + Object { + "label": "age", + "type": "number", + }, + ], + "fieldRangeEnd": 40, + "fieldRangeStart": 20, + "operator": "in_range", + }, +} +`; + +exports[`formikToUiSearch can build ui search with term where field 1`] = ` +Object { + "aggregationType": "count", + "bucketUnitOfTime": "h", + "bucketValue": 1, + "fieldName": "bytes", + "groupedOverFieldName": "bytes", + "groupedOverTop": 5, + "overDocuments": "all documents", + "searchType": "graph", + "timeField": "@timestamp", + "where": Object { + "fieldName": Array [ + Object { + "label": "age", + "type": "number", + }, + ], + "fieldValue": 20, + "operator": "is_greater_equal", + }, } `; diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/constants.js b/public/pages/CreateMonitor/containers/CreateMonitor/utils/constants.js index d6fe26dc..65d5bbe4 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/utils/constants.js +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/constants.js @@ -1,3 +1,5 @@ +import { OPERATORS_MAP } from '../../../components/MonitorExpressions/expressions/utils/constants'; + /* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * @@ -41,4 +43,11 @@ export const FORMIK_INITIAL_VALUES = { groupedOverFieldName: 'bytes', bucketValue: 1, bucketUnitOfTime: 'h', // m = minute, h = hour, d = day + where: { + fieldName: [], + operator: OPERATORS_MAP.IS, + fieldValue: 0, + fieldRangeStart: 0, + fieldRangeEnd: 0, + }, }; diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.js b/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.js index a09b640e..9ea6f1d7 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.js +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.js @@ -17,6 +17,7 @@ import _ from 'lodash'; import moment from 'moment-timezone'; import { BUCKET_COUNT } from './constants'; import { SEARCH_TYPE } from '../../../../../utils/constants'; +import { OPERATORS_QUERY_MAP } from './whereFilters'; export function formikToMonitor(values) { const query = formikToQuery(values); @@ -56,6 +57,7 @@ export function formikToUiSearch(values) { groupedOverFieldName, bucketValue, bucketUnitOfTime, + where, } = values; return { searchType, @@ -67,6 +69,7 @@ export function formikToUiSearch(values) { groupedOverFieldName, bucketValue, bucketUnitOfTime, + where, }; } @@ -87,20 +90,27 @@ export function formikToGraphQuery(values) { const { bucketValue, bucketUnitOfTime } = values; const whenAggregation = formikToWhenAggregation(values); const timeField = values.timeField; + const filters = [ + { + range: { + [timeField]: { + gte: `{{period_end}}||-${Math.round(bucketValue)}${bucketUnitOfTime}`, + lte: '{{period_end}}', + format: 'epoch_millis', + }, + }, + }, + ]; + const whereClause = formikToWhereClause(values); + if (whereClause) { + filters.push({ ...whereClause }); + } return { size: 0, aggregations: whenAggregation, query: { bool: { - filter: { - range: { - [timeField]: { - gte: `{{period_end}}||-${Math.round(bucketValue)}${bucketUnitOfTime}`, - lte: '{{period_end}}', - format: 'epoch_millis', - }, - }, - }, + filter: filters, }, }, }; @@ -110,20 +120,27 @@ export function formikToUiGraphQuery(values) { const { bucketValue, bucketUnitOfTime } = values; const overAggregation = formikToUiOverAggregation(values); const timeField = values.timeField; + const filters = [ + { + range: { + [timeField]: { + // default range window to [BUCKET_COUNT] * the date histogram interval + gte: `now-${bucketValue * BUCKET_COUNT}${bucketUnitOfTime}`, + lte: 'now', + }, + }, + }, + ]; + const whereClause = formikToWhereClause(values); + if (whereClause) { + filters.push({ ...whereClause }); + } return { size: 0, aggregations: overAggregation, query: { bool: { - filter: { - range: { - [timeField]: { - // default range window to [BUCKET_COUNT] * the date histogram interval - gte: `now-${bucketValue * BUCKET_COUNT}${bucketUnitOfTime}`, - lte: 'now', - }, - }, - }, + filter: filters, }, }, }; @@ -151,6 +168,12 @@ export function formikToUiOverAggregation(values) { }; } +export function formikToWhereClause({ where }) { + if (where.fieldName.length > 0) { + return OPERATORS_QUERY_MAP[where.operator].query(where); + } +} + export function formikToWhenAggregation(values) { const { aggregationType, diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.test.js b/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.test.js index b5a8b4e0..6d6a406d 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.test.js +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.test.js @@ -26,9 +26,11 @@ import { formikToWhenAggregation, formikToUiSchedule, buildSchedule, + formikToWhereClause, } from './formikToMonitor'; import { FORMIK_INITIAL_VALUES } from './constants'; +import { OPERATORS_MAP } from '../../../components/MonitorExpressions/expressions/utils/constants'; jest.mock('moment-timezone', () => { const moment = require.requireActual('moment-timezone'); @@ -55,6 +57,24 @@ describe('formikToUiSearch', () => { test('can build ui search', () => { expect(formikToUiSearch(formikValues)).toMatchSnapshot(); }); + test('can build ui search with term where field', () => { + formikValues.where = { + fieldName: [{ label: 'age', type: 'number' }], + operator: OPERATORS_MAP.IS_GREATER_EQUAL, + fieldValue: 20, + }; + expect(formikToUiSearch(formikValues)).toMatchSnapshot(); + }); + + test('can build ui search with range where field', () => { + formikValues.where = { + fieldName: [{ label: 'age', type: 'number' }], + operator: OPERATORS_MAP.IN_RANGE, + fieldRangeStart: 20, + fieldRangeEnd: 40, + }; + expect(formikToUiSearch(formikValues)).toMatchSnapshot(); + }); }); describe('formikToIndices', () => { @@ -162,3 +182,49 @@ describe('buildSchedule', () => { expect(buildSchedule('cronExpression', uiSchedule)).toMatchSnapshot(); }); }); + +describe('formikToWhereClause', () => { + const numericFieldName = [{ label: 'age', type: 'number' }]; + const textField = [{ label: 'city', type: 'text' }]; + const keywordField = [{ label: 'city.keyword', type: 'keyword' }]; + + test.each([ + [numericFieldName, OPERATORS_MAP.IS, 20, { term: { age: 20 } }], + [textField, OPERATORS_MAP.IS, 'Seattle', { match_phrase: { city: 'Seattle' } }], + [numericFieldName, OPERATORS_MAP.IS_NOT, 20, { bool: { must_not: { term: { age: 20 } } } }], + [ + textField, + OPERATORS_MAP.IS_NOT, + 'Seattle', + { bool: { must_not: { match_phrase: { city: 'Seattle' } } } }, + ], + [ + numericFieldName, + OPERATORS_MAP.IS_NULL, + undefined, + { bool: { must_not: { exists: { field: 'age' } } } }, + ], + [numericFieldName, OPERATORS_MAP.IS_NOT_NULL, undefined, { exists: { field: 'age' } }], + [numericFieldName, OPERATORS_MAP.IS_GREATER, 20, { range: { age: { gt: 20 } } }], + [numericFieldName, OPERATORS_MAP.IS_GREATER_EQUAL, 20, { range: { age: { gte: 20 } } }], + [numericFieldName, OPERATORS_MAP.IS_LESS, 20, { range: { age: { lt: 20 } } }], + [numericFieldName, OPERATORS_MAP.IS_LESS_EQUAL, 20, { range: { age: { lte: 20 } } }], + [textField, OPERATORS_MAP.STARTS_WITH, 'Se', { prefix: { city: 'Se' } }], + [textField, OPERATORS_MAP.ENDS_WITH, 'Se', { wildcard: { city: '*Se' } }], + [ + textField, + OPERATORS_MAP.CONTAINS, + 'Se', + { query_string: { query: `*Se*`, default_field: 'city' } }, + ], + [keywordField, OPERATORS_MAP.CONTAINS, 'Se', { wildcard: { 'city.keyword': '*Se*' } }], + [ + textField, + OPERATORS_MAP.NOT_CONTAINS, + 'Se', + { bool: { must_not: { query_string: { query: `*Se*`, default_field: 'city' } } } }, + ], + ])('.formikToWhereClause (%j, %S)', (fieldName, operator, fieldValue, expected) => { + expect(formikToWhereClause({ where: { fieldName, operator, fieldValue } })).toEqual(expected); + }); +}); diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/whereFilters.js b/public/pages/CreateMonitor/containers/CreateMonitor/utils/whereFilters.js new file mode 100644 index 00000000..2abe81b8 --- /dev/null +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/whereFilters.js @@ -0,0 +1,110 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { OPERATORS_MAP } from '../../../components/MonitorExpressions/expressions/utils/constants'; +import { DATA_TYPES } from '../../../../../utils/constants'; + +//TODO:: Breakdown to factory pattern for rules in-case we support multiple filters. This is just ease for the single one +export const OPERATORS_QUERY_MAP = { + [OPERATORS_MAP.IS]: { + query: ({ fieldName: [{ label, type }], fieldValue }) => + type === DATA_TYPES.TEXT + ? { match_phrase: { [label]: fieldValue } } + : { term: { [label]: fieldValue } }, + }, + [OPERATORS_MAP.IS_NOT]: { + query: ({ fieldName: [{ label, type }], fieldValue }) => + type === DATA_TYPES.TEXT + ? { + bool: { must_not: { match_phrase: { [label]: fieldValue } } }, + } + : { + bool: { must_not: { term: { [label]: fieldValue } } }, + }, + }, + [OPERATORS_MAP.IS_NULL]: { + query: ({ fieldName: [{ label: fieldKey }] }) => ({ + bool: { must_not: { exists: { field: fieldKey } } }, + }), + }, + [OPERATORS_MAP.IS_NOT_NULL]: { + query: ({ fieldName: [{ label: fieldKey }] }) => ({ exists: { field: fieldKey } }), + }, + [OPERATORS_MAP.IS_GREATER]: { + query: ({ fieldName: [{ label: fieldKey }], fieldValue }) => ({ + range: { [fieldKey]: { gt: fieldValue } }, + }), + }, + + [OPERATORS_MAP.IS_GREATER_EQUAL]: { + query: ({ fieldName: [{ label: fieldKey }], fieldValue }) => ({ + range: { [fieldKey]: { gte: fieldValue } }, + }), + }, + [OPERATORS_MAP.IS_LESS]: { + query: ({ fieldName: [{ label: fieldKey }], fieldValue }) => ({ + range: { [fieldKey]: { lt: fieldValue } }, + }), + }, + + [OPERATORS_MAP.IS_LESS_EQUAL]: { + query: ({ fieldName: [{ label: fieldKey }], fieldValue }) => ({ + range: { [fieldKey]: { lte: fieldValue } }, + }), + }, + + [OPERATORS_MAP.IN_RANGE]: { + query: ({ fieldName: [{ label: fieldKey }], fieldRangeStart, fieldRangeEnd }) => ({ + range: { [fieldKey]: { gte: fieldRangeStart, lte: fieldRangeEnd } }, + }), + }, + [OPERATORS_MAP.NOT_IN_RANGE]: { + query: ({ fieldName: [{ label: fieldKey }], fieldRangeStart, fieldRangeEnd }) => ({ + bool: { must_not: { range: { [fieldKey]: { gte: fieldRangeStart, lte: fieldRangeEnd } } } }, + }), + }, + + [OPERATORS_MAP.STARTS_WITH]: { + query: ({ fieldName, fieldValue }) => ({ prefix: { [fieldName[0].label]: fieldValue } }), + }, + + [OPERATORS_MAP.ENDS_WITH]: { + query: ({ fieldName, fieldValue }) => ({ + wildcard: { [fieldName[0].label]: `*${fieldValue}` }, + }), + }, + [OPERATORS_MAP.CONTAINS]: { + query: ({ fieldName: [{ label, type }], fieldValue }) => + type === DATA_TYPES.TEXT + ? { + query_string: { query: `*${fieldValue}*`, default_field: label }, + } + : { + wildcard: { [label]: `*${fieldValue}*` }, + }, + }, + [OPERATORS_MAP.NOT_CONTAINS]: { + query: ({ fieldName: [{ label, type }], fieldValue }) => + type === DATA_TYPES.TEXT + ? { + bool: { + must_not: { query_string: { query: `*${fieldValue}*`, default_field: label } }, + }, + } + : { + bool: { must_not: { wildcard: { [label]: `*${fieldValue}*` } } }, + }, + }, +}; diff --git a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js index 4331fc37..e6e74786 100644 --- a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js +++ b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js @@ -15,6 +15,7 @@ import React, { Component, Fragment } from 'react'; import _ from 'lodash'; +import PropTypes from 'prop-types'; import { EuiSpacer, EuiButton, EuiText } from '@elastic/eui'; import ContentPanel from '../../../../components/ContentPanel'; import VisualGraph from '../../components/VisualGraph'; @@ -41,7 +42,16 @@ function renderEmptyMessage(message) { ); } -export default class DefineMonitor extends Component { +const propTypes = { + values: PropTypes.object.isRequired, + httpClient: PropTypes.object.isRequired, + errors: PropTypes.object, +}; +const defaultProps = { + errors: {}, +}; + +class DefineMonitor extends Component { constructor(props) { super(props); @@ -103,6 +113,7 @@ export default class DefineMonitor extends Component { } renderGraph() { + const { errors } = this.props; return ( @@ -115,11 +126,15 @@ export default class DefineMonitor extends Component { ofEnabled={this.props.values.aggregationType !== 'count'} /> - + {errors.where ? ( + renderEmptyMessage('Invalid input in WHERE filter. Remove WHERE filter or adjust filter ') + ) : ( + + )} ); } @@ -247,3 +262,8 @@ export default class DefineMonitor extends Component { ); } } + +DefineMonitor.propTypes = propTypes; +DefineMonitor.defaultProps = defaultProps; + +export default DefineMonitor; diff --git a/public/pages/CreateMonitor/containers/MonitorIndex/__snapshots__/MonitorIndex.test.js.snap b/public/pages/CreateMonitor/containers/MonitorIndex/__snapshots__/MonitorIndex.test.js.snap index 59dc74e3..5e74b2ec 100644 --- a/public/pages/CreateMonitor/containers/MonitorIndex/__snapshots__/MonitorIndex.test.js.snap +++ b/public/pages/CreateMonitor/containers/MonitorIndex/__snapshots__/MonitorIndex.test.js.snap @@ -44,6 +44,13 @@ exports[`MonitorIndex renders 1`] = ` "tue": false, "wed": false, }, + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, } } isInitialValid={false} @@ -161,6 +168,13 @@ exports[`MonitorIndex renders 1`] = ` "tue": false, "wed": false, }, + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, }, "isSubmitting": false, "isValid": false, @@ -227,6 +241,13 @@ exports[`MonitorIndex renders 1`] = ` "tue": false, "wed": false, }, + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, }, } } @@ -283,6 +304,13 @@ exports[`MonitorIndex renders 1`] = ` "tue": false, "wed": false, }, + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, }, "isSubmitting": false, "isValid": false, @@ -347,6 +375,13 @@ exports[`MonitorIndex renders 1`] = ` "tue": false, "wed": false, }, + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, }, } } @@ -458,6 +493,13 @@ exports[`MonitorIndex renders 1`] = ` "tue": false, "wed": false, }, + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, }, "isSubmitting": false, "isValid": false, @@ -522,6 +564,13 @@ exports[`MonitorIndex renders 1`] = ` "tue": false, "wed": false, }, + "where": Object { + "fieldName": Array [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": 0, + "operator": "is", + }, }, } } diff --git a/public/pages/Monitors/containers/Monitors/Monitors.test.js b/public/pages/Monitors/containers/Monitors/Monitors.test.js index 3e3cf663..30f34d92 100644 --- a/public/pages/Monitors/containers/Monitors/Monitors.test.js +++ b/public/pages/Monitors/containers/Monitors/Monitors.test.js @@ -89,12 +89,10 @@ describe('Monitors', () => { expect(mountWrapper.instance().state.size).not.toBe(17); expect(mountWrapper.instance().state.sortField).not.toBe('testing_sort_field'); expect(mountWrapper.instance().state.sortDirection).not.toBe('asc'); - mountWrapper - .instance() - .onTableChange({ - page: { index: 17, size: 17 }, - sort: { field: 'testing_sort_field', direction: 'desc' }, - }); + mountWrapper.instance().onTableChange({ + page: { index: 17, size: 17 }, + sort: { field: 'testing_sort_field', direction: 'desc' }, + }); mountWrapper.update(); expect(onTableChange).toHaveBeenCalled(); diff --git a/public/utils/constants.js b/public/utils/constants.js index 64fe516d..eeb3c4fd 100644 --- a/public/utils/constants.js +++ b/public/utils/constants.js @@ -44,3 +44,10 @@ export const TRIGGER_ACTIONS = { UPDATE_TRIGGER: 'update-trigger', CREATE_TRIGGER: 'create-trigger', }; + +export const DATA_TYPES = { + NUMBER: 'number', + TEXT: 'text', + BOOLEAN: 'boolean', + KEYWORD: 'keyword', +}; From 06ed5aa96db6d557b8a4f6e5c026db406012ce39 Mon Sep 17 00:00:00 2001 From: Mihir Soni Date: Tue, 7 May 2019 10:29:07 -0700 Subject: [PATCH 2/3] Fixes destructuring , proptypes and import --- .../MonitorExpressions/expressions/WhereExpression.js | 4 ++-- .../containers/CreateMonitor/utils/whereFilters.js | 8 +++++--- .../containers/DefineMonitor/DefineMonitor.js | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.js index 1e669219..26e43737 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/WhereExpression.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import React, { PureComponent, Fragment } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'formik'; import { EuiFlexGroup, EuiFlexItem, EuiPopover, EuiExpression, EuiText } from '@elastic/eui'; @@ -50,7 +50,7 @@ const propTypes = { openExpression: PropTypes.func.isRequired, }; -class WhereExpression extends PureComponent { +class WhereExpression extends Component { constructor(props) { super(props); } diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/whereFilters.js b/public/pages/CreateMonitor/containers/CreateMonitor/utils/whereFilters.js index 2abe81b8..b741aa79 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/utils/whereFilters.js +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/whereFilters.js @@ -77,12 +77,14 @@ export const OPERATORS_QUERY_MAP = { }, [OPERATORS_MAP.STARTS_WITH]: { - query: ({ fieldName, fieldValue }) => ({ prefix: { [fieldName[0].label]: fieldValue } }), + query: ({ fieldName: [{ label: fieldKey }], fieldValue }) => ({ + prefix: { [fieldKey]: fieldValue }, + }), }, [OPERATORS_MAP.ENDS_WITH]: { - query: ({ fieldName, fieldValue }) => ({ - wildcard: { [fieldName[0].label]: `*${fieldValue}` }, + query: ({ fieldName: [{ label: fieldKey }], fieldValue }) => ({ + wildcard: { [fieldKey]: `*${fieldValue}` }, }), }, [OPERATORS_MAP.CONTAINS]: { diff --git a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js index e6e74786..fee65370 100644 --- a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js +++ b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js @@ -44,7 +44,7 @@ function renderEmptyMessage(message) { const propTypes = { values: PropTypes.object.isRequired, - httpClient: PropTypes.object.isRequired, + httpClient: PropTypes.func.isRequired, errors: PropTypes.object, }; const defaultProps = { From d3c599352487c1f38b838a629d33d15ffed0db41 Mon Sep 17 00:00:00 2001 From: Mihir Soni Date: Tue, 7 May 2019 11:52:55 -0700 Subject: [PATCH 3/3] Fixes values to make it consistent --- .../MonitorExpressions/expressions/utils/constants.js | 5 ++--- .../expressions/utils/whereHelpers.test.js | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants.js index 758dcf56..e065bc6c 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants.js @@ -52,9 +52,8 @@ export const OPERATORS_MAP = { IS_NOT_NULL: 'is_not_null', IS_GREATER: 'is_greater', IS_GREATER_EQUAL: 'is_greater_equal', - IS_LESS: 'is_lesser', - IS_LESS_EQUAL: 'is_lesser_equal', - IS_LESS_EQUAL: 'is_lesser_equal', + IS_LESS: 'is_less', + IS_LESS_EQUAL: 'is_less_equal', STARTS_WITH: 'starts_with', ENDS_WITH: 'ends_with', CONTAINS: 'contains', diff --git a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.test.js b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.test.js index 5a719674..5c9036d7 100644 --- a/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.test.js +++ b/public/pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers.test.js @@ -52,11 +52,11 @@ describe('whereHelpers', () => { }, { text: 'is less than', - value: 'is_lesser', + value: 'is_less', }, { text: 'is less than equal', - value: 'is_lesser_equal', + value: 'is_less_equal', }, { text: 'is in range',