Skip to content
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', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is the same test as the one above, maybe you forgot to implement it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. You're right. Copy/paste for the win.

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']);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some test ideas that I don't see, but you can add if you would like:

  • Test that toggling the switch twice ends in the same state as before
  • Test that toggling the switch enables the switch in the other dimension
  • Test the case where it's all buckets- should be able to replace the last column


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