-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Lens] Add bucket nesting editor to indexpattern #42869
Merged
chrisdavies
merged 7 commits into
elastic:feature/lens
from
chrisdavies:lens/nested-xy-split
Aug 14, 2019
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a4bae59
Add bucket nesting editor to indexpattern
chrisdavies afa5bc0
Merge branch 'feature/lens' of github.com:elastic/kibana into lens/ne…
chrisdavies 283ee62
Fix tests
chrisdavies 3f5a48f
Merge remote-tracking branch 'upstream/feature/lens' into lens/nested…
flash1293 09659b7
Merge branch 'feature/lens' of github.com:elastic/kibana into lens/ne…
chrisdavies 4d53018
Tweaks based on PR feedback ... added test, adjusted verbiage
chrisdavies 0f44fb8
Merge branch 'lens/nested-xy-split' of github.com:chrisdavies/kibana …
chrisdavies File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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']); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
|
||
|
||
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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.