diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx index 8b03a879da431e..b3bd08d3bbfbe6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx @@ -140,12 +140,16 @@ export function onDrop( operationsForNewField && operationsForNewField.includes(selectedColumn.operationType); + if (!operationsForNewField || operationsForNewField.length === 0) { + return false; + } + // If only the field has changed use the onFieldChange method on the operation to get the // new column, otherwise use the regular buildColumn to get a new column. const newColumn = hasFieldChanged ? changeField(selectedColumn, currentIndexPattern, droppedItem.field) : buildColumn({ - op: operationsForNewField ? operationsForNewField[0] : undefined, + op: operationsForNewField[0], columns: props.state.layers[props.layerId].columns, indexPattern: currentIndexPattern, layerId, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index d339171a5ae1f6..2b3e976a77ea75 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -14,6 +14,7 @@ import { getOperationTypesForField, operationDefinitionMap, IndexPatternColumn, + OperationType, } from './operations'; import { operationDefinitions } from './operations/definitions'; import { hasField } from './utils'; @@ -141,7 +142,13 @@ function getExistingLayerSuggestionsForField( suggestions.push( buildSuggestion({ state, - updatedLayer: addFieldAsBucketOperation(layer, layerId, indexPattern, field), + updatedLayer: addFieldAsBucketOperation( + layer, + layerId, + indexPattern, + field, + usableAsBucketOperation + ), layerId, changeType: 'extended', }) @@ -176,26 +183,7 @@ function addFieldAsMetricOperation( indexPattern: IndexPattern, field: IndexPatternField ): IndexPatternLayer | undefined { - const operations = getOperationTypesForField(field); - const operationsAlreadyAppliedToThisField = Object.values(layer.columns) - .filter(column => hasField(column) && column.sourceField === field.name) - .map(column => column.operationType); - const operationCandidate = operations.find( - operation => !operationsAlreadyAppliedToThisField.includes(operation) - ); - - if (!operationCandidate) { - return; - } - - const newColumn = buildColumn({ - op: operationCandidate, - columns: layer.columns, - layerId, - indexPattern, - suggestedPriority: undefined, - field, - }); + const newColumn = getMetricColumn(indexPattern, layerId, field); const addedColumnId = generateId(); const [, metrics] = separateBucketColumns(layer); @@ -226,11 +214,11 @@ function addFieldAsBucketOperation( layer: IndexPatternLayer, layerId: string, indexPattern: IndexPattern, - field: IndexPatternField + field: IndexPatternField, + operation: OperationType ): IndexPatternLayer { - const applicableBucketOperation = getBucketOperation(field); const newColumn = buildColumn({ - op: applicableBucketOperation, + op: operation, columns: layer.columns, layerId, indexPattern, @@ -252,7 +240,7 @@ function addFieldAsBucketOperation( let updatedColumnOrder: string[] = []; if (oldDateHistogramId) { - if (applicableBucketOperation === 'terms') { + if (operation === 'terms') { // Insert the new terms bucket above the first date histogram updatedColumnOrder = [ ...buckets.slice(0, oldDateHistogramIndex), @@ -260,7 +248,7 @@ function addFieldAsBucketOperation( ...buckets.slice(oldDateHistogramIndex, buckets.length), ...metrics, ]; - } else if (applicableBucketOperation === 'date_histogram') { + } else if (operation === 'date_histogram') { // Replace date histogram with new date histogram delete updatedColumns[oldDateHistogramId]; updatedColumnOrder = layer.columnOrder.map(columnId => @@ -287,8 +275,9 @@ function getEmptyLayerSuggestionsForField( ): IndexPatternSugestion[] { const indexPattern = state.indexPatterns[indexPatternId]; let newLayer: IndexPatternLayer | undefined; - if (getBucketOperation(field)) { - newLayer = createNewLayerWithBucketAggregation(layerId, indexPattern, field); + const bucketOperation = getBucketOperation(field); + if (bucketOperation) { + newLayer = createNewLayerWithBucketAggregation(layerId, indexPattern, field, bucketOperation); } else if (indexPattern.timeFieldName && getOperationTypesForField(field).length > 0) { newLayer = createNewLayerWithMetricAggregation(layerId, indexPattern, field); } @@ -312,7 +301,8 @@ function getEmptyLayerSuggestionsForField( function createNewLayerWithBucketAggregation( layerId: string, indexPattern: IndexPattern, - field: IndexPatternField + field: IndexPatternField, + operation: OperationType ): IndexPatternLayer { const countColumn = buildColumn({ op: 'count', @@ -329,7 +319,7 @@ function createNewLayerWithBucketAggregation( // let column know about count column const column = buildColumn({ layerId, - op: getBucketOperation(field), + op: operation, indexPattern, columns: { [col2]: countColumn, @@ -355,15 +345,7 @@ function createNewLayerWithMetricAggregation( ): IndexPatternLayer { const dateField = indexPattern.fields.find(f => f.name === indexPattern.timeFieldName)!; - const operations = getOperationTypesForField(field); - const column = buildColumn({ - op: operations[0], - columns: {}, - suggestedPriority: undefined, - field, - indexPattern, - layerId, - }); + const column = getMetricColumn(indexPattern, layerId, field); const dateColumn = buildColumn({ op: 'date_histogram', @@ -500,12 +482,7 @@ function createChangedNestingSuggestion(state: IndexPatternPrivateState, layerId }); } -function createMetricSuggestion( - indexPattern: IndexPattern, - layerId: string, - state: IndexPatternPrivateState, - field: IndexPatternField -) { +function getMetricColumn(indexPattern: IndexPattern, layerId: string, field: IndexPatternField) { const operationDefinitionsMap = _.indexBy(operationDefinitions, 'type'); const [column] = getOperationTypesForField(field) .map(type => @@ -518,6 +495,16 @@ function createMetricSuggestion( }) ) .filter(op => (op.dataType === 'number' || op.dataType === 'document') && !op.isBucketed); + return column; +} + +function createMetricSuggestion( + indexPattern: IndexPattern, + layerId: string, + state: IndexPatternPrivateState, + field: IndexPatternField +) { + const column = getMetricColumn(indexPattern, layerId, field); if (!column) { return; @@ -572,21 +559,26 @@ function createAlternativeMetricSuggestions( return; } const field = indexPattern.fields.find(({ name }) => column.sourceField === name)!; - const alternativeMetricOperations = getOperationTypesForField(field).filter( - operationType => operationType !== column.operationType - ); + const alternativeMetricOperations = getOperationTypesForField(field) + .map(op => + buildColumn({ + op, + columns: layer.columns, + indexPattern, + layerId, + field, + suggestedPriority: undefined, + }) + ) + .filter( + fullOperation => + fullOperation.operationType !== column.operationType && !fullOperation.isBucketed + ); if (alternativeMetricOperations.length === 0) { return; } const newId = generateId(); - const newColumn = buildColumn({ - op: alternativeMetricOperations[0], - columns: layer.columns, - indexPattern, - layerId, - field, - suggestedPriority: undefined, - }); + const newColumn = alternativeMetricOperations[0]; const updatedLayer = { indexPatternId: indexPattern.id, columns: { [newId]: newColumn }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 7a36d52ad897b7..6161df1167afe0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -42,6 +42,7 @@ export const dateHistogramOperation: OperationDefinition { if ( type === 'date' && diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx index 29e5787fa4f544..7eb10456b2a6e0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx @@ -53,6 +53,7 @@ export const termsOperation: OperationDefinition = { displayName: i18n.translate('xpack.lens.indexPattern.terms', { defaultMessage: 'Top values', }), + priority: 3, // Higher than any metric getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { if ( supportedTypes.has(type) && diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index 111b1040de9891..e5d20839aae3d1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -5,8 +5,7 @@ */ import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColumn } from './index'; -import { AvgIndexPatternColumn, MinIndexPatternColumn } from './definitions/metrics'; -import { CountIndexPatternColumn } from './definitions/count'; +import { AvgIndexPatternColumn } from './definitions/metrics'; import { IndexPatternPrivateState } from '../types'; import { documentField } from '../document_field'; @@ -197,33 +196,21 @@ describe('getOperationTypesForField', () => { expect(column.operationType).toEqual('avg'); expect(column.sourceField).toEqual(field.name); }); - - it('should pick a suitable field operation if none is passed in', () => { - const field = expectedIndexPatterns[1].fields[1]; - const column = buildColumn({ - layerId: 'first', - indexPattern: expectedIndexPatterns[1], - columns: state.layers.first.columns, - suggestedPriority: 0, - field, - }) as MinIndexPatternColumn; - expect(column.operationType).toEqual('avg'); - expect(column.sourceField).toEqual(field.name); - }); - - it('should pick a suitable document operation if none is passed in', () => { - const column = buildColumn({ - layerId: 'first', - indexPattern: expectedIndexPatterns[1], - columns: state.layers.first.columns, - suggestedPriority: 0, - field: documentField, - }) as CountIndexPatternColumn; - expect(column.operationType).toEqual('count'); - }); }); describe('getAvailableOperationsByMetaData', () => { + it('should put the average operation first', () => { + const numberOperation = getAvailableOperationsByMetadata(expectedIndexPatterns[1]).find( + ({ operationMetaData }) => + !operationMetaData.isBucketed && operationMetaData.dataType === 'number' + )!; + expect(numberOperation.operations[0]).toEqual( + expect.objectContaining({ + operationType: 'avg', + }) + ); + }); + it('should list out all field-operation tuples for different operation meta data', () => { expect(getAvailableOperationsByMetadata(expectedIndexPatterns[1])).toMatchInlineSnapshot(` Array [ @@ -278,17 +265,22 @@ describe('getOperationTypesForField', () => { "operations": Array [ Object { "field": "bytes", - "operationType": "min", + "operationType": "avg", "type": "field", }, Object { "field": "bytes", - "operationType": "max", + "operationType": "sum", "type": "field", }, Object { "field": "bytes", - "operationType": "avg", + "operationType": "min", + "type": "field", + }, + Object { + "field": "bytes", + "operationType": "max", "type": "field", }, Object { @@ -306,11 +298,6 @@ describe('getOperationTypesForField', () => { "operationType": "cardinality", "type": "field", }, - Object { - "field": "bytes", - "operationType": "sum", - "type": "field", - }, ], }, ] diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts index ce8ea55c445dce..dbcd4eac7fd591 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts @@ -52,20 +52,22 @@ export function getOperationDisplay() { return display; } +function getSortScoreByPriority(a: GenericOperationDefinition, b: GenericOperationDefinition) { + return (b.priority || Number.NEGATIVE_INFINITY) - (a.priority || Number.NEGATIVE_INFINITY); +} + /** * Returns all `OperationType`s that can build a column using `buildColumn` based on the * passed in field. */ -export function getOperationTypesForField(field: IndexPatternField) { +export function getOperationTypesForField(field: IndexPatternField): OperationType[] { return operationDefinitions .filter( operationDefinition => 'getPossibleOperationForField' in operationDefinition && operationDefinition.getPossibleOperationForField(field) ) - .sort( - (a, b) => (b.priority || Number.NEGATIVE_INFINITY) - (a.priority || Number.NEGATIVE_INFINITY) - ) + .sort(getSortScoreByPriority) .map(({ type }) => type); } @@ -131,7 +133,7 @@ export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) { } }; - operationDefinitions.forEach(operationDefinition => { + operationDefinitions.sort(getSortScoreByPriority).forEach(operationDefinition => { indexPattern.fields.forEach(field => { addToMap( { @@ -156,13 +158,6 @@ function getPossibleOperationForField( : undefined; } -function getDefinition(findFunction: (definition: GenericOperationDefinition) => boolean) { - const candidates = operationDefinitions.filter(findFunction); - return candidates.reduce((a, b) => - (a.priority || Number.NEGATIVE_INFINITY) > (b.priority || Number.NEGATIVE_INFINITY) ? a : b - ); -} - /** * Changes the field of the passed in colum. To do so, this method uses the `onFieldChange` function of * the operation definition of the column. Returns a new column object with the field changed. @@ -204,7 +199,7 @@ export function buildColumn({ suggestedPriority, previousColumn, }: { - op?: OperationType; + op: OperationType; columns: Partial>; suggestedPriority: DimensionPriority | undefined; layerId: string; @@ -212,15 +207,7 @@ export function buildColumn({ field: IndexPatternField; previousColumn?: IndexPatternColumn; }): IndexPatternColumn { - let operationDefinition: GenericOperationDefinition | undefined; - - if (op) { - operationDefinition = operationDefinitionMap[op]; - } else if (field) { - operationDefinition = getDefinition(definition => - Boolean(getPossibleOperationForField(definition, field)) - ); - } + const operationDefinition = operationDefinitionMap[op]; if (!operationDefinition) { throw new Error('No suitable operation found for given parameters');