diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md
index 4b3c915b49c2d..440fd25993d64 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md
@@ -46,6 +46,7 @@ search: {
boundLabel: string;
intervalLabel: string;
})[];
+ getNumberHistogramIntervalByDatatableColumn: (column: import("../../expressions").DatatableColumn) => number | undefined;
};
getRequestInspectorStats: typeof getRequestInspectorStats;
getResponseInspectorStats: typeof getResponseInspectorStats;
diff --git a/src/plugins/data/common/search/aggs/buckets/histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/histogram.test.ts
index bddc7060af440..23693eaf5fca5 100644
--- a/src/plugins/data/common/search/aggs/buckets/histogram.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/histogram.test.ts
@@ -12,6 +12,7 @@ import { AggTypesDependencies } from '../agg_types';
import { BUCKET_TYPES } from './bucket_agg_types';
import { IBucketHistogramAggConfig, getHistogramBucketAgg, AutoBounds } from './histogram';
import { BucketAggType } from './bucket_agg_type';
+import { SerializableState } from 'src/plugins/expressions/common';
describe('Histogram Agg', () => {
let aggTypesDependencies: AggTypesDependencies;
@@ -230,6 +231,27 @@ describe('Histogram Agg', () => {
expect(params.interval).toBeNaN();
});
+ test('will serialize the auto interval along with the actually chosen interval and deserialize correctly', () => {
+ const aggConfigs = getAggConfigs({
+ interval: 'auto',
+ field: {
+ name: 'field',
+ },
+ });
+ (aggConfigs.aggs[0] as IBucketHistogramAggConfig).setAutoBounds({ min: 0, max: 1000 });
+ const serializedAgg = aggConfigs.aggs[0].serialize();
+ const serializedIntervalParam = (serializedAgg.params as SerializableState).used_interval;
+ expect(serializedIntervalParam).toBe(500);
+ const freshHistogramAggConfig = getAggConfigs({
+ interval: 100,
+ field: {
+ name: 'field',
+ },
+ }).aggs[0];
+ freshHistogramAggConfig.setParams(serializedAgg.params);
+ expect(freshHistogramAggConfig.getParam('interval')).toEqual('auto');
+ });
+
describe('interval scaling', () => {
const getInterval = (
maxBars: number,
diff --git a/src/plugins/data/common/search/aggs/buckets/histogram.ts b/src/plugins/data/common/search/aggs/buckets/histogram.ts
index 5d6d7d509f08e..e04ebfe494ba9 100644
--- a/src/plugins/data/common/search/aggs/buckets/histogram.ts
+++ b/src/plugins/data/common/search/aggs/buckets/histogram.ts
@@ -8,6 +8,7 @@
import { get } from 'lodash';
import { i18n } from '@kbn/i18n';
+import { IUiSettingsClient } from 'kibana/public';
import { KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common';
import { AggTypesDependencies } from '../agg_types';
@@ -39,6 +40,7 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig {
export interface AggParamsHistogram extends BaseAggParams {
field: string;
interval: number | string;
+ used_interval?: number | string;
maxBars?: number;
intervalBase?: number;
min_doc_count?: boolean;
@@ -141,17 +143,22 @@ export const getHistogramBucketAgg = ({
});
},
write(aggConfig, output) {
- const values = aggConfig.getAutoBounds();
-
- output.params.interval = calculateHistogramInterval({
- values,
- interval: aggConfig.params.interval,
- maxBucketsUiSettings: getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS),
- maxBucketsUserInput: aggConfig.params.maxBars,
- intervalBase: aggConfig.params.intervalBase,
- esTypes: aggConfig.params.field?.spec?.esTypes || [],
- });
+ output.params.interval = calculateInterval(aggConfig, getConfig);
+ },
+ },
+ {
+ name: 'used_interval',
+ default: autoInterval,
+ shouldShow() {
+ return false;
},
+ write: () => {},
+ serialize(val, aggConfig) {
+ if (!aggConfig) return undefined;
+ // store actually used auto interval in serialized agg config to be able to read it from the result data table meta information
+ return calculateInterval(aggConfig, getConfig);
+ },
+ toExpressionAst: () => undefined,
},
{
name: 'maxBars',
@@ -193,3 +200,18 @@ export const getHistogramBucketAgg = ({
},
],
});
+
+function calculateInterval(
+ aggConfig: IBucketHistogramAggConfig,
+ getConfig: IUiSettingsClient['get']
+): any {
+ const values = aggConfig.getAutoBounds();
+ return calculateHistogramInterval({
+ values,
+ interval: aggConfig.params.interval,
+ maxBucketsUiSettings: getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS),
+ maxBucketsUserInput: aggConfig.params.maxBars,
+ intervalBase: aggConfig.params.intervalBase,
+ esTypes: aggConfig.params.field?.spec?.esTypes || [],
+ });
+}
diff --git a/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.test.ts b/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.test.ts
new file mode 100644
index 0000000000000..9b08426b551aa
--- /dev/null
+++ b/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.test.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { getNumberHistogramIntervalByDatatableColumn } from '.';
+import { BUCKET_TYPES } from '../buckets';
+
+describe('getNumberHistogramIntervalByDatatableColumn', () => {
+ it('returns nothing on column from other data source', () => {
+ expect(
+ getNumberHistogramIntervalByDatatableColumn({
+ id: 'test',
+ name: 'test',
+ meta: {
+ type: 'date',
+ source: 'essql',
+ },
+ })
+ ).toEqual(undefined);
+ });
+
+ it('returns nothing on non histogram column', () => {
+ expect(
+ getNumberHistogramIntervalByDatatableColumn({
+ id: 'test',
+ name: 'test',
+ meta: {
+ type: 'date',
+ source: 'esaggs',
+ sourceParams: {
+ type: BUCKET_TYPES.TERMS,
+ },
+ },
+ })
+ ).toEqual(undefined);
+ });
+
+ it('returns interval on resolved auto interval', () => {
+ expect(
+ getNumberHistogramIntervalByDatatableColumn({
+ id: 'test',
+ name: 'test',
+ meta: {
+ type: 'date',
+ source: 'esaggs',
+ sourceParams: {
+ type: BUCKET_TYPES.HISTOGRAM,
+ params: {
+ interval: 'auto',
+ used_interval: 20,
+ },
+ },
+ },
+ })
+ ).toEqual(20);
+ });
+
+ it('returns interval on fixed interval', () => {
+ expect(
+ getNumberHistogramIntervalByDatatableColumn({
+ id: 'test',
+ name: 'test',
+ meta: {
+ type: 'date',
+ source: 'esaggs',
+ sourceParams: {
+ type: BUCKET_TYPES.HISTOGRAM,
+ params: {
+ interval: 7,
+ used_interval: 7,
+ },
+ },
+ },
+ })
+ ).toEqual(7);
+ });
+
+ it('returns undefined if information is not available', () => {
+ expect(
+ getNumberHistogramIntervalByDatatableColumn({
+ id: 'test',
+ name: 'test',
+ meta: {
+ type: 'date',
+ source: 'esaggs',
+ sourceParams: {
+ type: BUCKET_TYPES.HISTOGRAM,
+ params: {},
+ },
+ },
+ })
+ ).toEqual(undefined);
+ });
+});
diff --git a/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.ts b/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.ts
new file mode 100644
index 0000000000000..e1c0cf2d69c60
--- /dev/null
+++ b/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { DatatableColumn } from 'src/plugins/expressions/common';
+import type { AggParamsHistogram } from '../buckets';
+import { BUCKET_TYPES } from '../buckets/bucket_agg_types';
+
+/**
+ * Helper function returning the used interval for data table column created by the histogramm agg type.
+ * "auto" will get expanded to the actually used interval.
+ * If the column is not a column created by a histogram aggregation of the esaggs data source,
+ * this function will return undefined.
+ */
+export const getNumberHistogramIntervalByDatatableColumn = (column: DatatableColumn) => {
+ if (column.meta.source !== 'esaggs') return;
+ if (column.meta.sourceParams?.type !== BUCKET_TYPES.HISTOGRAM) return;
+ const params = (column.meta.sourceParams.params as unknown) as AggParamsHistogram;
+
+ if (!params.used_interval || typeof params.used_interval === 'string') {
+ return undefined;
+ }
+ return params.used_interval;
+};
diff --git a/src/plugins/data/common/search/aggs/utils/index.ts b/src/plugins/data/common/search/aggs/utils/index.ts
index 40451a0f66e0c..f90e8f88546f4 100644
--- a/src/plugins/data/common/search/aggs/utils/index.ts
+++ b/src/plugins/data/common/search/aggs/utils/index.ts
@@ -7,6 +7,7 @@
*/
export * from './calculate_auto_time_expression';
+export { getNumberHistogramIntervalByDatatableColumn } from './get_number_histogram_interval';
export * from './date_interval_utils';
export * from './get_format_with_aggs';
export * from './ipv4_address';
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index df799ede08a31..00bf0385487d8 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -308,6 +308,7 @@ import {
parseInterval,
toAbsoluteDates,
boundsDescendingRaw,
+ getNumberHistogramIntervalByDatatableColumn,
// expressions utils
getRequestInspectorStats,
getResponseInspectorStats,
@@ -417,6 +418,7 @@ export const search = {
termsAggFilter,
toAbsoluteDates,
boundsDescendingRaw,
+ getNumberHistogramIntervalByDatatableColumn,
},
getRequestInspectorStats,
getResponseInspectorStats,
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 745f4a7d29d22..923e76c9adf54 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -2238,6 +2238,7 @@ export const search: {
boundLabel: string;
intervalLabel: string;
})[];
+ getNumberHistogramIntervalByDatatableColumn: (column: import("../../expressions").DatatableColumn) => number | undefined;
};
getRequestInspectorStats: typeof getRequestInspectorStats;
getResponseInspectorStats: typeof getResponseInspectorStats;
@@ -2647,21 +2648,21 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/search/session/session_service.ts:42:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts
diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap
index a2047b7bae669..9a32f1c331152 100644
--- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap
+++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap
@@ -26,6 +26,13 @@ exports[`xy_expression XYChart component it renders area 1`] = `
"headerFormatter": [Function],
}
}
+ xDomain={
+ Object {
+ "max": undefined,
+ "min": undefined,
+ "minInterval": 50,
+ }
+ }
/>
{
}}
/>
);
- expect(component.find(Settings).prop('xDomain')).toBeUndefined();
+ const xDomain = component.find(Settings).prop('xDomain');
+ expect(xDomain).toEqual(
+ expect.objectContaining({
+ min: undefined,
+ max: undefined,
+ })
+ );
+ });
+
+ test('it uses min interval if passed in', () => {
+ const { data, args } = sampleArgs();
+
+ const component = shallow(
+
+ );
+ expect(component.find(Settings).prop('xDomain')).toEqual({ minInterval: 101 });
});
test('it renders bar', () => {
@@ -1881,6 +1904,24 @@ describe('xy_expression', () => {
expect(result).toEqual(5 * 60 * 1000);
});
+ it('should return interval of number histogram if available on first x axis columns', async () => {
+ xyProps.args.layers[0].xScaleType = 'linear';
+ xyProps.data.tables.first.columns[2].meta = {
+ source: 'esaggs',
+ type: 'number',
+ field: 'someField',
+ sourceParams: {
+ type: 'histogram',
+ params: {
+ interval: 'auto',
+ used_interval: 5,
+ },
+ },
+ };
+ const result = await calculateMinInterval(xyProps, jest.fn().mockResolvedValue(undefined));
+ expect(result).toEqual(5);
+ });
+
it('should return undefined if data table is empty', async () => {
xyProps.data.tables.first.rows = [];
const result = await calculateMinInterval(
diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx
index 7f6414b40cb90..eda08715b394e 100644
--- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx
@@ -199,13 +199,20 @@ export async function calculateMinInterval(
const filteredLayers = getFilteredLayers(layers, data);
if (filteredLayers.length === 0) return;
const isTimeViz = data.dateRange && filteredLayers.every((l) => l.xScaleType === 'time');
-
- if (!isTimeViz) return;
- const dateColumn = data.tables[filteredLayers[0].layerId].columns.find(
+ const xColumn = data.tables[filteredLayers[0].layerId].columns.find(
(column) => column.id === filteredLayers[0].xAccessor
);
- if (!dateColumn) return;
- const dateMetaData = await getIntervalByColumn(dateColumn);
+
+ if (!xColumn) return;
+ if (!isTimeViz) {
+ const histogramInterval = search.aggs.getNumberHistogramIntervalByDatatableColumn(xColumn);
+ if (typeof histogramInterval === 'number') {
+ return histogramInterval;
+ } else {
+ return undefined;
+ }
+ }
+ const dateMetaData = await getIntervalByColumn(xColumn);
if (!dateMetaData) return;
const intervalDuration = search.aggs.parseInterval(dateMetaData.interval);
if (!intervalDuration) return;
@@ -381,13 +388,11 @@ export function XYChart({
const isTimeViz = data.dateRange && filteredLayers.every((l) => l.xScaleType === 'time');
const isHistogramViz = filteredLayers.every((l) => l.isHistogram);
- const xDomain = isTimeViz
- ? {
- min: data.dateRange?.fromDate.getTime(),
- max: data.dateRange?.toDate.getTime(),
- minInterval,
- }
- : undefined;
+ const xDomain = {
+ min: isTimeViz ? data.dateRange?.fromDate.getTime() : undefined,
+ max: isTimeViz ? data.dateRange?.toDate.getTime() : undefined,
+ minInterval,
+ };
const getYAxesTitles = (
axisSeries: Array<{ layer: string; accessor: string }>,