From a4bae5919c27f7547a48ab3111ceb1f151de0a5a Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Wed, 7 Aug 2019 15:53:22 -0400 Subject: [PATCH 1/3] Add bucket nesting editor to indexpattern --- .../bucket_nesting_editor.test.tsx | 215 ++++++++++++++++++ .../dimension_panel/bucket_nesting_editor.tsx | 94 ++++++++ .../dimension_panel/popover_editor.tsx | 17 ++ 3 files changed, 326 insertions(+) create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx new file mode 100644 index 0000000000000..a483a9492180b --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx @@ -0,0 +1,215 @@ +/* + * 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 { mount } from 'enzyme'; +import React from 'react'; +import { BucketNestingEditor } from './bucket_nesting_editor'; +import { IndexPatternColumn } from '../indexpattern'; + +describe('BucketNestingEditor', () => { + function mockCol(col: Partial = {}): IndexPatternColumn { + const result = { + dataType: 'string', + isBucketed: true, + label: 'a', + operationType: 'terms', + params: { + size: 5, + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + }, + sourceField: 'a', + suggestedPriority: 0, + ...col, + }; + + return result as IndexPatternColumn; + } + + it('should display a checked switch if there are two buckets and it is the root', () => { + const component = mount( + + ); + const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first(); + + expect(control.prop('checked')).toBeFalsy(); + }); + + it('should display an unchecked switch if there are two buckets and it is not the root', () => { + const component = mount( + + ); + const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first(); + + expect(control.prop('checked')).toBeTruthy(); + }); + + it('should reorder the columns when toggled', () => { + const setColumns = jest.fn(); + const component = mount( + + ); + const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first(); + + (control.prop('onChange') as () => {})(); + + expect(setColumns).toHaveBeenCalledWith(['a', 'b', 'c']); + }); + + it('should display nothing if there are no buckets', () => { + const setColumns = jest.fn(); + const component = mount( + + ); + const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first(); + + (control.prop('onChange') as () => {})(); + + expect(setColumns).toHaveBeenCalledWith(['a', 'b', 'c']); + }); + + it('should display nothing if there is one bucket', () => { + const component = mount( + + ); + + expect(component.children().length).toBe(0); + }); + + it('should display a dropdown with the parent column selected if 3+ buckets', () => { + const component = mount( + + ); + + const control = component.find('[data-test-subj="indexPattern-nesting-select"]').first(); + + expect(control.prop('value')).toEqual('c'); + }); + + it('should reorder the columns when a column is selected in the dropdown', () => { + const setColumns = jest.fn(); + const component = mount( + + ); + + const control = component.find('[data-test-subj="indexPattern-nesting-select"]').first(); + (control.prop('onChange') as (e: unknown) => {})({ + target: { value: 'b' }, + }); + + expect(setColumns).toHaveBeenCalledWith(['c', 'b', 'a']); + }); + + it('should move to root if the first dropdown item is selected', () => { + const setColumns = jest.fn(); + const component = mount( + + ); + + const control = component.find('[data-test-subj="indexPattern-nesting-select"]').first(); + (control.prop('onChange') as (e: unknown) => {})({ + target: { value: '' }, + }); + + expect(setColumns).toHaveBeenCalledWith(['a', 'c', 'b']); + }); +}); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx new file mode 100644 index 0000000000000..2198e081ae034 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx @@ -0,0 +1,94 @@ +/* + * 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 _ from 'lodash'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiHorizontalRule, EuiSwitch, EuiSelect, EuiFormLabel } from '@elastic/eui'; +import { IndexPatternLayer } from '../indexpattern'; + +function nestColumn(columnOrder: string[], outer: string, inner: string) { + const result = columnOrder.filter(c => c !== inner); + const outerPosition = result.indexOf(outer); + + result.splice(outerPosition + 1, 0, inner); + + return result; +} + +export function BucketNestingEditor({ + columnId, + layer, + setColumns, +}: { + columnId: string; + layer: IndexPatternLayer; + setColumns: (columns: string[]) => void; +}) { + const column = layer.columns[columnId]; + const columns = Object.entries(layer.columns); + const aggColumns = columns + .filter(([id, c]) => id !== columnId && c.isBucketed) + .map(([value, c]) => ({ value, text: c.label })); + + if (!column || !column.isBucketed || !aggColumns.length) { + return null; + } + + const prevColumn = layer.columnOrder[layer.columnOrder.indexOf(columnId) - 1]; + + if (aggColumns.length === 1) { + const [target] = aggColumns; + + return ( + + <> + + { + if (prevColumn) { + setColumns(nestColumn(layer.columnOrder, columnId, target.value)); + } else { + setColumns(nestColumn(layer.columnOrder, target.value, columnId)); + } + }} + /> + + + ); + } + + return ( + + <> + + + {i18n.translate('xpack.lens.xyChart.nestUnder', { + defaultMessage: 'Nest under', + })} + + setColumns(nestColumn(layer.columnOrder, e.target.value, columnId))} + /> + + + ); +} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx index cc899caf8f4e7..86c06ea98b001 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx @@ -31,6 +31,7 @@ import { operationDefinitionMap, getOperationDisplay, buildColumn } from '../ope import { deleteColumn, changeColumn } from '../state_helpers'; import { FieldSelect } from './field_select'; import { hasField } from '../utils'; +import { BucketNestingEditor } from './bucket_nesting_editor'; const operationPanels = getOperationDisplay(); @@ -335,6 +336,22 @@ export function PopoverEditor(props: PopoverEditorProps) { /> )} + { + setState({ + ...state, + layers: { + ...state.layers, + [props.layerId]: { + ...state.layers[props.layerId], + columnOrder, + }, + }, + }); + }} + /> From 283ee62a255b2f89d5d97e51ae8aee95eab27805 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Fri, 9 Aug 2019 14:21:31 -0400 Subject: [PATCH 2/3] Fix tests --- .../bucket_nesting_editor.test.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx index a483a9492180b..afab6671532fb 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx @@ -29,7 +29,7 @@ describe('BucketNestingEditor', () => { return result as IndexPatternColumn; } - it('should display a checked switch if there are two buckets and it is the root', () => { + it('should display an unchecked switch if there are two buckets and it is the root', () => { const component = mount( { expect(control.prop('checked')).toBeFalsy(); }); - it('should display an unchecked switch if there are two buckets and it is not the root', () => { + it('should display a checked switch if there are two buckets and it is not the root', () => { const component = mount( { }); it('should display nothing if there are no buckets', () => { - const setColumns = jest.fn(); const component = mount( ); - const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first(); - - (control.prop('onChange') as () => {})(); - expect(setColumns).toHaveBeenCalledWith(['a', 'b', 'c']); + expect(component.children().length).toBe(0); }); it('should display nothing if there is one bucket', () => { From 4d53018bf2f31a71b6cdc3f6b25101f2c8266654 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Tue, 13 Aug 2019 14:15:27 -0400 Subject: [PATCH 3/3] Tweaks based on PR feedback ... added test, adjusted verbiage --- .../bucket_nesting_editor.test.tsx | 26 +++++++++++++++++++ .../dimension_panel/bucket_nesting_editor.tsx | 4 ++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx index afab6671532fb..c9d7ce1ea81f8 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx @@ -208,4 +208,30 @@ describe('BucketNestingEditor', () => { expect(setColumns).toHaveBeenCalledWith(['a', 'c', 'b']); }); + + it('should allow the last bucket to be moved', () => { + const setColumns = jest.fn(); + const component = mount( + + ); + + const control = component.find('[data-test-subj="indexPattern-nesting-select"]').first(); + (control.prop('onChange') as (e: unknown) => {})({ + target: { value: '' }, + }); + + expect(setColumns).toHaveBeenCalledWith(['b', 'c', 'a']); + }); }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx index 2198e081ae034..630dc6252b6ee 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx @@ -81,7 +81,9 @@ export function BucketNestingEditor({ options={[ { value: '', - text: '', + text: i18n.translate('xpack.lens.xyChart.nestUnderRoot', { + defaultMessage: 'Top level', + }), }, ...aggColumns, ]}