Skip to content
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][Visualize] Enables histogram for histogram fields #131379

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
6 changes: 5 additions & 1 deletion src/plugins/data/common/search/aggs/buckets/histogram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ export const getHistogramBucketAgg = ({
{
name: 'field',
type: 'field',
filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.NUMBER_RANGE],
filterFieldTypes: [
KBN_FIELD_TYPES.NUMBER,
KBN_FIELD_TYPES.NUMBER_RANGE,
KBN_FIELD_TYPES.HISTOGRAM,
],
},
{
/*
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/data/common/search/aggs/buckets/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const getRangeBucketAgg = ({ getFieldFormatsStart }: RangeBucketAggDepend
name: 'field',
type: 'field',
// number_range is not supported by Elasticsearch
filterFieldTypes: [KBN_FIELD_TYPES.NUMBER],
filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM],
},
{
name: 'ranges',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function NumberIntervalParamEditor({
setValue,
}: AggParamEditorProps<string | undefined>) {
const field = agg.getField();
const fieldSupportsAuto = !field || field.type === 'number';
const fieldSupportsAuto = !field || field.type === 'number' || field.type === 'histogram';
const isAutoChecked = fieldSupportsAuto && isAutoInterval(value);
const base: number = get(editorConfig, 'interval.base') as number;
const min = base || 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ export function DimensionEditor(props: DimensionEditorProps) {
definition.getDisabledStatus(
state.indexPatterns[state.currentIndexPatternId],
state.layers[layerId],
layerType
layerType,
columnId
),
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
*/

import { createMockedIndexPattern } from '../../mocks';
import { getInvalidFieldMessage } from './helpers';
import { getFieldByNameFactory } from '../../pure_helpers';
import type { IndexPatternLayer } from '../../types';
import { getInvalidFieldMessage, getErrorsForHistogramField } from './helpers';
import type { TermsIndexPatternColumn } from './terms';

describe('helpers', () => {
Expand Down Expand Up @@ -127,4 +129,134 @@ describe('helpers', () => {
expect(messages).toBeUndefined();
});
});

describe('getErrorsForHistogramField', () => {
const layer = {
columns: {
'731ee6a5-da3d-4b0f-8f37-ffeb539a7980': {
label: 'Count of records',
dataType: 'number',
operationType: 'count',
isBucketed: false,
scale: 'ratio',
sourceField: '___records___',
params: {
emptyAsNull: true,
},
},
'd682b1d9-ce53-4443-a1e6-959197a314a6': {
label: 'my_histogram',
dataType: 'number',
operationType: 'range',
sourceField: 'my_histogram',
isBucketed: true,
scale: 'interval',
params: {
includeEmptyRows: true,
type: 'histogram',
ranges: [
{
from: 0,
to: 1000,
label: '',
},
],
maxBars: 'auto',
},
},
},
columnOrder: ['d682b1d9-ce53-4443-a1e6-959197a314a6', '731ee6a5-da3d-4b0f-8f37-ffeb539a7980'],
incompleteColumns: {},
indexPatternId: '1',
} as IndexPatternLayer;
const indexPattern = createMockedIndexPattern();
const updatedIndexPattern = {
...indexPattern,
fields: [
...indexPattern.fields,
{
name: 'my_histogram',
displayName: 'my_histogram',
type: 'histogram',
aggregatable: true,
searchable: true,
},
],
getFieldByName: getFieldByNameFactory([
{
name: 'my_histogram',
type: 'histogram',
displayName: 'my_histogram',
searchable: true,
aggregatable: true,
},
]),
};

it('return no error if a count aggregation is given', () => {
const messages = getErrorsForHistogramField(
layer,
'd682b1d9-ce53-4443-a1e6-959197a314a6',
updatedIndexPattern
);
expect(messages).toBeUndefined();
});

it('returns an error if a metric is non a count aggregation', () => {
layer.columns['731ee6a5-da3d-4b0f-8f37-ffeb539a7980'].operationType = 'average';
const messages = getErrorsForHistogramField(
layer,
'd682b1d9-ce53-4443-a1e6-959197a314a6',
updatedIndexPattern
);
expect(messages).toHaveLength(1);
expect(messages![0]).toEqual(
'Histogram fields can only be used with a count aggregation. Please remove the histogram field or change the metric to a count or remove the breakdown dimension.'
);
});

it('returns an error if a metric is a count aggregation and a breakdown is also given', () => {
layer.columns['731ee6a5-da3d-4b0f-8f37-ffeb539a7980'].operationType = 'count';
const newLayer = {
...layer,
columns: {
...layer.columns,
'ef5fa77a-b1f7-405e-9551-6b533aafa114': {
label: 'bytes',
dataType: 'number',
operationType: 'range',
sourceField: 'bytes',
isBucketed: true,
scale: 'interval',
params: {
includeEmptyRows: true,
type: 'histogram',
ranges: [
{
from: 0,
to: 1000,
label: '',
},
],
maxBars: 'auto',
},
},
},
columnOrder: [
'd682b1d9-ce53-4443-a1e6-959197a314a6',
'731ee6a5-da3d-4b0f-8f37-ffeb539a7980',
'ef5fa77a-b1f7-405e-9551-6b533aafa114',
],
} as IndexPatternLayer;
const messages = getErrorsForHistogramField(
newLayer,
'd682b1d9-ce53-4443-a1e6-959197a314a6',
updatedIndexPattern
);
expect(messages).toHaveLength(1);
expect(messages![0]).toEqual(
'Histogram fields can only be used with a count aggregation. Please remove the histogram field or change the metric to a count or remove the breakdown dimension.'
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
FormattedIndexPatternColumn,
ReferenceBasedIndexPatternColumn,
} from './column_types';
import { IndexPattern, IndexPatternField } from '../../types';
import { IndexPattern, IndexPatternField, IndexPatternLayer } from '../../types';
import { hasField } from '../../pure_utils';

export function getInvalidFieldMessage(
Expand Down Expand Up @@ -87,6 +87,44 @@ export function getInvalidFieldMessage(
return undefined;
}

export function getErrorsForHistogramField(
layer: IndexPatternLayer,
columnId: string,
indexPattern: IndexPattern
) {
// check if has field of histogram type
const column = layer.columns[columnId];
if (!column) return;
const { operationType } = column;
const operationDefinition = operationType ? operationDefinitionMap[operationType] : undefined;
const fieldNames =
hasField(column) && operationDefinition
? operationDefinition?.getCurrentFields?.(column) ?? [column.sourceField]
: [];
const fields = fieldNames.map((fieldName) => indexPattern.getFieldByName(fieldName));
const filteredFields = fields.filter(Boolean) as IndexPatternField[];
const hasHistogramField = filteredFields.some((field) => field.type === 'histogram');
// check if metric is not count
const metric = layer.columnOrder.filter((colId) => !layer.columns[colId].isBucketed);
const hasCountAggregation = metric.some(
(colId) => layer.columns[colId].operationType === 'count'
);

// check if other bucket is present
const otherBucketsExist = layer.columnOrder.some(
(colId) => layer.columns[colId].isBucketed && colId !== columnId
);

if (hasHistogramField && (!hasCountAggregation || otherBucketsExist)) {
return [
i18n.translate('xpack.lens.indexPattern.histogramFieldsWithNoCount', {
defaultMessage:
'Histogram fields can only be used with a count aggregation. Please remove the histogram field or change the metric to a count or remove the breakdown dimension.',
}),
];
}
}

export function combineErrorMessages(
errorMessages: Array<string[] | undefined>
): string[] | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ interface BaseOperationDefinitionProps<C extends BaseIndexPatternColumn, P = {}>
getDisabledStatus?: (
indexPattern: IndexPattern,
layer: IndexPatternLayer,
layerType?: LayerType
layerType?: LayerType,
columnId?: string
) => string | undefined;
/**
* Validate that the operation has the right preconditions in the state. For example:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,22 @@ describe('ranges', () => {
});
});

it('should return operation with the right type for histogram', () => {
expect(
rangeOperation.getPossibleOperationForField({
aggregatable: true,
searchable: true,
name: 'test',
displayName: 'test',
type: 'histogram',
})
).toEqual({
dataType: 'number',
isBucketed: true,
scale: 'interval',
});
});

it('should not return operation if field type is not number', () => {
expect(
rangeOperation.getPossibleOperationForField({
Expand Down Expand Up @@ -931,4 +947,73 @@ describe('ranges', () => {
});
});
});

describe('getErrorMessage', () => {
it('should return no error for valid values', () => {
expect(
rangeOperation.getErrorMessage!(layer, 'col1', defaultOptions.indexPattern)
).toBeUndefined();
});

it('should return error for histogram with no count aggregation values', () => {
const updatedIndexPattern = {
...defaultOptions.indexPattern,
fields: [
{
name: sourceField,
type: 'number',
displayName: sourceField,
searchable: true,
aggregatable: true,
},
{
name: 'my_histogram',
displayName: 'my_histogram',
type: 'histogram',
aggregatable: true,
searchable: true,
},
],
getFieldByName: getFieldByNameFactory([
{
name: 'my_histogram',
type: 'histogram',
displayName: 'my_histogram',
searchable: true,
aggregatable: true,
},
]),
};
const updatedLayer = {
...layer,
columns: {
col1: {
label: 'my_histogram',
dataType: 'number',
operationType: 'range',
scale: 'interval',
isBucketed: true,
sourceField: 'my_histogram',
params: {
type: MODES.Histogram,
ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }],
maxBars: 'auto',
},
} as RangeIndexPatternColumn,
col2: {
label: 'Average',
dataType: 'number',
isBucketed: false,
sourceField,
operationType: 'average',
},
},
} as IndexPatternLayer;
expect(rangeOperation.getErrorMessage!(updatedLayer, 'col1', updatedIndexPattern)).toEqual(
expect.arrayContaining([
expect.stringMatching('Histogram fields can only be used with a count aggregation'),
])
);
});
});
});
Loading