diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx index f649564b2231a..083ceff3bb444 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx @@ -176,5 +176,39 @@ describe('Datatable Visualization', () => { ], }); }); + + it('reorders the rendered colums based on the order from the datasource', () => { + const datasource = createMockDatasource(); + const layer = { layerId: 'a', columns: ['b', 'c'] }; + const frame = mockFrame(); + frame.datasourceLayers = { a: datasource.publicAPIMock }; + const component = mount( + {} }} + frame={frame} + layer={layer} + setState={jest.fn()} + state={{ layers: [layer] }} + /> + ); + + const accessors = component + .find('[data-test-subj="datatable_multicolumnEditor"]') + .first() + .prop('accessors') as string[]; + + expect(accessors).toEqual(['b', 'c']); + + component.setProps({ + layer: { layerId: 'a', columns: ['c', 'b'] }, + }); + + const newAccessors = component + .find('[data-test-subj="datatable_multicolumnEditor"]') + .first() + .prop('accessors') as string[]; + + expect(newAccessors).toEqual(['c', 'b']); + }); }); }); diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx index 18a6d112e14d6..31d2e6ad4c7a4 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx @@ -58,6 +58,11 @@ export function DataTableLayer({ dragDropContext, }: { layer: LayerState } & VisualizationProps) { const datasource = frame.datasourceLayers[layer.layerId]; + + const originalOrder = datasource.getTableSpec().map(({ columnId }) => columnId); + // When we add a column it could be empty, and therefore have no order + const sortedColumns = Array.from(new Set(originalOrder.concat(layer.columns))); + return ( { return result as IndexPatternColumn; } - it('should display an unchecked switch if there are two buckets and it is the root', () => { + it('should display the top level grouping when at the root', () => { const component = mount( { setColumns={jest.fn()} /> ); - const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first(); + const control1 = component.find('[data-test-subj="indexPattern-nesting-topLevel"]').first(); + const control2 = component.find('[data-test-subj="indexPattern-nesting-bottomLevel"]').first(); - expect(control.prop('checked')).toBeFalsy(); + expect(control1.prop('checked')).toBeTruthy(); + expect(control2.prop('checked')).toBeFalsy(); }); - it('should display a checked switch if there are two buckets and it is not the root', () => { + it('should display the bottom level grouping when appropriate', () => { const component = mount( { setColumns={jest.fn()} /> ); - const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first(); - expect(control.prop('checked')).toBeTruthy(); + const control1 = component.find('[data-test-subj="indexPattern-nesting-topLevel"]').first(); + const control2 = component.find('[data-test-subj="indexPattern-nesting-bottomLevel"]').first(); + + expect(control1.prop('checked')).toBeFalsy(); + expect(control2.prop('checked')).toBeTruthy(); }); it('should reorder the columns when toggled', () => { @@ -88,11 +93,31 @@ describe('BucketNestingEditor', () => { setColumns={setColumns} /> ); - const control = component.find('[data-test-subj="indexPattern-nesting-switch"]').first(); + const control1 = component.find('[data-test-subj="indexPattern-nesting-topLevel"]').first(); - (control.prop('onChange') as () => {})(); + (control1.prop('onChange') as () => {})(); + expect(setColumns).toHaveBeenCalledTimes(1); expect(setColumns).toHaveBeenCalledWith(['a', 'b', 'c']); + + component.setProps({ + layer: { + columnOrder: ['a', 'b', 'c'], + columns: { + a: mockCol({ suggestedPriority: 0 }), + b: mockCol({ suggestedPriority: 1 }), + c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }), + }, + indexPatternId: 'foo', + }, + }); + + const control2 = component.find('[data-test-subj="indexPattern-nesting-bottomLevel"]').first(); + + (control2.prop('onChange') as () => {})(); + + expect(setColumns).toHaveBeenCalledTimes(2); + expect(setColumns).toHaveBeenLastCalledWith(['b', 'a', 'c']); }); it('should display nothing if there are no buckets', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx index 71f5722151802..04e13fead6fca 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/bucket_nesting_editor.tsx @@ -7,8 +7,11 @@ import _ from 'lodash'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFormRow, EuiHorizontalRule, EuiSwitch, EuiSelect } from '@elastic/eui'; +import { EuiFormRow, EuiHorizontalRule, EuiRadio, EuiSelect, htmlIdGenerator } from '@elastic/eui'; import { IndexPatternLayer } from '../types'; +import { hasField } from '../utils'; + +const generator = htmlIdGenerator('lens-nesting'); function nestColumn(columnOrder: string[], outer: string, inner: string) { const result = columnOrder.filter(c => c !== inner); @@ -32,35 +35,75 @@ export function BucketNestingEditor({ const columns = Object.entries(layer.columns); const aggColumns = columns .filter(([id, c]) => id !== columnId && c.isBucketed) - .map(([value, c]) => ({ value, text: c.label })); + .map(([value, c]) => ({ + value, + text: c.label, + fieldName: hasField(c) ? c.sourceField : '', + })); if (!column || !column.isBucketed || !aggColumns.length) { return null; } + const fieldName = hasField(column) ? column.sourceField : ''; + const prevColumn = layer.columnOrder[layer.columnOrder.indexOf(columnId) - 1]; if (aggColumns.length === 1) { const [target] = aggColumns; + function toggleNesting() { + if (prevColumn) { + setColumns(nestColumn(layer.columnOrder, columnId, target.value)); + } else { + setColumns(nestColumn(layer.columnOrder, target.value, columnId)); + } + } + return ( <> - { - if (prevColumn) { - setColumns(nestColumn(layer.columnOrder, columnId, target.value)); - } else { - setColumns(nestColumn(layer.columnOrder, target.value, columnId)); - } - }} - /> + > + <> + + + + ); } @@ -69,8 +112,8 @@ export function BucketNestingEditor({ <> @@ -81,7 +124,7 @@ export function BucketNestingEditor({ { value: '', text: i18n.translate('xpack.lens.xyChart.nestUnderRoot', { - defaultMessage: 'Top level', + defaultMessage: 'Entire data set', }), }, ...aggColumns, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx index bc46143b5f3b5..b9baa489f3382 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx @@ -70,6 +70,7 @@ export function PopoverEditor(props: PopoverEditorProps) { layerId, currentIndexPattern, uniqueLabel, + hideGrouping, } = props; const { operationByDocument, operationByField, fieldByOperation } = operationFieldSupportMatrix; const [isPopoverOpen, setPopoverOpen] = useState(false); @@ -399,22 +400,25 @@ export function PopoverEditor(props: PopoverEditorProps) { /> )} - { - setState({ - ...state, - layers: { - ...state.layers, - [props.layerId]: { - ...state.layers[props.layerId], - columnOrder, + + {!hideGrouping && ( + { + setState({ + ...state, + layers: { + ...state.layers, + [props.layerId]: { + ...state.layers[props.layerId], + columnOrder, + }, }, - }, - }); - }} - /> + }); + }} + /> + )} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts index f3e4c32103abf..49944614edbb4 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts @@ -460,9 +460,9 @@ function createMetricSuggestion( function getNestedTitle([outerBucket, innerBucket]: IndexPatternColumn[]) { return i18n.translate('xpack.lens.indexpattern.suggestions.nestingChangeLabel', { - defaultMessage: '{innerOperation} per each {outerOperation}', + defaultMessage: '{innerOperation} for each {outerOperation}', values: { - innerOperation: innerBucket.label, + innerOperation: hasField(innerBucket) ? innerBucket.sourceField : innerBucket.label, outerOperation: hasField(outerBucket) ? outerBucket.sourceField : outerBucket.label, }, }); diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index 2713a44f53980..1efa123f4f0a3 100644 --- a/x-pack/legacy/plugins/lens/public/types.ts +++ b/x-pack/legacy/plugins/lens/public/types.ts @@ -189,6 +189,11 @@ export interface DatasourceDimensionPanelProps { // affects the default ordering of the query suggestedPriority?: DimensionPriority; onRemove?: (accessor: string) => void; + + // Some dimension editors will allow users to change the operation grouping + // from the panel, and this lets the visualization hint that it doesn't want + // users to have that level of control + hideGrouping?: boolean; } export interface DatasourceLayerPanelProps { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx index 9461229313f4c..322e1f1ea4a0e 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx @@ -186,6 +186,7 @@ export function XYConfigPanel(props: VisualizationProps) { filterOperations: isBucketed, suggestedPriority: 1, layerId: layer.layerId, + hideGrouping: true, }} />