diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 4e48d0c0987b51..e36622f876acd5 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -780,7 +780,7 @@ describe('IndexPattern Data Source suggestions', () => { expect(suggestions[0].table.columns[0].operation.isBucketed).toBeFalsy(); }); - it('prepends a terms column on string field', () => { + it('appends a terms column on string field', () => { const initialState = stateWithNonEmptyTables(); const suggestions = getDatasourceSuggestionsForField(initialState, '1', { name: 'dest', @@ -795,7 +795,7 @@ describe('IndexPattern Data Source suggestions', () => { layers: { previousLayer: initialState.layers.previousLayer, currentLayer: expect.objectContaining({ - columnOrder: ['id1', 'cola', 'colb'], + columnOrder: ['cola', 'id1', 'colb'], columns: { ...initialState.layers.currentLayer.columns, id1: expect.objectContaining({ @@ -810,7 +810,7 @@ describe('IndexPattern Data Source suggestions', () => { ); }); - it('appends a metric column on a number field', () => { + it('replaces a metric column on a number field if only one other metric is already set', () => { const initialState = stateWithNonEmptyTables(); const suggestions = getDatasourceSuggestionsForField(initialState, '1', { name: 'memory', @@ -819,15 +819,57 @@ describe('IndexPattern Data Source suggestions', () => { searchable: true, }); + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: expect.objectContaining({ + currentLayer: expect.objectContaining({ + columnOrder: ['cola', 'id1'], + columns: { + cola: initialState.layers.currentLayer.columns.cola, + id1: expect.objectContaining({ + operationType: 'avg', + sourceField: 'memory', + }), + }, + }), + }), + }), + }) + ); + }); + + it('adds a metric column on a number field if no other metrics set', () => { + const initialState = stateWithNonEmptyTables(); + const modifiedState: IndexPatternPrivateState = { + ...initialState, + layers: { + ...initialState.layers, + currentLayer: { + ...initialState.layers.currentLayer, + columns: { + cola: initialState.layers.currentLayer.columns.cola, + }, + columnOrder: ['cola'], + }, + }, + }; + const suggestions = getDatasourceSuggestionsForField(modifiedState, '1', { + name: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }); + expect(suggestions).toContainEqual( expect.objectContaining({ state: expect.objectContaining({ layers: { - previousLayer: initialState.layers.previousLayer, + previousLayer: modifiedState.layers.previousLayer, currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'colb', 'id1'], + columnOrder: ['cola', 'id1'], columns: { - ...initialState.layers.currentLayer.columns, + ...modifiedState.layers.currentLayer.columns, id1: expect.objectContaining({ operationType: 'avg', sourceField: 'memory', @@ -840,10 +882,30 @@ describe('IndexPattern Data Source suggestions', () => { ); }); - it('appends a metric column with a different operation on a number field if field is already in use', () => { + it('adds a metric column on a number field if 2 or more other metric', () => { const initialState = stateWithNonEmptyTables(); - const suggestions = getDatasourceSuggestionsForField(initialState, '1', { - name: 'bytes', + const modifiedState: IndexPatternPrivateState = { + ...initialState, + layers: { + ...initialState.layers, + currentLayer: { + ...initialState.layers.currentLayer, + columns: { + ...initialState.layers.currentLayer.columns, + colc: { + dataType: 'number', + isBucketed: false, + sourceField: 'dest', + label: 'Unique count of dest', + operationType: 'cardinality', + }, + }, + columnOrder: ['cola', 'colb', 'colc'], + }, + }, + }; + const suggestions = getDatasourceSuggestionsForField(modifiedState, '1', { + name: 'memory', type: 'number', aggregatable: true, searchable: true, @@ -853,14 +915,14 @@ describe('IndexPattern Data Source suggestions', () => { expect.objectContaining({ state: expect.objectContaining({ layers: { - previousLayer: initialState.layers.previousLayer, + previousLayer: modifiedState.layers.previousLayer, currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'colb', 'id1'], + columnOrder: ['cola', 'colb', 'colc', 'id1'], columns: { - ...initialState.layers.currentLayer.columns, + ...modifiedState.layers.currentLayer.columns, id1: expect.objectContaining({ - operationType: 'sum', - sourceField: 'bytes', + operationType: 'avg', + sourceField: 'memory', }), }, }), diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index 35e99fc4fe98de..96127caa67bb42 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -197,16 +197,29 @@ function addFieldAsMetricOperation( field, }); const newColumnId = generateId(); - const updatedColumns = { - ...layer.columns, - [newColumnId]: newColumn, - }; - const updatedColumnOrder = [...layer.columnOrder, newColumnId]; + + const [, metrics] = separateBucketColumns(layer); + + // Add metrics if there are 0 or > 1 metric + if (metrics.length !== 1) { + return { + indexPatternId: indexPattern.id, + columns: { + ...layer.columns, + [newColumnId]: newColumn, + }, + columnOrder: [...layer.columnOrder, newColumnId], + }; + } + + // If only one metric, replace instead of add + const newColumns = { ...layer.columns, [newColumnId]: newColumn }; + delete newColumns[metrics[0]]; return { indexPatternId: indexPattern.id, - columns: updatedColumns, - columnOrder: updatedColumnOrder, + columns: newColumns, + columnOrder: [...layer.columnOrder.filter(c => c !== metrics[0]), newColumnId], }; } @@ -231,21 +244,34 @@ function addFieldAsBucketOperation( ...layer.columns, [newColumnId]: newColumn, }; + + const oldDateHistogramIndex = layer.columnOrder.findIndex( + columnId => layer.columns[columnId].operationType === 'date_histogram' + ); + const oldDateHistogramId = + oldDateHistogramIndex > -1 ? layer.columnOrder[oldDateHistogramIndex] : null; + let updatedColumnOrder: string[] = []; - if (applicableBucketOperation === 'terms') { - updatedColumnOrder = [newColumnId, ...buckets, ...metrics]; - } else { - const oldDateHistogramColumn = layer.columnOrder.find( - columnId => layer.columns[columnId].operationType === 'date_histogram' - ); - if (oldDateHistogramColumn) { - delete updatedColumns[oldDateHistogramColumn]; + if (oldDateHistogramId) { + if (applicableBucketOperation === 'terms') { + // Insert the new terms bucket above the first date histogram + updatedColumnOrder = [ + ...buckets.slice(0, oldDateHistogramIndex), + newColumnId, + ...buckets.slice(oldDateHistogramIndex, buckets.length), + ...metrics, + ]; + } else if (applicableBucketOperation === 'date_histogram') { + // Replace date histogram with new date histogram + delete updatedColumns[oldDateHistogramId]; updatedColumnOrder = layer.columnOrder.map(columnId => - columnId !== oldDateHistogramColumn ? columnId : newColumnId + columnId !== oldDateHistogramId ? columnId : newColumnId ); - } else { - updatedColumnOrder = [...buckets, newColumnId, ...metrics]; } + } else { + // Insert the new bucket after existing buckets. Users will see the same data + // they already had, with an extra level of detail. + updatedColumnOrder = [...buckets, newColumnId, ...metrics]; } return { indexPatternId: indexPattern.id,