Skip to content

Commit

Permalink
[XY] Add minTimeBarInterval arg (#128726)
Browse files Browse the repository at this point in the history
* Added `xAxisInterval` arg

* Add validation

* Add tests

* Rename xAxisInterval to minTimeBarInterval and add validation

* Fix imports

* Add tests to validation

* Fix conflicts

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* Fix tests

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
VladLasitsa and kibanamachine authored May 17, 2022
1 parent ca5398a commit fe04423
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 5 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,8 @@ export const commonXYArgs: CommonXYFn['args'] = {
types: ['string'],
help: strings.getAriaLabelHelp(),
},
minTimeBarInterval: {
types: ['string'],
help: strings.getMinTimeBarIntervalHelp(),
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@
*/

import { XY_VIS_RENDERER } from '../constants';
import { appendLayerIds } from '../helpers';
import { appendLayerIds, getDataLayers } from '../helpers';
import { LayeredXyVisFn } from '../types';
import { logDatatables } from '../utils';
import { validateMinTimeBarInterval, hasBarLayer } from './validate';

export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) => {
const layers = appendLayerIds(args.layers ?? [], 'layers');

logDatatables(layers, handlers);

const dataLayers = getDataLayers(layers);
const hasBar = hasBarLayer(dataLayers);
validateMinTimeBarInterval(dataLayers, hasBar, args.minTimeBarInterval);

return {
type: 'render',
as: XY_VIS_RENDERER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
*/

import { i18n } from '@kbn/i18n';
import { isValidInterval } from '@kbn/data-plugin/common';
import { AxisExtentModes, ValueLabelModes } from '../constants';
import {
AxisExtentConfigResult,
DataLayerConfigResult,
CommonXYDataLayerConfigResult,
ValueLabelMode,
CommonXYDataLayerConfig,
} from '../types';
import { isTimeChart } from '../helpers';

const errors = {
extendBoundsAreInvalidError: () =>
Expand All @@ -37,6 +40,18 @@ const errors = {
i18n.translate('expressionXY.reusable.function.xyVis.errors.dataBoundsForNotLineChartError', {
defaultMessage: 'Only line charts can be fit to the data bounds',
}),
isInvalidIntervalError: () =>
i18n.translate('expressionXY.reusable.function.xyVis.errors.isInvalidIntervalError', {
defaultMessage:
'Provided x-axis interval is invalid. The interval should include quantity and unit names. Examples: 1d, 24h, 1w.',
}),
minTimeBarIntervalNotForTimeBarChartError: () =>
i18n.translate(
'expressionXY.reusable.function.xyVis.errors.minTimeBarIntervalNotForTimeBarChartError',
{
defaultMessage: '`minTimeBarInterval` argument is applicable only for time bar charts.',
}
),
};

export const hasBarLayer = (layers: Array<DataLayerConfigResult | CommonXYDataLayerConfig>) =>
Expand Down Expand Up @@ -101,3 +116,19 @@ export const validateValueLabels = (
throw new Error(errors.valueLabelsForNotBarsOrHistogramBarsChartsError());
}
};

export const validateMinTimeBarInterval = (
dataLayers: CommonXYDataLayerConfigResult[],
hasBar: boolean,
minTimeBarInterval?: string
) => {
if (minTimeBarInterval) {
if (!isValidInterval(minTimeBarInterval)) {
throw new Error(errors.isInvalidIntervalError());
}

if (!hasBar || !isTimeChart(dataLayers)) {
throw new Error(errors.minTimeBarIntervalNotForTimeBarChartError());
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,44 @@ describe('xyVis', () => {
});
});

test('it should throw error if minTimeBarInterval is invalid', async () => {
const { data, args } = sampleArgs();
const { layers, ...rest } = args;
const { layerId, layerType, table, type, ...restLayerArgs } = sampleLayer;
expect(
xyVisFunction.fn(
data,
{
...rest,
...restLayerArgs,
minTimeBarInterval: '1q',
referenceLineLayers: [],
annotationLayers: [],
},
createMockExecutionContext()
)
).rejects.toThrowErrorMatchingSnapshot();
});

test('it should throw error if minTimeBarInterval applied for not time bar chart', async () => {
const { data, args } = sampleArgs();
const { layers, ...rest } = args;
const { layerId, layerType, table, type, ...restLayerArgs } = sampleLayer;
expect(
xyVisFunction.fn(
data,
{
...rest,
...restLayerArgs,
minTimeBarInterval: '1h',
referenceLineLayers: [],
annotationLayers: [],
},
createMockExecutionContext()
)
).rejects.toThrowErrorMatchingSnapshot();
});

test('it should throw error if splitRowAccessor is pointing to the absent column', async () => {
const { data, args } = sampleArgs();
const { layers, ...rest } = args;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
validateExtent,
validateFillOpacity,
validateValueLabels,
validateMinTimeBarInterval,
} from './validate';

const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult => {
Expand Down Expand Up @@ -99,6 +100,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
validateExtent(args.yLeftExtent, hasBar || hasArea, dataLayers);
validateExtent(args.yRightExtent, hasBar || hasArea, dataLayers);
validateFillOpacity(args.fillOpacity, hasArea);
validateMinTimeBarInterval(dataLayers, hasBar, args.minTimeBarInterval);

const hasNotHistogramBars = !hasHistogramBarLayer(dataLayers);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
* Side Public License, v 1.
*/

export { appendLayerIds, getAccessors } from './layers';
export { appendLayerIds, getDataLayers, getAccessors } from './layers';
export { isTimeChart } from './visualization';
export { normalizeTable } from './table';
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Side Public License, v 1.
*/

import { generateLayerId, appendLayerIds } from './layers';
import { XYExtendedLayerConfigResult } from '../types';
import { generateLayerId, appendLayerIds, getDataLayers } from './layers';

describe('#generateLayerId', () => {
it('should return the combination of keyword and index', () => {
Expand Down Expand Up @@ -47,3 +48,28 @@ describe('#appendLayerIds', () => {
expect(layersWithIds).toStrictEqual(expectedLayerIds);
});
});

describe('#getDataLayers', () => {
it('should return only data layers', () => {
const layers: XYExtendedLayerConfigResult[] = [
{
type: 'extendedDataLayer',
layerType: 'data',
accessors: ['y'],
seriesType: 'bar',
xScaleType: 'time',
isHistogram: false,
table: { rows: [], columns: [], type: 'datatable' },
palette: { type: 'system_palette', name: 'system' },
},
{
type: 'extendedReferenceLineLayer',
layerType: 'referenceLine',
accessors: ['y'],
table: { rows: [], columns: [], type: 'datatable' },
},
];

expect(getDataLayers(layers)).toStrictEqual([layers[0]]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
*/

import { Datatable, PointSeriesColumnNames } from '@kbn/expressions-plugin/common';
import { WithLayerId } from '../types';
import { WithLayerId, ExtendedDataLayerConfig, XYExtendedLayerConfigResult } from '../types';
import { LayerTypes } from '../constants';

function isWithLayerId<T>(layer: T): layer is T & WithLayerId {
return (layer as T & WithLayerId).layerId ? true : false;
Expand All @@ -27,6 +28,13 @@ export function appendLayerIds<T>(
}));
}

export function getDataLayers(layers: XYExtendedLayerConfigResult[]) {
return layers.filter<ExtendedDataLayerConfig>(
(layer): layer is ExtendedDataLayerConfig =>
layer.layerType === LayerTypes.DATA || !layer.layerType
);
}

export function getAccessors<T, U extends { splitAccessor?: T; xAccessor?: T; accessors: T[] }>(
args: U,
table: Datatable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 { CommonXYDataLayerConfigResult } from '../types';
import { isTimeChart } from './visualization';

describe('#isTimeChart', () => {
it('should return true if all data layers have xAccessor collumn type `date` and scaleType is `time`', () => {
const layers: CommonXYDataLayerConfigResult[] = [
{
type: 'extendedDataLayer',
layerType: 'data',
xAccessor: 'x',
accessors: ['y'],
seriesType: 'bar',
xScaleType: 'time',
isHistogram: false,
table: {
rows: [],
columns: [
{
id: 'x',
name: 'x',
meta: {
type: 'date',
},
},
],
type: 'datatable',
},
palette: { type: 'system_palette', name: 'system' },
},
];

expect(isTimeChart(layers)).toBeTruthy();

layers[0].xScaleType = 'linear';

expect(isTimeChart(layers)).toBeFalsy();

layers[0].xScaleType = 'time';
layers[0].table.columns[0].meta.type = 'number';

expect(isTimeChart(layers)).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { XScaleTypes } from '../constants';
import { CommonXYDataLayerConfigResult } from '../types';

export function isTimeChart(layers: CommonXYDataLayerConfigResult[]) {
return layers.every<CommonXYDataLayerConfigResult>(
(l): l is CommonXYDataLayerConfigResult =>
l.table.columns.find((col) => col.id === l.xAccessor)?.meta.type === 'date' &&
l.xScaleType === XScaleTypes.TIME
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ export const strings = {
i18n.translate('expressionXY.xyVis.ariaLabel.help', {
defaultMessage: 'Specifies the aria label of the xy chart',
}),
getMinTimeBarIntervalHelp: () =>
i18n.translate('expressionXY.xyVis.xAxisInterval.help', {
defaultMessage: 'Specifies the min interval for time bar chart',
}),
getSplitColumnAccessorHelp: () =>
i18n.translate('expressionXY.xyVis.splitColumnAccessor.help', {
defaultMessage: 'Specifies split column of the xy chart',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export interface XYArgs extends DataLayerArgs {
hideEndzones?: boolean;
valuesInLegend?: boolean;
ariaLabel?: string;
minTimeBarInterval?: string;
splitRowAccessor?: ExpressionValueVisDimension | string;
splitColumnAccessor?: ExpressionValueVisDimension | string;
}
Expand Down Expand Up @@ -230,6 +231,7 @@ export interface LayeredXYArgs {
hideEndzones?: boolean;
valuesInLegend?: boolean;
ariaLabel?: string;
minTimeBarInterval?: string;
}

export interface XYProps {
Expand All @@ -255,6 +257,7 @@ export interface XYProps {
hideEndzones?: boolean;
valuesInLegend?: boolean;
ariaLabel?: string;
minTimeBarInterval?: string;
splitRowAccessor?: ExpressionValueVisDimension | string;
splitColumnAccessor?: ExpressionValueVisDimension | string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,18 @@ describe('calculateMinInterval', () => {
const result = await calculateMinInterval(xyProps);
expect(result).toEqual(undefined);
});

it('should return specified interval if user provided it as `xAxisInterval`', async () => {
layer.table.columns[2].meta.source = 'esaggs';
layer.table.columns[2].meta.sourceParams = {
type: 'date_histogram',
params: {
used_interval: '5m',
},
};
xyProps.args.layers[0] = layer;
xyProps.args.minTimeBarInterval = '1h';
const result = await calculateMinInterval(xyProps);
expect(result).toEqual(60 * 60 * 1000);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { XYChartProps } from '../../common';
import { getFilteredLayers } from './layers';
import { isDataLayer } from './visualization';

export function calculateMinInterval({ args: { layers } }: XYChartProps) {
export function calculateMinInterval({ args: { layers, minTimeBarInterval } }: XYChartProps) {
const filteredLayers = getFilteredLayers(layers);
if (filteredLayers.length === 0) return;
const isTimeViz = filteredLayers.every((l) => isDataLayer(l) && l.xScaleType === 'time');
Expand All @@ -22,6 +22,9 @@ export function calculateMinInterval({ args: { layers } }: XYChartProps) {
getColumnByAccessor(filteredLayers[0].xAccessor, filteredLayers[0].table.columns);

if (!xColumn) return;
if (minTimeBarInterval) {
return search.aggs.parseInterval(minTimeBarInterval)?.as('milliseconds');
}
if (!isTimeViz) {
const histogramInterval = search.aggs.getNumberHistogramIntervalByDatatableColumn(xColumn);
if (typeof histogramInterval === 'number') {
Expand Down

0 comments on commit fe04423

Please sign in to comment.