[Data] Move the rest of the datatable utility functions to the related service (#134562)

* Move date histogram column meta utility function to the datatable utilities service
* Move number histogram interval utility function to the datatable utilities service
* Move precision error check to the datatable utility service
This commit is contained in:
Michael Dokolin 2022-06-20 10:25:37 +02:00 committed by GitHub
parent 161e9df8c3
commit f13d321c9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 453 additions and 293 deletions

View file

@ -8,6 +8,7 @@
import type { PaletteRegistry } from '@kbn/coloring';
import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import type { IFieldFormat, SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
import type { RangeSelectContext, ValueClickContext } from '@kbn/embeddable-plugin/public';
import type { PersistedState } from '@kbn/visualizations-plugin/public';
@ -29,6 +30,7 @@ export type HeatmapRenderProps = HeatmapExpressionProps & {
timeZone?: string;
formatFactory: FormatFactory;
chartsThemeService: ChartsPluginSetup['theme'];
datatableUtilities: DatatableUtilitiesService;
onClickValue: (data: FilterEvent['data']) => void;
onSelectRange: (data: BrushEvent['data']) => void;
paletteService: PaletteRegistry;

View file

@ -10,6 +10,7 @@ import React from 'react';
import { Settings, TooltipType, Heatmap } from '@elastic/charts';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { EmptyPlaceholder } from '@kbn/charts-plugin/public';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
import type { Datatable } from '@kbn/expressions-plugin/public';
import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers';
@ -110,6 +111,7 @@ describe('HeatmapComponent', function () {
uiState,
onClickValue: jest.fn(),
onSelectRange: jest.fn(),
datatableUtilities: createDatatableUtilitiesMock(),
paletteService: palettesRegistry,
formatFactory: formatService.deserialize,
interactive: true,

View file

@ -131,6 +131,7 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
timeZone,
formatFactory,
chartsThemeService,
datatableUtilities,
onClickValue,
onSelectRange,
paletteService,
@ -315,7 +316,7 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
const xValuesFormatter = formatFactory(xAxisMeta?.params);
const metricFormatter = formatFactory(getFormatByAccessor(args.valueAccessor!, table.columns));
const dateHistogramMeta = xAxisColumn
? search.aggs.getDateHistogramMetaDataByDatatableColumn(xAxisColumn)
? datatableUtilities.getDateHistogramMeta(xAxisColumn)
: undefined;
// Fallback to the ordinal scale type when a single row of data is provided.

View file

@ -18,7 +18,13 @@ import {
FilterEvent,
BrushEvent,
} from '../../common';
import { getFormatService, getPaletteService, getUISettings, getThemeService } from '../services';
import {
getDatatableUtilities,
getFormatService,
getPaletteService,
getUISettings,
getThemeService,
} from '../services';
import { getTimeZone } from '../utils/get_timezone';
interface ExpressioHeatmapRendererDependencies {
@ -55,6 +61,7 @@ export const heatmapRenderer: (
onClickValue={onClickValue}
onSelectRange={onSelectRange}
timeZone={timeZone}
datatableUtilities={getDatatableUtilities()}
formatFactory={getFormatService().deserialize}
chartsThemeService={getThemeService()}
paletteService={getPaletteService()}

View file

@ -7,10 +7,17 @@
*/
import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
import { CoreSetup, CoreStart } from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { heatmapFunction, heatmapLegendConfig, heatmapGridConfig } from '../common';
import { setFormatService, setPaletteService, setUISettings, setThemeService } from './services';
import {
setDatatableUtilities,
setFormatService,
setPaletteService,
setUISettings,
setThemeService,
} from './services';
import { heatmapRenderer } from './expression_renderers';
/** @internal */
@ -21,6 +28,7 @@ export interface ExpressionHeatmapPluginSetup {
/** @internal */
export interface ExpressionHeatmapPluginStart {
data: DataPublicPluginStart;
fieldFormats: FieldFormatsStart;
}
@ -38,7 +46,8 @@ export class ExpressionHeatmapPlugin {
expressions.registerRenderer(heatmapRenderer({ theme: core.theme }));
}
public start(core: CoreStart, { fieldFormats }: ExpressionHeatmapPluginStart) {
public start(core: CoreStart, { data, fieldFormats }: ExpressionHeatmapPluginStart) {
setFormatService(fieldFormats);
setDatatableUtilities(data.datatableUtilities);
}
}

View file

@ -6,8 +6,8 @@
* Side Public License, v 1.
*/
import type { DatatableColumn } from '@kbn/expressions-plugin';
import { createGetterSetter } from '@kbn/kibana-utils-plugin/public';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
/** @public **/
export const checkColumnForPrecisionError = (column: DatatableColumn) =>
column.meta.sourceParams?.hasPrecisionError;
export const [getDatatableUtilities, setDatatableUtilities] =
createGetterSetter<DatatableUtilitiesService>('data.datatableUtilities');

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
export { getDatatableUtilities, setDatatableUtilities } from './datatable_utilities';
export { getFormatService, setFormatService } from './format_service';
export {
getPaletteService,

View file

@ -10,7 +10,7 @@ import { isUndefined, uniq } from 'lodash';
import React from 'react';
import moment from 'moment';
import { Endzones } from '@kbn/charts-plugin/public';
import { search } from '@kbn/data-plugin/public';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import {
getAccessorByDimension,
getColumnByAccessor,
@ -23,12 +23,14 @@ export interface XDomain {
minInterval?: number;
}
export const getAppliedTimeRange = (layers: CommonXYDataLayerConfig[]) => {
export const getAppliedTimeRange = (
datatableUtilitites: DatatableUtilitiesService,
layers: CommonXYDataLayerConfig[]
) => {
return layers
.map(({ xAccessor, table }) => {
const xColumn = xAccessor ? getColumnByAccessor(xAccessor, table.columns) : null;
const timeRange =
xColumn && search.aggs.getDateHistogramMetaDataByDatatableColumn(xColumn)?.timeRange;
const timeRange = xColumn && datatableUtilitites.getDateHistogramMeta(xColumn)?.timeRange;
if (timeRange) {
return {
timeRange,
@ -40,13 +42,14 @@ export const getAppliedTimeRange = (layers: CommonXYDataLayerConfig[]) => {
};
export const getXDomain = (
datatableUtilitites: DatatableUtilitiesService,
layers: CommonXYDataLayerConfig[],
minInterval: number | undefined,
isTimeViz: boolean,
isHistogram: boolean,
xExtent?: AxisExtentConfigResult
) => {
const appliedTimeRange = getAppliedTimeRange(layers)?.timeRange;
const appliedTimeRange = getAppliedTimeRange(datatableUtilitites, layers)?.timeRange;
const from = appliedTimeRange?.from;
const to = appliedTimeRange?.to;
const baseDomain = isTimeViz

View file

@ -32,6 +32,7 @@ import {
} from '@elastic/charts';
import { Datatable } from '@kbn/expressions-plugin/common';
import { EmptyPlaceholder } from '@kbn/charts-plugin/public';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks';
import { EventAnnotationOutput } from '@kbn/event-annotation-plugin/common';
import { DataLayerConfig } from '../../common';
@ -69,7 +70,7 @@ const onSelectRange = jest.fn();
describe('XYChart component', () => {
let getFormatSpy: jest.Mock;
let convertSpy: jest.Mock;
let defaultProps: Omit<XYChartRenderProps, 'data' | 'args'>;
let defaultProps: Omit<XYChartRenderProps, 'args'>;
const dataWithoutFormats: Datatable = {
type: 'datatable',
@ -109,6 +110,7 @@ describe('XYChart component', () => {
getFormatSpy.mockReturnValue({ convert: convertSpy });
defaultProps = {
data: dataPluginMock.createStartContract(),
formatFactory: getFormatSpy,
timeZone: 'UTC',
renderMode: 'view',

View file

@ -30,6 +30,7 @@ import {
import { IconType } from '@elastic/eui';
import { PaletteRegistry } from '@kbn/coloring';
import { RenderMode } from '@kbn/expressions-plugin/common';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { EmptyPlaceholder } from '@kbn/charts-plugin/public';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
import { ChartsPluginSetup, ChartsPluginStart, useActiveCursor } from '@kbn/charts-plugin/public';
@ -101,6 +102,7 @@ declare global {
export type XYChartRenderProps = XYChartProps & {
chartsThemeService: ChartsPluginSetup['theme'];
chartsActiveCursorService: ChartsPluginStart['activeCursor'];
data: DataPublicPluginStart;
paletteService: PaletteRegistry;
formatFactory: FormatFactory;
timeZone: string;
@ -144,6 +146,7 @@ export const XYChartReportable = React.memo(XYChart);
export function XYChart({
args,
data,
formatFactory,
timeZone,
chartsThemeService,
@ -277,6 +280,7 @@ export function XYChart({
const isHistogramViz = dataLayers.every((l) => l.isHistogram);
const { baseDomain: rawXDomain, extendedDomain: xDomain } = getXDomain(
data.datatableUtilities,
dataLayers,
minInterval,
isTimeViz,

View file

@ -13,6 +13,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import type { PaletteRegistry } from '@kbn/coloring';
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
import { ExpressionRenderDefinition } from '@kbn/expressions-plugin';
import { FormatFactory } from '@kbn/field-formats-plugin/common';
@ -21,6 +22,7 @@ import type { XYChartProps } from '../../common';
import type { BrushEvent, FilterEvent } from '../types';
export type GetStartDepsFn = () => Promise<{
data: DataPublicPluginStart;
formatFactory: FormatFactory;
theme: ChartsPluginStart['theme'];
activeCursor: ChartsPluginStart['activeCursor'];
@ -69,6 +71,7 @@ export const getXyChartRenderer = ({
>
<XYChartReportable
{...config}
data={deps.data}
formatFactory={deps.formatFactory}
chartsActiveCursorService={deps.activeCursor}
chartsThemeService={deps.theme}
@ -76,7 +79,7 @@ export const getXyChartRenderer = ({
timeZone={deps.timeZone}
eventAnnotationService={deps.eventAnnotationService}
useLegacyTimeAxis={deps.useLegacyTimeAxis}
minInterval={calculateMinInterval(config)}
minInterval={calculateMinInterval(deps.data.datatableUtilities, config)}
interactive={handlers.isInteractive()}
onClickValue={onClickValue}
onSelectRange={onSelectRange}

View file

@ -6,11 +6,13 @@
* Side Public License, v 1.
*/
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import { DataLayerConfig, XYChartProps } from '../../common';
import { sampleArgs } from '../../common/__mocks__';
import { calculateMinInterval } from './interval';
describe('calculateMinInterval', () => {
const datatableUtilities = createDatatableUtilitiesMock();
let xyProps: XYChartProps;
let layer: DataLayerConfig;
beforeEach(() => {
@ -29,7 +31,7 @@ describe('calculateMinInterval', () => {
},
};
xyProps.args.layers[0] = layer;
const result = await calculateMinInterval(xyProps);
const result = await calculateMinInterval(datatableUtilities, xyProps);
expect(result).toEqual(5 * 60 * 1000);
});
@ -48,7 +50,7 @@ describe('calculateMinInterval', () => {
},
};
xyProps.args.layers[0] = layer;
const result = await calculateMinInterval(xyProps);
const result = await calculateMinInterval(datatableUtilities, xyProps);
expect(result).toEqual(5);
});
@ -63,19 +65,19 @@ describe('calculateMinInterval', () => {
};
xyProps.args.layers[0] = layer;
const result = await calculateMinInterval(xyProps);
const result = await calculateMinInterval(datatableUtilities, xyProps);
expect(result).toEqual(undefined);
});
it('should return undefined if interval can not be checked', async () => {
const result = await calculateMinInterval(xyProps);
const result = await calculateMinInterval(datatableUtilities, xyProps);
expect(result).toEqual(undefined);
});
it('should return undefined if date column is not found', async () => {
layer.table.columns.splice(2, 1);
xyProps.args.layers[0] = layer;
const result = await calculateMinInterval(xyProps);
const result = await calculateMinInterval(datatableUtilities, xyProps);
expect(result).toEqual(undefined);
});
@ -83,7 +85,7 @@ describe('calculateMinInterval', () => {
layer.xScaleType = 'ordinal';
xyProps.args.layers[0] = layer;
xyProps.args.layers[0].table.columns.splice(2, 1);
const result = await calculateMinInterval(xyProps);
const result = await calculateMinInterval(datatableUtilities, xyProps);
expect(result).toEqual(undefined);
});
@ -97,7 +99,7 @@ describe('calculateMinInterval', () => {
};
xyProps.args.layers[0] = layer;
xyProps.args.minTimeBarInterval = '1h';
const result = await calculateMinInterval(xyProps);
const result = await calculateMinInterval(datatableUtilities, xyProps);
expect(result).toEqual(60 * 60 * 1000);
});
});

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import { search } from '@kbn/data-plugin/public';
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
import { XYChartProps } from '../../common';
@ -13,7 +14,10 @@ import { isTimeChart } from '../../common/helpers';
import { getFilteredLayers } from './layers';
import { isDataLayer, getDataLayers } from './visualization';
export function calculateMinInterval({ args: { layers, minTimeBarInterval } }: XYChartProps) {
export function calculateMinInterval(
datatableUtilities: DatatableUtilitiesService,
{ args: { layers, minTimeBarInterval } }: XYChartProps
) {
const filteredLayers = getFilteredLayers(layers);
if (filteredLayers.length === 0) return;
const isTimeViz = isTimeChart(getDataLayers(filteredLayers));
@ -27,14 +31,14 @@ export function calculateMinInterval({ args: { layers, minTimeBarInterval } }: X
return search.aggs.parseInterval(minTimeBarInterval)?.as('milliseconds');
}
if (!isTimeViz) {
const histogramInterval = search.aggs.getNumberHistogramIntervalByDatatableColumn(xColumn);
const histogramInterval = datatableUtilities.getNumberHistogramInterval(xColumn);
if (typeof histogramInterval === 'number') {
return histogramInterval;
} else {
return undefined;
}
}
const dateInterval = search.aggs.getDateHistogramMetaDataByDatatableColumn(xColumn)?.interval;
const dateInterval = datatableUtilities.getDateHistogramMeta(xColumn)?.interval;
if (!dateInterval) return;
const intervalDuration = search.aggs.parseInterval(dateInterval);
if (!intervalDuration) return;

View file

@ -12,6 +12,7 @@ import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'
import { FieldFormat } from '@kbn/field-formats-plugin/common';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import type { AggsCommonStart } from '../search';
import { BUCKET_TYPES } from '../search/aggs/buckets/bucket_agg_types';
import { DatatableUtilitiesService } from './datatable_utilities_service';
describe('DatatableUtilitiesService', () => {
@ -106,6 +107,94 @@ describe('DatatableUtilitiesService', () => {
});
});
describe('getNumberHistogramInterval', () => {
it('should return nothing on column from other data source', () => {
expect(
datatableUtilitiesService.getNumberHistogramInterval({
id: 'test',
name: 'test',
meta: {
type: 'date',
source: 'essql',
},
})
).toEqual(undefined);
});
it('should return nothing on non histogram column', () => {
expect(
datatableUtilitiesService.getNumberHistogramInterval({
id: 'test',
name: 'test',
meta: {
type: 'date',
source: 'esaggs',
sourceParams: {
type: BUCKET_TYPES.TERMS,
},
},
})
).toEqual(undefined);
});
it('should return interval on resolved auto interval', () => {
expect(
datatableUtilitiesService.getNumberHistogramInterval({
id: 'test',
name: 'test',
meta: {
type: 'date',
source: 'esaggs',
sourceParams: {
type: BUCKET_TYPES.HISTOGRAM,
params: {
interval: 'auto',
used_interval: 20,
},
},
},
})
).toEqual(20);
});
it('should return interval on fixed interval', () => {
expect(
datatableUtilitiesService.getNumberHistogramInterval({
id: 'test',
name: 'test',
meta: {
type: 'date',
source: 'esaggs',
sourceParams: {
type: BUCKET_TYPES.HISTOGRAM,
params: {
interval: 7,
used_interval: 7,
},
},
},
})
).toEqual(7);
});
it('should return `undefined` if information is not available', () => {
expect(
datatableUtilitiesService.getNumberHistogramInterval({
id: 'test',
name: 'test',
meta: {
type: 'date',
source: 'esaggs',
sourceParams: {
type: BUCKET_TYPES.HISTOGRAM,
params: {},
},
},
})
).toEqual(undefined);
});
});
describe('getTotalCount', () => {
it('should return a total hits count', () => {
const table = {
@ -116,6 +205,40 @@ describe('DatatableUtilitiesService', () => {
});
});
describe('hasPrecisionError', () => {
test('should return true if there is a precision error in the column', () => {
expect(
datatableUtilitiesService.hasPrecisionError({
meta: {
sourceParams: {
hasPrecisionError: true,
},
},
} as unknown as DatatableColumn)
).toBeTruthy();
});
test('should return false if there is no precision error in the column', () => {
expect(
datatableUtilitiesService.hasPrecisionError({
meta: {
sourceParams: {
hasPrecisionError: false,
},
},
} as unknown as DatatableColumn)
).toBeFalsy();
});
test('should return false if precision error is not defined', () => {
expect(
datatableUtilitiesService.hasPrecisionError({
meta: {
sourceParams: {},
},
} as unknown as DatatableColumn)
).toBeFalsy();
});
});
describe('setFieldFormat', () => {
it('should set new field format', () => {
const column = { meta: {} } as DatatableColumn;

View file

@ -9,7 +9,22 @@
import type { DataView, DataViewsContract, DataViewField } from '@kbn/data-views-plugin/common';
import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common';
import type { FieldFormatsStartCommon, FieldFormat } from '@kbn/field-formats-plugin/common';
import type { AggsCommonStart, AggConfig, CreateAggConfigParams, IAggType } from '../search';
import type {
AggsCommonStart,
AggConfig,
AggParamsDateHistogram,
AggParamsHistogram,
CreateAggConfigParams,
IAggType,
} from '../search';
import { BUCKET_TYPES } from '../search/aggs/buckets/bucket_agg_types';
import type { TimeRange } from '../types';
interface DateHistogramMeta {
interval?: string;
timeZone?: string;
timeRange?: TimeRange;
}
export class DatatableUtilitiesService {
constructor(
@ -46,6 +61,39 @@ export class DatatableUtilitiesService {
return aggs[0];
}
/**
* Helper function returning the used interval, used time zone and applied time filters for data table column created by the date_histogramm agg type.
* "auto" will get expanded to the actually used interval.
* If the column is not a column created by a date_histogram aggregation of the esaggs data source,
* this function will return undefined.
*/
getDateHistogramMeta(
column: DatatableColumn,
defaults: Partial<{
timeZone: string;
}> = {}
): DateHistogramMeta | undefined {
if (column.meta.source !== 'esaggs') {
return;
}
if (column.meta.sourceParams?.type !== BUCKET_TYPES.DATE_HISTOGRAM) {
return;
}
const params = column.meta.sourceParams.params as AggParamsDateHistogram;
let interval: string | undefined;
if (params.used_interval && params.used_interval !== 'auto') {
interval = params.used_interval;
}
return {
interval,
timeZone: params.used_time_zone || defaults.timeZone,
timeRange: column.meta.sourceParams.appliedTimeRange as TimeRange | undefined,
};
}
async getDataView(column: DatatableColumn): Promise<DataView | undefined> {
if (!column.meta.index) {
return;
@ -77,10 +125,37 @@ export class DatatableUtilitiesService {
return params?.interval;
}
/**
* 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.
*/
getNumberHistogramInterval(column: DatatableColumn): number | undefined {
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;
}
return params.used_interval;
}
getTotalCount(table: Datatable): number | undefined {
return table.meta?.statistics?.totalCount;
}
hasPrecisionError(column: DatatableColumn) {
return column.meta.sourceParams?.hasPrecisionError;
}
isFilterable(column: DatatableColumn): boolean {
if (column.meta.source !== 'esaggs') {
return false;

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { DatatableUtilitiesService } from './datatable_utilities_service';
import { DatatableUtilitiesService } from './datatable_utilities_service';
export function createDatatableUtilitiesMock(): jest.Mocked<DatatableUtilitiesService> {
return {
@ -14,8 +14,13 @@ export function createDatatableUtilitiesMock(): jest.Mocked<DatatableUtilitiesSe
clearFieldFormat: jest.fn(),
getAggConfig: jest.fn(),
getDataView: jest.fn(),
getDateHistogramMeta: jest.fn(DatatableUtilitiesService.prototype.getDateHistogramMeta),
getField: jest.fn(),
getFieldFormat: jest.fn(),
getNumberHistogramInterval: jest.fn(
DatatableUtilitiesService.prototype.getNumberHistogramInterval
),
hasPrecisionError: jest.fn(DatatableUtilitiesService.prototype.hasPrecisionError),
isFilterable: jest.fn(),
setFieldFormat: jest.fn(),
} as unknown as jest.Mocked<DatatableUtilitiesService>;

View file

@ -1,40 +0,0 @@
/*
* 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 '@kbn/expressions-plugin/common';
import { TimeRange } from '../../../types';
import type { AggParamsDateHistogram } from '../buckets';
import { BUCKET_TYPES } from '../buckets/bucket_agg_types';
/**
* Helper function returning the used interval, used time zone and applied time filters for data table column created by the date_histogramm agg type.
* "auto" will get expanded to the actually used interval.
* If the column is not a column created by a date_histogram aggregation of the esaggs data source,
* this function will return undefined.
*/
export const getDateHistogramMetaDataByDatatableColumn = (
column: DatatableColumn,
defaults: Partial<{
timeZone: string;
}> = {}
) => {
if (column.meta.source !== 'esaggs') return;
if (column.meta.sourceParams?.type !== BUCKET_TYPES.DATE_HISTOGRAM) return;
const params = column.meta.sourceParams.params as unknown as AggParamsDateHistogram;
let interval: string | undefined;
if (params.used_interval && params.used_interval !== 'auto') {
interval = params.used_interval;
}
return {
interval,
timeZone: params.used_time_zone || defaults.timeZone,
timeRange: column.meta.sourceParams.appliedTimeRange as TimeRange | undefined,
};
};

View file

@ -1,98 +0,0 @@
/*
* 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);
});
});

View file

@ -1,28 +0,0 @@
/*
* 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 '@kbn/expressions-plugin/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;
};

View file

@ -7,8 +7,6 @@
*/
export * from './calculate_auto_time_expression';
export { getNumberHistogramIntervalByDatatableColumn } from './get_number_histogram_interval';
export { getDateHistogramMetaDataByDatatableColumn } from './get_date_histogram_meta';
export * from './date_interval_utils';
export * from './get_aggs_formats';
export * from './ip_address';

View file

@ -9,4 +9,3 @@
export { tabifyDocs, flattenHit } from './tabify_docs';
export { tabifyAggResponse } from './tabify';
export { tabifyGetColumns } from './get_columns';
export { checkColumnForPrecisionError } from './utils';

View file

@ -1,46 +0,0 @@
/*
* 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 { checkColumnForPrecisionError } from './utils';
import type { DatatableColumn } from '@kbn/expressions-plugin';
describe('tabify utils', () => {
describe('checkDatatableForPrecisionError', () => {
test('should return true if there is a precision error in the column', () => {
expect(
checkColumnForPrecisionError({
meta: {
sourceParams: {
hasPrecisionError: true,
},
},
} as unknown as DatatableColumn)
).toBeTruthy();
});
test('should return false if there is no precision error in the column', () => {
expect(
checkColumnForPrecisionError({
meta: {
sourceParams: {
hasPrecisionError: false,
},
},
} as unknown as DatatableColumn)
).toBeFalsy();
});
test('should return false if precision error is not defined', () => {
expect(
checkColumnForPrecisionError({
meta: {
sourceParams: {},
},
} as unknown as DatatableColumn)
).toBeFalsy();
});
});
});

View file

@ -115,13 +115,10 @@ import {
parseInterval,
toAbsoluteDates,
boundsDescendingRaw,
getNumberHistogramIntervalByDatatableColumn,
getDateHistogramMetaDataByDatatableColumn,
getResponseInspectorStats,
// tabify
tabifyAggResponse,
tabifyGetColumns,
checkColumnForPrecisionError,
} from '../common';
export { AggGroupLabels, AggGroupNames, METRIC_TYPES, BUCKET_TYPES } from '../common';
@ -225,13 +222,10 @@ export const search = {
termsAggFilter,
toAbsoluteDates,
boundsDescendingRaw,
getNumberHistogramIntervalByDatatableColumn,
getDateHistogramMetaDataByDatatableColumn,
},
getResponseInspectorStats,
tabifyAggResponse,
tabifyGetColumns,
checkColumnForPrecisionError,
};
/*

View file

@ -37,7 +37,6 @@ export {
parseSearchSourceJSON,
SearchSource,
SortDirection,
checkColumnForPrecisionError,
} from '../../common/search';
export type {
ISessionService,

View file

@ -7,6 +7,7 @@
import moment from 'moment';
import type { Datatable } from '@kbn/expressions-plugin/common';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import type { TimeRange } from '@kbn/data-plugin/common';
import { functionWrapper } from '@kbn/expressions-plugin/common/expression_functions/specs/tests/utils';
@ -27,7 +28,7 @@ import type { TimeScaleArgs } from './types';
describe('time_scale', () => {
let timeScaleWrapped: (input: Datatable, args: TimeScaleArgs) => Promise<Datatable>;
const timeScale = getTimeScale(() => 'UTC');
const timeScale = getTimeScale(createDatatableUtilitiesMock, () => 'UTC');
const emptyTable: Datatable = {
type: 'datatable',
@ -395,7 +396,7 @@ describe('time_scale', () => {
resolveTimezonePromise = res;
});
const timeScaleResolved = jest.fn((x) => x);
const delayedTimeScale = getTimeScale(() => timezonePromise);
const delayedTimeScale = getTimeScale(createDatatableUtilitiesMock, () => timezonePromise);
const delayedTimeScaleWrapper = functionWrapper(delayedTimeScale);
const result = delayedTimeScaleWrapper(
{

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import type { ExecutionContext } from '@kbn/expressions-plugin/common';
import type { TimeScaleExpressionFunction } from './types';
import type { timeScaleFn } from './time_scale_fn';
export const getTimeScale = (
getTimezone: (context: ExecutionContext) => string | Promise<string>
...timeScaleFnParameters: Parameters<typeof timeScaleFn>
): TimeScaleExpressionFunction => ({
name: 'lens_time_scale',
type: 'datatable',
@ -45,6 +45,6 @@ export const getTimeScale = (
async fn(...args) {
/** Build optimization: prevent adding extra code into initial bundle **/
const { timeScaleFn } = await import('./time_scale_fn');
return timeScaleFn(getTimezone)(...args);
return timeScaleFn(...timeScaleFnParameters)(...args);
},
});

View file

@ -8,11 +8,7 @@
import moment from 'moment-timezone';
import { i18n } from '@kbn/i18n';
import { buildResultColumns, Datatable, ExecutionContext } from '@kbn/expressions-plugin/common';
import {
calculateBounds,
getDateHistogramMetaDataByDatatableColumn,
parseInterval,
} from '@kbn/data-plugin/common';
import { calculateBounds, DatatableUtilitiesService, parseInterval } from '@kbn/data-plugin/common';
import type { TimeScaleExpressionFunction, TimeScaleUnit, TimeScaleArgs } from './types';
const unitInMs: Record<TimeScaleUnit, number> = {
@ -24,6 +20,9 @@ const unitInMs: Record<TimeScaleUnit, number> = {
export const timeScaleFn =
(
getDatatableUtilities: (
context: ExecutionContext
) => DatatableUtilitiesService | Promise<DatatableUtilitiesService>,
getTimezone: (context: ExecutionContext) => string | Promise<string>
): TimeScaleExpressionFunction['fn'] =>
async (
@ -59,7 +58,8 @@ export const timeScaleFn =
}
const targetUnitInMs = unitInMs[targetUnit];
const timeInfo = getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition, {
const datatableUtilities = await getDatatableUtilities(context);
const timeInfo = datatableUtilities.getDateHistogramMeta(dateColumnDefinition, {
timeZone: await getTimezone(context),
});
const intervalDuration = timeInfo?.interval && parseInterval(timeInfo.interval);

View file

@ -301,7 +301,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
plugins.uiActions.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({
data: {
...event.data,
timeFieldName: inferTimeField(event.data),
timeFieldName: inferTimeField(plugins.data.datatableUtilities, event.data),
},
});
}
@ -309,7 +309,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
plugins.uiActions.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({
data: {
...event.data,
timeFieldName: inferTimeField(event.data),
timeFieldName: inferTimeField(plugins.data.datatableUtilities, event.data),
},
});
}
@ -322,7 +322,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
);
}
},
[plugins.uiActions, activeVisualization, dispatchLens]
[plugins.data.datatableUtilities, plugins.uiActions, activeVisualization, dispatchLens]
);
const hasCompatibleActions = useCallback(

View file

@ -107,6 +107,8 @@ const attributeServiceMockFromSavedVis = (document: Document): LensAttributeServ
return service;
};
const dataMock = dataPluginMock.createStartContract();
describe('embeddable', () => {
let mountpoint: HTMLDivElement;
let expressionRenderer: jest.Mock<null, [ReactExpressionRendererProps]>;
@ -139,6 +141,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
indexPatternService: {} as DataViewsContract,
@ -188,6 +191,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
indexPatternService: {} as DataViewsContract,
@ -238,6 +242,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -298,6 +303,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
inspector: inspectorPluginMock.createStartContract(),
expressionRenderer,
basePath,
@ -347,6 +353,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -396,6 +403,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -443,6 +451,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -494,6 +503,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -549,6 +559,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -602,6 +613,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -662,6 +674,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -723,6 +736,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -787,6 +801,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -836,6 +851,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -887,6 +903,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -935,6 +952,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -998,6 +1016,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -1080,6 +1099,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -1137,6 +1157,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -1191,6 +1212,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService,
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),
@ -1266,6 +1288,7 @@ describe('embeddable', () => {
{
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
attributeService: attributeServiceMockFromSavedVis(visDocument),
data: dataMock,
expressionRenderer,
basePath,
inspector: inspectorPluginMock.createStartContract(),

View file

@ -12,6 +12,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
import { DataViewBase, Filter } from '@kbn/es-query';
import type { PaletteOutput } from '@kbn/coloring';
import {
DataPublicPluginStart,
ExecutionContextSearch,
Query,
TimefilterContract,
@ -112,6 +113,7 @@ export interface LensEmbeddableOutput extends EmbeddableOutput {
export interface LensEmbeddableDeps {
attributeService: LensAttributeService;
data: DataPublicPluginStart;
documentToExpression: (
doc: Document
) => Promise<{ ast: Ast | null; errors: ErrorMessage[] | undefined }>;
@ -612,7 +614,9 @@ export class Embeddable
this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({
data: {
...event.data,
timeFieldName: event.data.timeFieldName || inferTimeField(event.data),
timeFieldName:
event.data.timeFieldName ||
inferTimeField(this.deps.data.datatableUtilities, event.data),
},
embeddable: this,
});
@ -625,7 +629,9 @@ export class Embeddable
this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({
data: {
...event.data,
timeFieldName: event.data.timeFieldName || inferTimeField(event.data),
timeFieldName:
event.data.timeFieldName ||
inferTimeField(this.deps.data.datatableUtilities, event.data),
},
embeddable: this,
});

View file

@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import { RecursiveReadonly } from '@kbn/utility-types';
import { Ast } from '@kbn/interpreter';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { FilterManager, TimefilterContract } from '@kbn/data-plugin/public';
import { DataPublicPluginStart, FilterManager, TimefilterContract } from '@kbn/data-plugin/public';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import { ReactExpressionRendererType } from '@kbn/expressions-plugin/public';
import { EmbeddableFactoryDefinition, IContainer } from '@kbn/embeddable-plugin/public';
@ -26,6 +26,7 @@ import { extract, inject } from '../../common/embeddable_factory';
import { DatasourceMap, VisualizationMap } from '../types';
export interface LensEmbeddableStartServices {
data: DataPublicPluginStart;
timefilter: TimefilterContract;
coreHttp: HttpSetup;
inspector: InspectorStart;
@ -85,6 +86,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
async create(input: LensEmbeddableInput, parent?: IContainer) {
const {
data,
timefilter,
expressionRenderer,
documentToExpression,
@ -107,6 +109,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
return new Embeddable(
{
attributeService,
data,
indexPatternService,
timefilter,
inspector,

View file

@ -17,7 +17,8 @@ import { collapse } from '../common/expressions';
export const setupExpressions = (
expressions: ExpressionsSetup,
formatFactory: Parameters<typeof getDatatable>[0],
getTimeZone: Parameters<typeof getTimeScale>[0]
getDatatableUtilities: Parameters<typeof getTimeScale>[0],
getTimeZone: Parameters<typeof getTimeScale>[1]
) => {
[
collapse,
@ -26,6 +27,6 @@ export const setupExpressions = (
renameColumns,
datatableColumn,
getDatatable(formatFactory),
getTimeScale(getTimeZone),
getTimeScale(getDatatableUtilities, getTimeZone),
].forEach((expressionFn) => expressions.registerFunction(expressionFn));
};

View file

@ -809,6 +809,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
selectedOperationDefinition.shiftable &&
selectedColumn.timeShift !== undefined ? (
<TimeShift
datatableUtilities={services.data.datatableUtilities}
indexPattern={currentIndexPattern}
selectedColumn={selectedColumn}
columnId={columnId}

View file

@ -11,7 +11,7 @@ import { EuiComboBox } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useRef, useState } from 'react';
import { Query } from '@kbn/data-plugin/public';
import { parseTimeShift } from '@kbn/data-plugin/common';
import { DatatableUtilitiesService, parseTimeShift } from '@kbn/data-plugin/common';
import {
adjustTimeScaleLabelSuffix,
GenericIndexPatternColumn,
@ -61,6 +61,7 @@ export function setTimeShift(
}
export function TimeShift({
datatableUtilities,
selectedColumn,
columnId,
layer,
@ -70,6 +71,7 @@ export function TimeShift({
activeData,
layerId,
}: {
datatableUtilities: DatatableUtilitiesService;
selectedColumn: GenericIndexPatternColumn;
indexPattern: IndexPattern;
columnId: string;
@ -89,7 +91,13 @@ export function TimeShift({
return null;
}
const dateHistogramInterval = getDateHistogramInterval(layer, indexPattern, activeData, layerId);
const dateHistogramInterval = getDateHistogramInterval(
datatableUtilities,
layer,
indexPattern,
activeData,
layerId
);
const { isValueTooSmall, isValueNotMultiple, isInvalid, canShift } =
getLayerTimeShiftChecks(dateHistogramInterval);

View file

@ -618,8 +618,14 @@ export function getIndexPatternDatasource({
},
getWarningMessages: (state, frame, setState) => {
return [
...(getStateTimeShiftWarningMessages(state, frame) || []),
...getPrecisionErrorWarningMessages(state, frame, core.docLinks, setState),
...(getStateTimeShiftWarningMessages(data.datatableUtilities, state, frame) || []),
...getPrecisionErrorWarningMessages(
data.datatableUtilities,
state,
frame,
core.docLinks,
setState
),
];
},
checkIntegrity: (state) => {

View file

@ -66,6 +66,7 @@ export const WrappedFormulaEditor = ({
...rest
}: ParamEditorProps<FormulaIndexPatternColumn>) => {
const dateHistogramInterval = getDateHistogramInterval(
rest.data.datatableUtilities,
rest.layer,
rest.indexPattern,
activeData,

View file

@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { uniq } from 'lodash';
import { FormattedMessage } from '@kbn/i18n-react';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import { Datatable } from '@kbn/expressions-plugin';
import { search } from '@kbn/data-plugin/public';
import { parseTimeShift } from '@kbn/data-plugin/common';
@ -98,6 +99,7 @@ export const timeShiftOptionOrder = timeShiftOptions.reduce<{ [key: string]: num
);
export function getDateHistogramInterval(
datatableUtilities: DatatableUtilitiesService,
layer: IndexPatternLayer,
indexPattern: IndexPattern,
activeData: Record<string, Datatable> | undefined,
@ -112,8 +114,7 @@ export function getDateHistogramInterval(
if (dateHistogramColumn && activeData && activeData[layerId] && activeData[layerId]) {
const column = activeData[layerId].columns.find((col) => col.id === dateHistogramColumn);
if (column) {
const expression =
search.aggs.getDateHistogramMetaDataByDatatableColumn(column)?.interval || '';
const expression = datatableUtilities.getDateHistogramMeta(column)?.interval || '';
return {
interval: search.aggs.parseInterval(expression),
expression,
@ -184,6 +185,7 @@ export function getDisallowedPreviousShiftMessage(
}
export function getStateTimeShiftWarningMessages(
datatableUtilities: DatatableUtilitiesService,
state: IndexPatternPrivateState,
{ activeData }: FramePublicAPI
) {
@ -195,6 +197,7 @@ export function getStateTimeShiftWarningMessages(
return;
}
const dateHistogramInterval = getDateHistogramInterval(
datatableUtilities,
layer,
layerIndexPattern,
activeData,

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import { getPrecisionErrorWarningMessages } from './utils';
import type { IndexPatternPrivateState, GenericIndexPatternColumn } from './types';
import type { FramePublicAPI } from '../types';
@ -18,6 +19,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
describe('indexpattern_datasource utils', () => {
describe('getPrecisionErrorWarningMessages', () => {
const datatableUtilitites = createDatatableUtilitiesMock();
let state: IndexPatternPrivateState;
let framePublicAPI: FramePublicAPI;
let docLinks: DocLinksStart;
@ -72,7 +74,13 @@ describe('indexpattern_datasource utils', () => {
});
test('should not show precisionError if hasPrecisionError is false', () => {
expect(
getPrecisionErrorWarningMessages(state, framePublicAPI, docLinks, () => {})
getPrecisionErrorWarningMessages(
datatableUtilitites,
state,
framePublicAPI,
docLinks,
() => {}
)
).toHaveLength(0);
});
@ -80,7 +88,13 @@ describe('indexpattern_datasource utils', () => {
delete framePublicAPI.activeData!.id.columns[0].meta.sourceParams!.hasPrecisionError;
expect(
getPrecisionErrorWarningMessages(state, framePublicAPI, docLinks, () => {})
getPrecisionErrorWarningMessages(
datatableUtilitites,
state,
framePublicAPI,
docLinks,
() => {}
)
).toHaveLength(0);
});
@ -95,6 +109,7 @@ describe('indexpattern_datasource utils', () => {
const setStateMock = jest.fn();
const warningMessages = getPrecisionErrorWarningMessages(
datatableUtilitites,
state,
framePublicAPI,
docLinks,
@ -119,6 +134,7 @@ describe('indexpattern_datasource utils', () => {
(state.layers.id.columns.col1 as TermsIndexPatternColumn).params.accuracyMode = true;
const warningMessages = getPrecisionErrorWarningMessages(
datatableUtilitites,
state,
framePublicAPI,
docLinks,
@ -157,7 +173,13 @@ describe('indexpattern_datasource utils', () => {
} as unknown as GenericIndexPatternColumn,
};
const setState = jest.fn();
const warnings = getPrecisionErrorWarningMessages(state, framePublicAPI, docLinks, setState);
const warnings = getPrecisionErrorWarningMessages(
datatableUtilitites,
state,
framePublicAPI,
docLinks,
setState
);
expect(warnings).toHaveLength(1);
const DummyComponent = () => <>{warnings[0]}</>;

View file

@ -9,12 +9,13 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { DocLinksStart } from '@kbn/core/public';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import { TimeRange } from '@kbn/es-query';
import { EuiLink, EuiTextColor, EuiButton, EuiSpacer } from '@elastic/eui';
import { DatatableColumn } from '@kbn/expressions-plugin';
import type { DatatableColumn } from '@kbn/expressions-plugin';
import { groupBy, escape } from 'lodash';
import { checkColumnForPrecisionError, Query } from '@kbn/data-plugin/common';
import type { Query } from '@kbn/data-plugin/common';
import type { FramePublicAPI, StateSetter } from '../types';
import type { IndexPattern, IndexPatternLayer, IndexPatternPrivateState } from './types';
import type { ReferenceBasedIndexPatternColumn } from './operations/definitions/column_types';
@ -159,6 +160,7 @@ const accuracyModeEnabledWarning = (columnName: string, docLink: string) => (
);
export function getPrecisionErrorWarningMessages(
datatableUtilities: DatatableUtilitiesService,
state: IndexPatternPrivateState,
{ activeData }: FramePublicAPI,
docLinks: DocLinksStart,
@ -178,7 +180,7 @@ export function getPrecisionErrorWarningMessages(
.forEach(({ layerId, column }) => {
const currentLayer = state.layers[layerId];
const currentColumn = currentLayer?.columns[column.id];
if (currentLayer && currentColumn && checkColumnForPrecisionError(column)) {
if (currentLayer && currentColumn && datatableUtilities.hasPrecisionError(column)) {
const indexPattern = state.indexPatterns[currentLayer.indexPatternId];
// currentColumnIsTerms is mostly a type guard. If there's a precision error,
// we already know that we're dealing with a terms-based operation (at least for now).

View file

@ -272,6 +272,7 @@ export class LensPlugin {
attributeService: getLensAttributeService(coreStart, plugins),
capabilities: coreStart.application.capabilities,
coreHttp: coreStart.http,
data: plugins.data,
timefilter: plugins.data.query.timefilter.timefilter,
expressionRenderer: plugins.expressions.ReactExpressionRenderer,
documentToExpression: this.editorFrameService!.documentToExpression,
@ -306,6 +307,7 @@ export class LensPlugin {
setupExpressions(
expressions,
() => startServices().plugins.fieldFormats.deserialize,
() => startServices().plugins.data.datatableUtilities,
async () => {
const { getTimeZone } = await import('./utils');
return getTimeZone(core.uiSettings);

View file

@ -5,9 +5,12 @@
* 2.0.
*/
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import { Datatable } from '@kbn/expressions-plugin/public';
import { inferTimeField } from './utils';
const datatableUtilities = createDatatableUtilitiesMock();
const table: Datatable = {
type: 'datatable',
rows: [],
@ -52,7 +55,7 @@ describe('utils', () => {
describe('inferTimeField', () => {
test('infer time field for brush event', () => {
expect(
inferTimeField({
inferTimeField(datatableUtilities, {
table,
column: 0,
range: [1, 2],
@ -62,7 +65,7 @@ describe('utils', () => {
test('do not return time field if time range is not bound', () => {
expect(
inferTimeField({
inferTimeField(datatableUtilities, {
table: tableWithoutAppliedTimeRange,
column: 0,
range: [1, 2],
@ -72,7 +75,7 @@ describe('utils', () => {
test('infer time field for click event', () => {
expect(
inferTimeField({
inferTimeField(datatableUtilities, {
data: [
{
table,
@ -87,7 +90,7 @@ describe('utils', () => {
test('do not return time field for negated click event', () => {
expect(
inferTimeField({
inferTimeField(datatableUtilities, {
data: [
{
table,
@ -103,7 +106,7 @@ describe('utils', () => {
test('do not return time field for click event without bound time field', () => {
expect(
inferTimeField({
inferTimeField(datatableUtilities, {
data: [
{
table: tableWithoutAppliedTimeRange,

View file

@ -11,7 +11,7 @@ import moment from 'moment-timezone';
import type { TimefilterContract } from '@kbn/data-plugin/public';
import type { IUiSettingsClient, SavedObjectReference } from '@kbn/core/public';
import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public';
import { search } from '@kbn/data-plugin/public';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import { BrushTriggerEvent, ClickTriggerEvent } from '@kbn/charts-plugin/public';
import type { Document } from './persistence/saved_object_store';
import type { Datasource, DatasourceMap, Visualization, StateSetter } from './types';
@ -147,7 +147,10 @@ export function getRemoveOperation(
return layerCount === 1 ? 'clear' : 'remove';
}
export function inferTimeField(context: BrushTriggerEvent['data'] | ClickTriggerEvent['data']) {
export function inferTimeField(
datatableUtilities: DatatableUtilitiesService,
context: BrushTriggerEvent['data'] | ClickTriggerEvent['data']
) {
const tablesAndColumns =
'table' in context
? [{ table: context.table, column: context.column }]
@ -159,7 +162,7 @@ export function inferTimeField(context: BrushTriggerEvent['data'] | ClickTrigger
.map(({ table, column }) => {
const tableColumn = table.columns[column];
const hasTimeRange = Boolean(
tableColumn && search.aggs.getDateHistogramMetaDataByDatatableColumn(tableColumn)?.timeRange
tableColumn && datatableUtilities.getDateHistogramMeta(tableColumn)?.timeRange
);
if (hasTimeRange) {
return tableColumn.meta.field;

View file

@ -29,11 +29,12 @@ export class XyVisualization {
) {
editorFrame.registerVisualization(async () => {
const { getXyVisualization } = await import('../async_services');
const [, { charts, fieldFormats, eventAnnotation }] = await core.getStartServices();
const [, { charts, data, fieldFormats, eventAnnotation }] = await core.getStartServices();
const palettes = await charts.palettes.getPalettes();
const eventAnnotationService = await eventAnnotation.getService();
const useLegacyTimeAxis = core.uiSettings.get(LEGACY_TIME_AXIS);
return getXyVisualization({
datatableUtilities: data.datatableUtilities,
paletteService: palettes,
eventAnnotationService,
fieldFormats,

View file

@ -8,6 +8,7 @@
import { Ast, fromExpression } from '@kbn/interpreter';
import { Position } from '@elastic/charts';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import { getXyVisualization } from './xy_visualization';
import { OperationDescriptor } from '../types';
import { createMockDatasource, createMockFramePublicAPI } from '../mocks';
@ -20,6 +21,7 @@ import { LegendSize } from '@kbn/visualizations-plugin/common';
describe('#toExpression', () => {
const xyVisualization = getXyVisualization({
datatableUtilities: createDatatableUtilitiesMock(),
paletteService: chartPluginMock.createPaletteRegistry(),
fieldFormats: fieldFormatsServiceMock.createStartContract(),
kibanaTheme: themeServiceMock.createStartContract(),

View file

@ -16,6 +16,7 @@ import type {
XYDataLayerConfig,
XYReferenceLineLayerConfig,
} from './types';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import type { SeriesType } from '@kbn/expression-xy-plugin/common';
import { layerTypes } from '../../common';
import { createMockDatasource, createMockFramePublicAPI } from '../mocks';
@ -68,6 +69,7 @@ const paletteServiceMock = chartPluginMock.createPaletteRegistry();
const fieldFormatsMock = fieldFormatsServiceMock.createStartContract();
const xyVisualization = getXyVisualization({
datatableUtilities: createDatatableUtilitiesMock(),
paletteService: paletteServiceMock,
fieldFormats: fieldFormatsMock,
useLegacyTimeAxis: false,

View file

@ -11,6 +11,7 @@ import { Position } from '@elastic/charts';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import type { PaletteRegistry } from '@kbn/coloring';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { ThemeServiceStart } from '@kbn/core/public';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
@ -72,12 +73,14 @@ import { DimensionTrigger } from '../shared_components/dimension_trigger';
import { defaultAnnotationLabel } from './annotations/helpers';
export const getXyVisualization = ({
datatableUtilities,
paletteService,
fieldFormats,
useLegacyTimeAxis,
kibanaTheme,
eventAnnotationService,
}: {
datatableUtilities: DatatableUtilitiesService;
paletteService: PaletteRegistry;
eventAnnotationService: EventAnnotationServiceType;
fieldFormats: FieldFormatsStart;
@ -496,6 +499,7 @@ export const getXyVisualization = ({
renderDimensionEditor(domElement, props) {
const allProps = {
...props,
datatableUtilities,
formatFactory: fieldFormats.deserialize,
paletteService,
};

View file

@ -27,6 +27,7 @@ import {
RangeEventAnnotationConfig,
} from '@kbn/event-annotation-plugin/common/types';
import { pick } from 'lodash';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import { search } from '@kbn/data-plugin/public';
import {
defaultAnnotationColor,
@ -56,6 +57,7 @@ export const toLineAnnotationColor = (color = defaultAnnotationRangeColor) => {
};
export const getEndTimestamp = (
datatableUtilities: DatatableUtilitiesService,
startTime: string,
{ activeData, dateRange }: FramePublicAPI,
dataLayers: XYDataLayerConfig[]
@ -81,7 +83,7 @@ export const getEndTimestamp = (
return fallbackValue;
}
const dateInterval = search.aggs.getDateHistogramMetaDataByDatatableColumn(xColumn)?.interval;
const dateInterval = datatableUtilities.getDateHistogramMeta(xColumn)?.interval;
if (!dateInterval) return fallbackValue;
const intervalDuration = search.aggs.parseInterval(dateInterval);
if (!intervalDuration) return fallbackValue;
@ -117,6 +119,7 @@ const sanitizeProperties = (annotation: EventAnnotationConfig) => {
export const AnnotationsPanel = (
props: VisualizationDimensionEditorProps<State> & {
datatableUtilities: DatatableUtilitiesService;
formatFactory: FormatFactory;
paletteService: PaletteRegistry;
}
@ -255,6 +258,7 @@ export const AnnotationsPanel = (
<ConfigPanelApplyAsRangeSwitch
annotation={currentAnnotation}
datatableUtilities={props.datatableUtilities}
onChange={setAnnotations}
frame={frame}
state={state}
@ -366,11 +370,13 @@ export const AnnotationsPanel = (
const ConfigPanelApplyAsRangeSwitch = ({
annotation,
datatableUtilities,
onChange,
frame,
state,
}: {
annotation?: EventAnnotationConfig;
datatableUtilities: DatatableUtilitiesService;
onChange: (annotations: Partial<EventAnnotationConfig> | undefined) => void;
frame: FramePublicAPI;
state: XYState;
@ -411,7 +417,12 @@ const ConfigPanelApplyAsRangeSwitch = ({
key: {
type: 'range',
timestamp: annotation.key.timestamp,
endTimestamp: getEndTimestamp(fromTimestamp.toISOString(), frame, dataLayers),
endTimestamp: getEndTimestamp(
datatableUtilities,
fromTimestamp.toISOString(),
frame,
dataLayers
),
},
id: annotation.id,
label:

View file

@ -7,6 +7,7 @@
import React from 'react';
import { mountWithIntl as mount } from '@kbn/test-jest-helpers';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import { AnnotationsPanel } from '.';
import { FramePublicAPI } from '../../../types';
import { layerTypes } from '../../..';
@ -36,6 +37,7 @@ const customLineStaticAnnotation = {
};
describe('AnnotationsPanel', () => {
const datatableUtilities = createDatatableUtilitiesMock();
let frame: FramePublicAPI;
function testState(): State {
@ -68,6 +70,7 @@ describe('AnnotationsPanel', () => {
accessor="ann1"
groupId="left"
state={state}
datatableUtilities={datatableUtilities}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}
@ -128,6 +131,7 @@ describe('AnnotationsPanel', () => {
accessor="ann1"
groupId="left"
state={state}
datatableUtilities={datatableUtilities}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}
@ -166,6 +170,7 @@ describe('AnnotationsPanel', () => {
accessor="ann1"
groupId="left"
state={state}
datatableUtilities={datatableUtilities}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}

View file

@ -9,6 +9,7 @@ import React, { useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonGroup, EuiFormRow, htmlIdGenerator } from '@elastic/eui';
import type { PaletteRegistry } from '@kbn/coloring';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import { YAxisMode, ExtendedYConfig } from '@kbn/expression-xy-plugin/common';
import type { VisualizationDimensionEditorProps } from '../../types';
import { State, XYState, XYDataLayerConfig } from '../types';
@ -43,6 +44,7 @@ export const idPrefix = htmlIdGenerator()();
export function DimensionEditor(
props: VisualizationDimensionEditorProps<State> & {
datatableUtilities: DatatableUtilitiesService;
formatFactory: FormatFactory;
paletteService: PaletteRegistry;
}

View file

@ -8,6 +8,7 @@
import React from 'react';
import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test-jest-helpers';
import { EuiButtonGroupProps, EuiButtonGroup } from '@elastic/eui';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import { XyToolbar } from '.';
import { DimensionEditor } from './dimension_editor';
import { AxisSettingsPopover } from './axis_settings_popover';
@ -221,6 +222,8 @@ describe('XY Config panels', () => {
});
describe('Dimension Editor', () => {
const datatableUtilities = createDatatableUtilitiesMock();
test('shows the correct axis side options when in horizontal mode', () => {
const state = testState();
const component = mount(
@ -234,6 +237,7 @@ describe('XY Config panels', () => {
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' } as XYDataLayerConfig],
}}
datatableUtilities={datatableUtilities}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}
@ -258,6 +262,7 @@ describe('XY Config panels', () => {
accessor="bar"
groupId="left"
state={state}
datatableUtilities={datatableUtilities}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}
@ -303,6 +308,7 @@ describe('XY Config panels', () => {
accessor="bar"
groupId="left"
state={state}
datatableUtilities={datatableUtilities}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}
@ -345,6 +351,7 @@ describe('XY Config panels', () => {
accessor="bar"
groupId="left"
state={state}
datatableUtilities={datatableUtilities}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}
@ -387,6 +394,7 @@ describe('XY Config panels', () => {
accessor="bar"
groupId="left"
state={state}
datatableUtilities={datatableUtilities}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}

View file

@ -17,6 +17,7 @@ import {
import { generateId } from '../id_generator';
import { getXyVisualization } from './xy_visualization';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks';
import type { PaletteOutput } from '@kbn/coloring';
import { layerTypes } from '../../common';
@ -26,6 +27,7 @@ import { themeServiceMock } from '@kbn/core/public/mocks';
jest.mock('../id_generator');
const xyVisualization = getXyVisualization({
datatableUtilities: createDatatableUtilitiesMock(),
paletteService: chartPluginMock.createPaletteRegistry(),
fieldFormats: fieldFormatsServiceMock.createStartContract(),
useLegacyTimeAxis: false,

View file

@ -14,7 +14,7 @@ import {
getTimeScale,
getDatatable,
} from '../../common/expressions';
import { getFormatFactory, getTimeZoneFactory } from './utils';
import { getDatatableUtilitiesFactory, getFormatFactory, getTimeZoneFactory } from './utils';
import type { PluginStartContract } from '../plugin';
@ -27,6 +27,6 @@ export const setupExpressions = (
formatColumn,
renameColumns,
getDatatable(getFormatFactory(core)),
getTimeScale(getTimeZoneFactory(core)),
getTimeScale(getDatatableUtilitiesFactory(core), getTimeZoneFactory(core)),
].forEach((expressionFn) => expressions.registerFunction(expressionFn));
};

View file

@ -43,3 +43,20 @@ export const getTimeZoneFactory =
/** if `Browser`, hardcode it to 'UTC' so the export has data that makes sense **/
return timezone === 'Browser' ? 'UTC' : timezone;
};
/** @internal **/
export const getDatatableUtilitiesFactory =
(core: CoreSetup<PluginStartContract>) => async (context: ExecutionContext) => {
const kibanaRequest = context.getKibanaRequest?.();
if (!kibanaRequest) {
throw new Error('expression function cannot be executed without a KibanaRequest');
}
const [{ elasticsearch, savedObjects }, { data }] = await core.getStartServices();
const elasticsearchClient = elasticsearch.client.asScoped(kibanaRequest).asCurrentUser;
const savedObjectsClient = savedObjects.getScopedClient(kibanaRequest);
const { datatableUtilities } = data;
return datatableUtilities.asScopedToClient(savedObjectsClient, elasticsearchClient);
};