Skip to content

Commit

Permalink
[Lens] Add bucket nesting editor to indexpattern (#42869)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdavies authored Aug 14, 2019
1 parent 398a3de commit e9f5807
Show file tree
Hide file tree
Showing 3 changed files with 350 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/*
* 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> = {}): 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 an unchecked switch if there are two buckets and it is the root', () => {
const component = mount(
<BucketNestingEditor
columnId="a"
layer={{
columnOrder: ['a', 'b', 'c'],
columns: {
a: mockCol({ suggestedPriority: 0 }),
b: mockCol({ suggestedPriority: 1 }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }),
},
indexPatternId: 'foo',
}}
setColumns={jest.fn()}
/>
);
const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first();

expect(control.prop('checked')).toBeFalsy();
});

it('should display a checked switch if there are two buckets and it is not the root', () => {
const component = mount(
<BucketNestingEditor
columnId="a"
layer={{
columnOrder: ['b', 'a', 'c'],
columns: {
a: mockCol({ suggestedPriority: 0 }),
b: mockCol({ suggestedPriority: 1 }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }),
},
indexPatternId: 'foo',
}}
setColumns={jest.fn()}
/>
);
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(
<BucketNestingEditor
columnId="a"
layer={{
columnOrder: ['b', 'a', 'c'],
columns: {
a: mockCol({ suggestedPriority: 0 }),
b: mockCol({ suggestedPriority: 1 }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }),
},
indexPatternId: 'foo',
}}
setColumns={setColumns}
/>
);
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 component = mount(
<BucketNestingEditor
columnId="a"
layer={{
columnOrder: ['a', 'b', 'c'],
columns: {
a: mockCol({ suggestedPriority: 0, operationType: 'avg', isBucketed: false }),
b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: false }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }),
},
indexPatternId: 'foo',
}}
setColumns={jest.fn()}
/>
);

expect(component.children().length).toBe(0);
});

it('should display nothing if there is one bucket', () => {
const component = mount(
<BucketNestingEditor
columnId="a"
layer={{
columnOrder: ['a', 'b', 'c'],
columns: {
a: mockCol({ suggestedPriority: 0 }),
b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: false }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }),
},
indexPatternId: 'foo',
}}
setColumns={jest.fn()}
/>
);

expect(component.children().length).toBe(0);
});

it('should display a dropdown with the parent column selected if 3+ buckets', () => {
const component = mount(
<BucketNestingEditor
columnId="a"
layer={{
columnOrder: ['c', 'a', 'b'],
columns: {
a: mockCol({ suggestedPriority: 0, operationType: 'count', isBucketed: true }),
b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: true }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: true }),
},
indexPatternId: 'foo',
}}
setColumns={jest.fn()}
/>
);

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(
<BucketNestingEditor
columnId="a"
layer={{
columnOrder: ['c', 'a', 'b'],
columns: {
a: mockCol({ suggestedPriority: 0, operationType: 'count', isBucketed: true }),
b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: true }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: true }),
},
indexPatternId: 'foo',
}}
setColumns={setColumns}
/>
);

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(
<BucketNestingEditor
columnId="a"
layer={{
columnOrder: ['c', 'a', 'b'],
columns: {
a: mockCol({ suggestedPriority: 0, operationType: 'count', isBucketed: true }),
b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: true }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: true }),
},
indexPatternId: 'foo',
}}
setColumns={setColumns}
/>
);

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']);
});

it('should allow the last bucket to be moved', () => {
const setColumns = jest.fn();
const component = mount(
<BucketNestingEditor
columnId="b"
layer={{
columnOrder: ['c', 'a', 'b'],
columns: {
a: mockCol({ suggestedPriority: 0, operationType: 'count', isBucketed: true }),
b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: true }),
c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: true }),
},
indexPatternId: 'foo',
}}
setColumns={setColumns}
/>
);

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']);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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 (
<EuiFormRow>
<>
<EuiHorizontalRule margin="m" />
<EuiSwitch
data-test-subj="indexPattern-nesting-switch"
label={i18n.translate('xpack.lens.xyChart.nestUnderTarget', {
defaultMessage: 'Nest under {target}',
values: { target: target.text },
})}
checked={!!prevColumn}
onChange={() => {
if (prevColumn) {
setColumns(nestColumn(layer.columnOrder, columnId, target.value));
} else {
setColumns(nestColumn(layer.columnOrder, target.value, columnId));
}
}}
/>
</>
</EuiFormRow>
);
}

return (
<EuiFormRow>
<>
<EuiHorizontalRule margin="m" />
<EuiFormLabel>
{i18n.translate('xpack.lens.xyChart.nestUnder', {
defaultMessage: 'Nest under',
})}
</EuiFormLabel>
<EuiSelect
data-test-subj="indexPattern-nesting-select"
options={[
{
value: '',
text: i18n.translate('xpack.lens.xyChart.nestUnderRoot', {
defaultMessage: 'Top level',
}),
},
...aggColumns,
]}
value={prevColumn}
onChange={e => setColumns(nestColumn(layer.columnOrder, e.target.value, columnId))}
/>
</>
</EuiFormRow>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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();

Expand Down Expand Up @@ -363,6 +364,22 @@ export function PopoverEditor(props: PopoverEditorProps) {
/>
</EuiFormRow>
)}
<BucketNestingEditor
layer={state.layers[props.layerId]}
columnId={props.columnId}
setColumns={columnOrder => {
setState({
...state,
layers: {
...state.layers,
[props.layerId]: {
...state.layers[props.layerId],
columnOrder,
},
},
});
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down

0 comments on commit e9f5807

Please sign in to comment.