-
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
Changes from 4 commits
a4bae59
afa5bc0
283ee62
3f5a48f
09659b7
4d53018
0f44fb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
/* | ||
* 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:
|
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<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: '', | ||
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. I think it's a little confusing to not have a label here- maybe we can have a placeholder text like 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. Yeah. I tried a bit of different verbiage, and to me the blank state was the clearest and least likely to conflict with column names. But I'm not strongly opinionated. |
||
}, | ||
...aggColumns, | ||
]} | ||
value={prevColumn} | ||
onChange={e => setColumns(nestColumn(layer.columnOrder, e.target.value, columnId))} | ||
/> | ||
</> | ||
</EuiFormRow> | ||
); | ||
} |
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.