-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Lens] Add bucket nesting editor to indexpattern (#42869)
- Loading branch information
1 parent
398a3de
commit e9f5807
Showing
3 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
237 changes: 237 additions & 0 deletions
237
...cy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']); | ||
}); | ||
}); |
96 changes: 96 additions & 0 deletions
96
.../legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters