diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg.test.tsx.snap new file mode 100644 index 00000000000000..5790e0d4e872f5 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg.test.tsx.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DefaultEditorAgg component should init with the default set of props 1`] = ` + + Schema name + + + } + buttonContentClassName="visEditorSidebar__aggGroupAccordionButtonContent eui-textTruncate" + className="visEditorSidebar__section visEditorSidebar__collapsible visEditorSidebar__collapsible--marginBottom" + data-test-subj="visEditorAggAccordion1" + extraAction={ +
+ + + +
+ } + id="visEditorAggAccordion1" + initialIsOpen={true} + onToggle={[Function]} + paddingSize="none" +> + + +
+`; diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg_group.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg_group.test.tsx.snap new file mode 100644 index 00000000000000..813b7978d26671 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg_group.test.tsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DefaultEditorAgg component should init with the default set of props 1`] = ` + + + +
+ Metrics +
+
+ + + + + + + + + +
+
+`; diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.test.tsx new file mode 100644 index 00000000000000..ce12b18cf777af --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.test.tsx @@ -0,0 +1,279 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 { mount, shallow } from 'enzyme'; +import { VisState } from 'ui/vis'; +import { AggGroupNames } from '../agg_groups'; +import { DefaultEditorAgg, DefaultEditorAggProps } from './default_editor_agg'; +import { act } from 'react-dom/test-utils'; +import { DefaultEditorAggParams } from './default_editor_agg_params'; + +jest.mock('./default_editor_agg_params', () => ({ + DefaultEditorAggParams: () => null, +})); + +describe('DefaultEditorAgg component', () => { + let defaultProps: DefaultEditorAggProps; + let onAggParamsChange: jest.Mock; + let setTouched: jest.Mock; + let onToggleEnableAgg: jest.Mock; + let removeAgg: jest.Mock; + let setValidity: jest.Mock; + + beforeEach(() => { + onAggParamsChange = jest.fn(); + setTouched = jest.fn(); + onToggleEnableAgg = jest.fn(); + removeAgg = jest.fn(); + setValidity = jest.fn(); + + defaultProps = { + agg: { + id: 1, + brandNew: true, + getIndexPattern: () => ({}), + schema: { title: 'Schema name' }, + title: 'Metrics', + params: {}, + }, + aggIndex: 0, + aggIsTooLow: false, + dragHandleProps: null, + formIsTouched: false, + groupName: AggGroupNames.Metrics, + isDraggable: false, + isLastBucket: false, + isRemovable: false, + metricAggs: [], + state: {} as VisState, + onAggParamsChange, + onAggTypeChange: () => {}, + setValidity, + setTouched, + onToggleEnableAgg, + removeAgg, + }; + }); + + it('should init with the default set of props', () => { + const comp = shallow(); + + expect(comp).toMatchSnapshot(); + }); + + it('should open accordion initially', () => { + const comp = shallow(); + + expect(comp.props()).toHaveProperty('initialIsOpen', true); + }); + + it('should not show description when agg is invalid', () => { + defaultProps.agg.brandNew = false; + const comp = mount(); + + act(() => { + comp + .find(DefaultEditorAggParams) + .props() + .setValidity(false); + }); + comp.update(); + expect(setValidity).toBeCalledWith(false); + + expect( + comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').exists() + ).toBeFalsy(); + }); + + it('should show description when agg is valid', () => { + defaultProps.agg.brandNew = false; + defaultProps.agg.type = { + makeLabel: () => 'Agg description', + }; + const comp = mount(); + + act(() => { + comp + .find(DefaultEditorAggParams) + .props() + .setValidity(true); + }); + comp.update(); + expect(setValidity).toBeCalledWith(true); + + expect(comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').text()).toBe( + 'Agg description' + ); + }); + + it('should call setTouched when accordion is collapsed', () => { + const comp = mount(); + expect(defaultProps.setTouched).toBeCalledTimes(0); + + comp.find('.euiAccordion__button').simulate('click'); + // make sure that the accordion is collapsed + expect(comp.find('.euiAccordion-isOpen').exists()).toBeFalsy(); + + expect(defaultProps.setTouched).toBeCalledWith(true); + }); + + it('should call setValidity inside onSetValidity', () => { + const comp = mount(); + + act(() => { + comp + .find(DefaultEditorAggParams) + .props() + .setValidity(false); + }); + + expect(setValidity).toBeCalledWith(false); + + expect( + comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').exists() + ).toBeFalsy(); + }); + + it('should add schema component', () => { + defaultProps.agg.schema = { + editorComponent: () =>
, + }; + const comp = mount(); + + expect(comp.find('.schemaComponent').exists()).toBeTruthy(); + }); + + describe('agg actions', () => { + beforeEach(() => { + defaultProps.agg.enabled = true; + }); + + it('should not have actions', () => { + const comp = shallow(); + const actions = shallow(comp.prop('extraAction')); + + expect(actions.children().exists()).toBeFalsy(); + }); + + it('should have disable and remove actions', () => { + defaultProps.isRemovable = true; + const comp = mount(); + + expect( + comp.find('[data-test-subj="toggleDisableAggregationBtn disable"] button').exists() + ).toBeTruthy(); + expect(comp.find('[data-test-subj="removeDimensionBtn"] button').exists()).toBeTruthy(); + }); + + it('should have draggable action', () => { + defaultProps.isDraggable = true; + const comp = mount(); + + expect(comp.find('[data-test-subj="dragHandleBtn"]').exists()).toBeTruthy(); + }); + + it('should disable agg', () => { + defaultProps.isRemovable = true; + const comp = mount(); + comp.find('[data-test-subj="toggleDisableAggregationBtn disable"] button').simulate('click'); + + expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg, false); + }); + + it('should enable agg', () => { + defaultProps.agg.enabled = false; + const comp = mount(); + comp.find('[data-test-subj="toggleDisableAggregationBtn enable"] button').simulate('click'); + + expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg, true); + }); + + it('should call removeAgg', () => { + defaultProps.isRemovable = true; + const comp = mount(); + comp.find('[data-test-subj="removeDimensionBtn"] button').simulate('click'); + + expect(defaultProps.removeAgg).toBeCalledWith(defaultProps.agg); + }); + }); + + describe('last bucket', () => { + beforeEach(() => { + defaultProps.isLastBucket = true; + defaultProps.lastParentPipelineAggTitle = 'ParentPipelineAgg'; + }); + + it('should disable min_doc_count when agg is histogram or date_histogram', () => { + defaultProps.agg.type = { + name: 'histogram', + }; + const compHistogram = shallow(); + defaultProps.agg.type = { + name: 'date_histogram', + }; + const compDateHistogram = shallow(); + + expect(compHistogram.find(DefaultEditorAggParams).props()).toHaveProperty('disabledParams', [ + 'min_doc_count', + ]); + expect(compDateHistogram.find(DefaultEditorAggParams).props()).toHaveProperty( + 'disabledParams', + ['min_doc_count'] + ); + }); + + it('should set error when agg is not histogram or date_histogram', () => { + defaultProps.agg.type = { + name: 'aggType', + }; + const comp = shallow(); + + expect(comp.find(DefaultEditorAggParams).prop('aggError')).toBeDefined(); + }); + + it('should set min_doc_count to true when agg type was changed to histogram', () => { + defaultProps.agg.type = { + name: 'aggType', + }; + const comp = mount(); + comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'histogram' } } }); + + expect(defaultProps.onAggParamsChange).toHaveBeenCalledWith( + defaultProps.agg.params, + 'min_doc_count', + true + ); + }); + + it('should set min_doc_count to 0 when agg type was changed to date_histogram', () => { + defaultProps.agg.type = { + name: 'aggType', + }; + const comp = mount(); + comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'date_histogram' } } }); + + expect(defaultProps.onAggParamsChange).toHaveBeenCalledWith( + defaultProps.agg.params, + 'min_doc_count', + 0 + ); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.tsx index 7d6bc34ac06c9b..40fb8a7b63a297 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.tsx @@ -32,7 +32,7 @@ import { AggConfig } from '../../../'; import { DefaultEditorAggParams } from './default_editor_agg_params'; import { DefaultEditorAggCommonProps } from './default_editor_agg_common_props'; -interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { +export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { agg: AggConfig; aggIndex: number; aggIsTooLow: boolean; @@ -137,7 +137,7 @@ function DefaultEditorAgg({ tooltip: i18n.translate('common.ui.vis.editors.agg.disableAggButtonTooltip', { defaultMessage: 'Disable aggregation', }), - dataTestSubj: 'toggleDisableAggregationBtn', + dataTestSubj: 'toggleDisableAggregationBtn disable', }); } if (!agg.enabled) { @@ -149,7 +149,7 @@ function DefaultEditorAgg({ tooltip: i18n.translate('common.ui.vis.editors.agg.enableAggButtonTooltip', { defaultMessage: 'Enable aggregation', }), - dataTestSubj: 'toggleDisableAggregationBtn', + dataTestSubj: 'toggleDisableAggregationBtn enable', }); } if (isDraggable) { diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.test.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.test.tsx new file mode 100644 index 00000000000000..17be226e32db61 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.test.tsx @@ -0,0 +1,221 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 { mount, shallow } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { VisState } from '../../../'; +import { Schema } from '../schemas'; +import { AggGroupNames } from '../agg_groups'; +import { AggConfig } from '../../../agg_config'; +import { AggConfigs } from '../../../agg_configs'; +import { DefaultEditorAggGroup, DefaultEditorAggGroupProps } from './default_editor_agg_group'; +import { DefaultEditorAgg } from './default_editor_agg'; +import { DefaultEditorAggAdd } from './default_editor_agg_add'; + +jest.mock('@elastic/eui', () => ({ + EuiTitle: 'eui-title', + EuiDragDropContext: 'eui-drag-drop-context', + EuiDroppable: 'eui-droppable', + EuiDraggable: (props: any) => props.children({ dragHandleProps: {} }), + EuiSpacer: 'eui-spacer', + EuiPanel: 'eui-panel', +})); + +jest.mock('./default_editor_agg', () => ({ + DefaultEditorAgg: () =>
, +})); + +jest.mock('./default_editor_agg_add', () => ({ + DefaultEditorAggAdd: () =>
, +})); + +describe('DefaultEditorAgg component', () => { + let defaultProps: DefaultEditorAggGroupProps; + let aggs: AggConfigs; + let setTouched: jest.Mock; + let setValidity: jest.Mock; + let reorderAggs: jest.Mock; + + beforeEach(() => { + setTouched = jest.fn(); + setValidity = jest.fn(); + reorderAggs = jest.fn(); + + aggs = [ + { + id: 1, + title: 'Metrics', + params: { + field: { + type: 'number', + }, + }, + group: 'metrics', + schema: {}, + }, + { + id: 3, + title: 'Agg', + params: { + field: { + type: 'string', + }, + }, + group: 'metrics', + schema: {}, + }, + { + id: 2, + title: 'Buckets', + params: { + field: { + type: 'number', + }, + }, + group: 'buckets', + schema: {}, + }, + ] as AggConfigs; + + Object.defineProperty(aggs, 'bySchemaGroup', { + get: () => + aggs.reduce((acc: { [key: string]: AggConfig }, option: AggConfig) => { + if (acc[option.group]) { + acc[option.group].push(option); + } else { + acc[option.group] = [option]; + } + + return acc; + }, {}), + }); + + defaultProps = { + formIsTouched: false, + metricAggs: [], + groupName: AggGroupNames.Metrics, + state: { + aggs, + } as VisState, + schemas: [ + { + max: 1, + } as Schema, + { + max: 1, + } as Schema, + ], + setTouched, + setValidity, + reorderAggs, + addSchema: () => {}, + removeAgg: () => {}, + onAggParamsChange: () => {}, + onAggTypeChange: () => {}, + onToggleEnableAgg: () => {}, + }; + }); + + it('should init with the default set of props', () => { + const comp = shallow(); + + expect(comp).toMatchSnapshot(); + }); + + it('should call setTouched with false', () => { + mount(); + + expect(setTouched).toBeCalledWith(false); + }); + + it('should mark group as touched when all invalid aggs are touched', () => { + defaultProps.groupName = AggGroupNames.Buckets; + const comp = mount(); + act(() => { + const aggProps = comp.find(DefaultEditorAgg).props(); + aggProps.setValidity(false); + aggProps.setTouched(true); + }); + + expect(setTouched).toBeCalledWith(true); + }); + + it('should mark group as touched when the form applied', () => { + const comp = mount(); + act(() => { + comp + .find(DefaultEditorAgg) + .first() + .props() + .setValidity(false); + }); + expect(setTouched).toBeCalledWith(false); + comp.setProps({ formIsTouched: true }); + + expect(setTouched).toBeCalledWith(true); + }); + + it('should mark group as invalid when at least one agg is invalid', () => { + const comp = mount(); + act(() => { + comp + .find(DefaultEditorAgg) + .first() + .props() + .setValidity(false); + }); + + expect(setValidity).toBeCalledWith(false); + }); + + it('should last bucket has truthy isLastBucket prop', () => { + defaultProps.groupName = AggGroupNames.Buckets; + const comp = mount(); + const lastAgg = comp.find(DefaultEditorAgg).last(); + + expect(lastAgg.props()).toHaveProperty('isLastBucket', true); + }); + + it('should call reorderAggs when dragging ended', () => { + const comp = shallow(); + act(() => { + // simulate dragging ending + comp.props().onDragEnd({ source: { index: 0 }, destination: { index: 1 } }); + }); + + expect(reorderAggs).toHaveBeenCalledWith([ + defaultProps.state.aggs[1], + defaultProps.state.aggs[0], + ]); + }); + + it('should show add button when schemas count is less than max', () => { + defaultProps.groupName = AggGroupNames.Buckets; + const comp = shallow(); + + expect(comp.find(DefaultEditorAggAdd).exists()).toBeTruthy(); + }); + + it('should not show add button when schemas count is not less than max', () => { + const comp = shallow(); + + expect(comp.find(DefaultEditorAggAdd).exists()).toBeFalsy(); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.tsx index 4a5f012f8783c9..d645efa818c141 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.tsx @@ -40,7 +40,7 @@ import { import { aggGroupReducer, initAggsState, AGGS_ACTION_KEYS } from './default_editor_agg_group_state'; import { Schema } from '../schemas'; -interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps { +export interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps { schemas: Schema[]; addSchema: (schems: Schema) => void; reorderAggs: (group: AggConfig[]) => void; diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.test.ts b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.test.ts new file mode 100644 index 00000000000000..36b5ad82ae29fd --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.test.ts @@ -0,0 +1,139 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 { AggConfig } from '../../../agg_config'; +import { + isAggRemovable, + calcAggIsTooLow, + isInvalidAggsTouched, +} from './default_editor_agg_group_helper'; +import { AggsState } from './default_editor_agg_group_state'; + +describe('DefaultEditorGroup helpers', () => { + let group: AggConfig; + + beforeEach(() => { + group = [ + { + id: 1, + title: 'Test1', + params: { + field: { + type: 'number', + }, + }, + group: 'metrics', + schema: { name: 'metric', min: 1, mustBeFirst: true }, + }, + { + id: 2, + title: 'Test2', + params: { + field: { + type: 'string', + }, + }, + group: 'metrics', + schema: { name: 'metric', min: 2 }, + }, + ]; + }); + describe('isAggRemovable', () => { + it('should return true when the number of aggs with the same schema is above the min', () => { + const isRemovable = isAggRemovable(group[0], group); + + expect(isRemovable).toBeTruthy(); + }); + + it('should return false when the number of aggs with the same schema is not above the min', () => { + const isRemovable = isAggRemovable(group[1], group); + + expect(isRemovable).toBeFalsy(); + }); + }); + + describe('calcAggIsTooLow', () => { + it('should return false when agg.schema.mustBeFirst has falsy value', () => { + const isRemovable = calcAggIsTooLow(group[1], 0, group); + + expect(isRemovable).toBeFalsy(); + }); + + it('should return false when there is no different schema', () => { + group[1].schema = group[0].schema; + const isRemovable = calcAggIsTooLow(group[0], 0, group); + + expect(isRemovable).toBeFalsy(); + }); + + it('should return false when different schema is not less than agg index', () => { + const isRemovable = calcAggIsTooLow(group[0], 0, group); + + expect(isRemovable).toBeFalsy(); + }); + + it('should return true when agg index is greater than different schema index', () => { + const isRemovable = calcAggIsTooLow(group[0], 2, group); + + expect(isRemovable).toBeTruthy(); + }); + }); + + describe('isInvalidAggsTouched', () => { + let aggsState: AggsState; + + beforeEach(() => { + aggsState = { + 1: { + valid: true, + touched: false, + }, + 2: { + valid: true, + touched: false, + }, + 3: { + valid: true, + touched: false, + }, + }; + }); + + it('should return false when there are no invalid aggs', () => { + const isAllInvalidAggsTouched = isInvalidAggsTouched(aggsState); + + expect(isAllInvalidAggsTouched).toBeFalsy(); + }); + + it('should return false when not all invalid aggs are touched', () => { + aggsState[1].valid = false; + const isAllInvalidAggsTouched = isInvalidAggsTouched(aggsState); + + expect(isAllInvalidAggsTouched).toBeFalsy(); + }); + + it('should return true when all invalid aggs are touched', () => { + aggsState[1].valid = false; + aggsState[1].touched = true; + const isAllInvalidAggsTouched = isInvalidAggsTouched(aggsState); + + expect(isAllInvalidAggsTouched).toBeTruthy(); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.ts similarity index 100% rename from src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.tsx rename to src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.ts