Add cardinality operation to Lens. Still need to figure out how to pass precision_threshold through to esaggs, so for now that's not configurable.
This commit is contained in:
parent
f582da0f0a
commit
ccba346cd7
|
@ -822,7 +822,7 @@ describe('IndexPatternDimensionPanel', () => {
|
|||
.find(EuiSideNav)
|
||||
.prop('items')[0]
|
||||
.items.map(({ name }) => name)
|
||||
).toEqual(['Average', 'Count', 'Filter Ratio', 'Maximum', 'Minimum', 'Sum']);
|
||||
).toEqual(['Unique count', 'Average', 'Count', 'Filter Ratio', 'Maximum', 'Minimum', 'Sum']);
|
||||
});
|
||||
|
||||
it('should add a column on selection of a field', () => {
|
||||
|
@ -956,6 +956,12 @@ describe('IndexPatternDimensionPanel', () => {
|
|||
searchable: true,
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'mystring',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -1026,7 +1032,7 @@ describe('IndexPatternDimensionPanel', () => {
|
|||
...dragDropContext,
|
||||
dragging: {
|
||||
indexPatternId: 'foo',
|
||||
field: { type: 'number', name: 'bar', aggregatable: true },
|
||||
field: { type: 'string', name: 'mystring', aggregatable: true },
|
||||
},
|
||||
}}
|
||||
state={dragDropState()}
|
||||
|
@ -1141,6 +1147,54 @@ describe('IndexPatternDimensionPanel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('selects the specific operation that was valid on drop', () => {
|
||||
const dragging = {
|
||||
field: { type: 'string', name: 'mystring', aggregatable: true },
|
||||
indexPatternId: 'foo',
|
||||
};
|
||||
const testState = dragDropState();
|
||||
wrapper = shallow(
|
||||
<IndexPatternDimensionPanel
|
||||
{...defaultProps}
|
||||
dragDropContext={{
|
||||
...dragDropContext,
|
||||
dragging,
|
||||
}}
|
||||
state={testState}
|
||||
columnId={'col2'}
|
||||
filterOperations={op => op.isBucketed}
|
||||
layerId="myLayer"
|
||||
/>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
const onDrop = wrapper
|
||||
.find('[data-test-subj="indexPattern-dropTarget"]')
|
||||
.first()
|
||||
.prop('onDrop') as DropHandler;
|
||||
|
||||
onDrop(dragging);
|
||||
});
|
||||
|
||||
expect(setState).toBeCalledTimes(1);
|
||||
expect(setState).toHaveBeenCalledWith({
|
||||
...testState,
|
||||
layers: {
|
||||
myLayer: {
|
||||
...testState.layers.myLayer,
|
||||
columnOrder: ['col1', 'col2'],
|
||||
columns: {
|
||||
...testState.layers.myLayer.columns,
|
||||
col2: expect.objectContaining({
|
||||
dataType: 'string',
|
||||
sourceField: 'mystring',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('updates a column when a field is dropped', () => {
|
||||
const dragging = {
|
||||
field: { type: 'number', name: 'bar', aggregatable: true },
|
||||
|
|
|
@ -134,6 +134,7 @@ export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPan
|
|||
const newColumn = hasFieldChanged
|
||||
? changeField(selectedColumn, currentIndexPattern, droppedItem.field)
|
||||
: buildColumn({
|
||||
op: operationsForNewField ? operationsForNewField[0] : undefined,
|
||||
columns: props.state.layers[props.layerId].columns,
|
||||
indexPattern: currentIndexPattern,
|
||||
layerId,
|
||||
|
|
|
@ -288,6 +288,11 @@ export function PopoverEditor(props: PopoverEditorProps) {
|
|||
column = changeField(selectedColumn, currentIndexPattern, fieldMap[choice.field]);
|
||||
} else {
|
||||
// Otherwise we'll use the buildColumn method to calculate a new column
|
||||
const compatibleOperations =
|
||||
('field' in choice &&
|
||||
operationFieldSupportMatrix.operationByField[choice.field]) ||
|
||||
[];
|
||||
|
||||
column = buildColumn({
|
||||
columns: props.state.layers[props.layerId].columns,
|
||||
field: 'field' in choice ? fieldMap[choice.field] : undefined,
|
||||
|
@ -296,7 +301,8 @@ export function PopoverEditor(props: PopoverEditorProps) {
|
|||
suggestedPriority: props.suggestedPriority,
|
||||
op:
|
||||
incompatibleSelectedOperationType ||
|
||||
('field' in choice ? choice.operationType : undefined),
|
||||
('field' in choice ? choice.operationType : undefined) ||
|
||||
compatibleOperations[0],
|
||||
asDocumentOperation: choice.type === 'document',
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { OperationDefinition } from '.';
|
||||
import { FieldBasedIndexPatternColumn } from './column_types';
|
||||
|
||||
const supportedTypes = new Set(['string', 'boolean', 'number', 'ip', 'date']);
|
||||
|
||||
const SCALE = 'ratio';
|
||||
const OPERATION_TYPE = 'cardinality';
|
||||
const IS_BUCKETED = false;
|
||||
|
||||
function ofName(name: string) {
|
||||
return i18n.translate('xpack.lens.indexPattern.cardinalityOf', {
|
||||
defaultMessage: 'Unique count of {name}',
|
||||
values: { name },
|
||||
});
|
||||
}
|
||||
|
||||
export interface CardinalityIndexPatternColumn extends FieldBasedIndexPatternColumn {
|
||||
operationType: 'cardinality';
|
||||
}
|
||||
|
||||
export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternColumn> = {
|
||||
type: OPERATION_TYPE,
|
||||
displayName: i18n.translate('xpack.lens.indexPattern.cardinality', {
|
||||
defaultMessage: 'Unique count',
|
||||
}),
|
||||
getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => {
|
||||
if (
|
||||
supportedTypes.has(type) &&
|
||||
aggregatable &&
|
||||
(!aggregationRestrictions || aggregationRestrictions.cardinality)
|
||||
) {
|
||||
return { dataType: 'number', isBucketed: IS_BUCKETED, scale: SCALE };
|
||||
}
|
||||
},
|
||||
isTransferable: (column, newIndexPattern) => {
|
||||
const newField = newIndexPattern.fields.find(field => field.name === column.sourceField);
|
||||
|
||||
return Boolean(
|
||||
newField &&
|
||||
supportedTypes.has(newField.type) &&
|
||||
newField.aggregatable &&
|
||||
(!newField.aggregationRestrictions || newField.aggregationRestrictions.cardinality)
|
||||
);
|
||||
},
|
||||
buildColumn({ suggestedPriority, field }) {
|
||||
return {
|
||||
label: ofName(field.name),
|
||||
dataType: 'number',
|
||||
operationType: OPERATION_TYPE,
|
||||
scale: SCALE,
|
||||
suggestedPriority,
|
||||
sourceField: field.name,
|
||||
isBucketed: IS_BUCKETED,
|
||||
};
|
||||
},
|
||||
toEsAggsConfig: (column, columnId) => ({
|
||||
id: columnId,
|
||||
enabled: true,
|
||||
type: OPERATION_TYPE,
|
||||
schema: 'metric',
|
||||
params: {
|
||||
field: column.sourceField,
|
||||
},
|
||||
}),
|
||||
onFieldChange: (oldColumn, indexPattern, field) => {
|
||||
return {
|
||||
...oldColumn,
|
||||
label: ofName(field.name),
|
||||
sourceField: field.name,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -11,6 +11,7 @@ import {
|
|||
HttpServiceBase,
|
||||
} from 'src/core/public';
|
||||
import { termsOperation } from './terms';
|
||||
import { cardinalityOperation } from './cardinality';
|
||||
import { minOperation, averageOperation, sumOperation, maxOperation } from './metrics';
|
||||
import { dateHistogramOperation } from './date_histogram';
|
||||
import { countOperation } from './count';
|
||||
|
@ -28,6 +29,7 @@ const internalOperationDefinitions = [
|
|||
minOperation,
|
||||
maxOperation,
|
||||
averageOperation,
|
||||
cardinalityOperation,
|
||||
sumOperation,
|
||||
countOperation,
|
||||
filterRatioOperation,
|
||||
|
|
|
@ -274,6 +274,21 @@ describe('getOperationTypesForField', () => {
|
|||
"operationType": "avg",
|
||||
"type": "field",
|
||||
},
|
||||
Object {
|
||||
"field": "timestamp",
|
||||
"operationType": "cardinality",
|
||||
"type": "field",
|
||||
},
|
||||
Object {
|
||||
"field": "bytes",
|
||||
"operationType": "cardinality",
|
||||
"type": "field",
|
||||
},
|
||||
Object {
|
||||
"field": "source",
|
||||
"operationType": "cardinality",
|
||||
"type": "field",
|
||||
},
|
||||
Object {
|
||||
"field": "bytes",
|
||||
"operationType": "sum",
|
||||
|
|
Loading…
Reference in a new issue