From fa4699fb8649c548810a6fdf5ddb332a89441e9c Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Thu, 2 Apr 2020 11:08:30 +0200 Subject: [PATCH] =?UTF-8?q?[7.x]=20[Lens]=20fix=20error=20for=20minInterva?= =?UTF-8?q?l>computedInterval=20for=20XY=E2=80=A6=20(#62260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xy_visualization/xy_expression.test.tsx | 310 ++++++++++++++---- .../public/xy_visualization/xy_expression.tsx | 9 +- 2 files changed, 251 insertions(+), 68 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx index cdc5fc2ff1c177..54abc2c2bb6673 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx @@ -18,6 +18,10 @@ import { } from '@elastic/charts'; import { xyChart, XYChart } from './xy_expression'; import { LensMultiTable } from '../types'; +import { + KibanaDatatable, + KibanaDatatableRow, +} from '../../../../../../src/plugins/expressions/public'; import React from 'react'; import { shallow } from 'enzyme'; import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerArgs } from './types'; @@ -26,57 +30,61 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; const executeTriggerActions = jest.fn(); +const createSampleDatatableWithRows = (rows: KibanaDatatableRow[]): KibanaDatatable => ({ + type: 'kibana_datatable', + columns: [ + { + id: 'a', + name: 'a', + formatHint: { id: 'number', params: { pattern: '0,0.000' } }, + }, + { id: 'b', name: 'b', formatHint: { id: 'number', params: { pattern: '000,0' } } }, + { + id: 'c', + name: 'c', + formatHint: { id: 'string' }, + meta: { type: 'date-histogram', aggConfigParams: { interval: '10s' } }, + }, + { id: 'd', name: 'ColD', formatHint: { id: 'string' } }, + ], + rows, +}); + +const sampleLayer: LayerArgs = { + layerId: 'first', + seriesType: 'line', + xAccessor: 'c', + accessors: ['a', 'b'], + splitAccessor: 'd', + columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}', + xScaleType: 'ordinal', + yScaleType: 'linear', + isHistogram: false, +}; + +const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({ + xTitle: '', + yTitle: '', + legend: { + type: 'lens_xy_legendConfig', + isVisible: false, + position: Position.Top, + }, + layers, +}); + function sampleArgs() { const data: LensMultiTable = { type: 'lens_multitable', tables: { - first: { - type: 'kibana_datatable', - columns: [ - { - id: 'a', - name: 'a', - formatHint: { id: 'number', params: { pattern: '0,0.000' } }, - }, - { id: 'b', name: 'b', formatHint: { id: 'number', params: { pattern: '000,0' } } }, - { - id: 'c', - name: 'c', - formatHint: { id: 'string' }, - meta: { type: 'date-histogram', aggConfigParams: { interval: '10s' } }, - }, - { id: 'd', name: 'ColD', formatHint: { id: 'string' } }, - ], - rows: [ - { a: 1, b: 2, c: 'I', d: 'Foo' }, - { a: 1, b: 5, c: 'J', d: 'Bar' }, - ], - }, + first: createSampleDatatableWithRows([ + { a: 1, b: 2, c: 'I', d: 'Foo' }, + { a: 1, b: 5, c: 'J', d: 'Bar' }, + ]), }, }; - const args: XYArgs = { - xTitle: '', - yTitle: '', - legend: { - type: 'lens_xy_legendConfig', - isVisible: false, - position: Position.Top, - }, - layers: [ - { - layerId: 'first', - seriesType: 'line', - xAccessor: 'c', - accessors: ['a', 'b'], - splitAccessor: 'd', - columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}', - xScaleType: 'ordinal', - yScaleType: 'linear', - isHistogram: false, - }, - ], - }; + const args: XYArgs = createArgsWithLayers(); return { data, args }; } @@ -158,35 +166,205 @@ describe('xy_expression', () => { expect(component.find(LineSeries)).toHaveLength(1); }); - test('it uses the full date range', () => { - const { data, args } = sampleArgs(); + describe('date range', () => { + const timeSampleLayer: LayerArgs = { + layerId: 'first', + seriesType: 'line', + xAccessor: 'c', + accessors: ['a', 'b'], + splitAccessor: 'd', + columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}', + xScaleType: 'time', + yScaleType: 'linear', + isHistogram: false, + }; + const multiLayerArgs = createArgsWithLayers([ + timeSampleLayer, + { + ...timeSampleLayer, + layerId: 'second', + seriesType: 'bar', + xScaleType: 'time', + }, + ]); + test('it uses the full date range', () => { + const { data, args } = sampleArgs(); + + const component = shallow( + + ); + expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` + Object { + "max": 1546491600000, + "min": 1546405200000, + "minInterval": undefined, + } + `); + }); - const component = shallow( - - ); - expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` + test('it generates correct xDomain for a layer with single value and a layer with no data (1-0) ', () => { + const data: LensMultiTable = { + type: 'lens_multitable', + tables: { + first: createSampleDatatableWithRows([{ a: 1, b: 2, c: 'I', d: 'Foo' }]), + second: createSampleDatatableWithRows([]), + }, + }; + + const component = shallow( + + ); + + expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` + Object { + "max": 1546491600000, + "min": 1546405200000, + "minInterval": 10000, + } + `); + }); + + test('it generates correct xDomain for two layers with single value(1-1)', () => { + const data: LensMultiTable = { + type: 'lens_multitable', + tables: { + first: createSampleDatatableWithRows([{ a: 1, b: 2, c: 'I', d: 'Foo' }]), + second: createSampleDatatableWithRows([{ a: 10, b: 5, c: 'J', d: 'Bar' }]), + }, + }; + const component = shallow( + + ); + + expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` Object { "max": 1546491600000, "min": 1546405200000, "minInterval": 10000, } `); + }); + test('it generates correct xDomain for a layer with single value and layer with multiple value data (1-n)', () => { + const data: LensMultiTable = { + type: 'lens_multitable', + tables: { + first: createSampleDatatableWithRows([{ a: 1, b: 2, c: 'I', d: 'Foo' }]), + second: createSampleDatatableWithRows([ + { a: 10, b: 5, c: 'J', d: 'Bar' }, + { a: 8, b: 5, c: 'K', d: 'Buzz' }, + ]), + }, + }; + const component = shallow( + + ); + + expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` + Object { + "max": 1546491600000, + "min": 1546405200000, + "minInterval": undefined, + } + `); + }); + + test('it generates correct xDomain for 2 layers with multiple value data (n-n)', () => { + const data: LensMultiTable = { + type: 'lens_multitable', + tables: { + first: createSampleDatatableWithRows([ + { a: 1, b: 2, c: 'I', d: 'Foo' }, + { a: 8, b: 5, c: 'K', d: 'Buzz' }, + { a: 9, b: 7, c: 'L', d: 'Bar' }, + { a: 10, b: 2, c: 'G', d: 'Bear' }, + ]), + second: createSampleDatatableWithRows([ + { a: 10, b: 5, c: 'J', d: 'Bar' }, + { a: 8, b: 4, c: 'K', d: 'Fi' }, + { a: 1, b: 8, c: 'O', d: 'Pi' }, + ]), + }, + }; + const component = shallow( + + ); + + expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` + Object { + "max": 1546491600000, + "min": 1546405200000, + "minInterval": undefined, + } + `); + }); }); test('it does not use date range if the x is not a time scale', () => { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx index a7d4b2a217f37a..f5798688badc5f 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -211,14 +211,19 @@ export function XYChart({ const shouldRotate = isHorizontalChart(layers); const xTitle = (xAxisColumn && xAxisColumn.name) || args.xTitle; - const interval = parseInterval(xAxisColumn?.meta?.aggConfigParams?.interval); + + // add minInterval only for single row value as it cannot be determined from dataset + + const minInterval = layers.every(layer => data.tables[layer.layerId].rows.length <= 1) + ? parseInterval(xAxisColumn?.meta?.aggConfigParams?.interval)?.asMilliseconds() + : undefined; const xDomain = data.dateRange && layers.every(l => l.xScaleType === 'time') ? { min: data.dateRange.fromDate.getTime(), max: data.dateRange.toDate.getTime(), - minInterval: interval?.asMilliseconds(), + minInterval, } : undefined; return (