From 3891bf36c1fe8829d66a9bf788998c133aecb07e Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 13 Jun 2019 12:04:32 +0200 Subject: [PATCH 01/17] pipe formatting informations through the expression --- .../common/types/kibana_datatable.ts | 1 + .../interpreter/public/functions/esaggs.ts | 11 ++- .../indexpattern_plugin/rename_columns.ts | 3 +- x-pack/plugins/lens/public/types.ts | 8 --- .../__snapshots__/xy_expression.test.tsx.snap | 18 +++-- .../xy_expression.test.tsx | 70 +++++++++++++++++-- .../xy_visualization_plugin/xy_expression.tsx | 18 ++++- 7 files changed, 104 insertions(+), 25 deletions(-) diff --git a/src/legacy/core_plugins/interpreter/common/types/kibana_datatable.ts b/src/legacy/core_plugins/interpreter/common/types/kibana_datatable.ts index d5622ff50dd83..169ce867f7080 100644 --- a/src/legacy/core_plugins/interpreter/common/types/kibana_datatable.ts +++ b/src/legacy/core_plugins/interpreter/common/types/kibana_datatable.ts @@ -24,6 +24,7 @@ const name = 'kibana_datatable'; interface Column { id: string; name: string; + formatterMapping?: unknown; } interface Row { diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts index c64464030f02b..090b3359c7a30 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts @@ -99,7 +99,7 @@ export const esaggs = (): ExpressionFunction = await courierRequestHandler({ + const response = await courierRequestHandler({ searchSource, aggs, timeRange: get(context, 'timeRange', null), @@ -112,13 +112,18 @@ export const esaggs = (): ExpressionFunction ({ + columns: response.columns.map((column: any) => ({ id: column.id, name: column.name, + formatterMapping: column.aggConfig.params.field + ? column.aggConfig.params.field.format.toJSON() + : column.aggConfig.type.getFormat().toJSON(), })), }; + + return table; }, }); diff --git a/x-pack/plugins/lens/public/indexpattern_plugin/rename_columns.ts b/x-pack/plugins/lens/public/indexpattern_plugin/rename_columns.ts index 1740d449b62cd..49c17b13e9d52 100644 --- a/x-pack/plugins/lens/public/indexpattern_plugin/rename_columns.ts +++ b/x-pack/plugins/lens/public/indexpattern_plugin/rename_columns.ts @@ -5,8 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; -import { KibanaDatatable } from '../types'; +import { ExpressionFunction, KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; interface RemapArgs { idMap: string; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index dc7f2e04609ef..a8dd23c3f262a 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -128,14 +128,6 @@ export interface TableSpecColumn { // TableSpec is managed by visualizations export type TableSpec = TableSpecColumn[]; -// This is a temporary type definition, to be replaced with -// the official Kibana Datatable type definition. -export interface KibanaDatatable { - type: 'kibana_datatable'; - rows: Array>; - columns: Array<{ id: string; name: string }>; -} - export interface VisualizationProps { dragDropContext: DragContextState; datasource: DatasourcePublicAPI; diff --git a/x-pack/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap index 85e0598114ce8..4392f633380a3 100644 --- a/x-pack/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap @@ -13,12 +13,14 @@ exports[`xy_expression XYChart component it renders area 1`] = ` id="x" position="bottom" showGridLines={false} + tickFormat={[Function]} title="C" /> { + const formatterSpy = jest.fn(); + const getFormatSpy = jest.fn(() => { + return { convert: formatterSpy }; + }); + return { getFormat: getFormatSpy }; +}); + +import { Position, Axis } from '@elastic/charts'; import { xyChart, XYChart } from './xy_expression'; -import { KibanaDatatable } from '../types'; import React from 'react'; import { shallow } from 'enzyme'; import { XYArgs, LegendConfig, legendConfig, XConfig, xConfig, YConfig, yConfig } from './types'; +import { getFormat } from '../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; +import { KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; function sampleArgs() { const data: KibanaDatatable = { type: 'kibana_datatable', - columns: [{ id: 'a', name: 'a' }, { id: 'b', name: 'b' }, { id: 'c', name: 'c' }], - rows: [{ a: 1, b: 2, c: 3 }, { a: 1, b: 5, c: 4 }], + columns: [ + { id: 'a', name: 'a', formatterMapping: { id: 'number', params: { pattern: '0,0.000' } } }, + { id: 'b', name: 'b', formatterMapping: { id: 'number', params: { pattern: '000,0' } } }, + { id: 'c', name: 'c', formatterMapping: { id: 'string' } }, + ], + rows: [{ a: 1, b: 2, c: 'I' }, { a: 1, b: 5, c: 'J' }], }; const args: XYArgs = { @@ -100,6 +113,10 @@ describe('xy_expression', () => { }); describe('XYChart component', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + test('it renders line', () => { const { data, args } = sampleArgs(); @@ -123,5 +140,50 @@ describe('xy_expression', () => { shallow() ).toMatchSnapshot(); }); + + test('it gets the formatter for the x axis', () => { + const { data, args } = sampleArgs(); + + shallow(); + + expect(getFormat as jest.Mock).toHaveBeenCalledWith({ id: 'string' }); + }); + + test('it gets a default formatter for y if there are multiple y accessors', () => { + const { data, args } = sampleArgs(); + + shallow(); + + expect(getFormat as jest.Mock).toHaveBeenCalledTimes(2); + expect(getFormat as jest.Mock).toHaveBeenCalledWith({ id: 'number' }); + }); + + test('it gets the formatter for the y axis if there is only one accessor', () => { + const { data, args } = sampleArgs(); + + shallow( + + ); + + expect(getFormat as jest.Mock).toHaveBeenCalledTimes(2); + expect(getFormat as jest.Mock).toHaveBeenCalledWith({ + id: 'number', + params: { pattern: '0,0.000' }, + }); + }); + + test('it should pass the formatter function to the axis', () => { + const { data, args } = sampleArgs(); + + const instance = shallow(); + + const tickFormatter = instance + .find(Axis) + .first() + .prop('tickFormat'); + tickFormatter('I'); + + expect(getFormat({}).convert as jest.Mock).toHaveBeenCalledWith('I'); + }); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index 9b2b9290b54f1..e2937152b6f89 100644 --- a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -16,10 +16,10 @@ import { AreaSeries, BarSeries, } from '@elastic/charts'; -import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; +import { ExpressionFunction, KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; import { XYArgs } from './types'; -import { KibanaDatatable } from '../types'; import { RenderFunction } from './plugin'; +import { getFormat } from '../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; export interface XYChartProps { data: KibanaDatatable; @@ -112,6 +112,18 @@ export function XYChart({ data, args }: XYChartProps) { data: data.rows, }; + const xAxisColumn = data.columns.find(({ id }) => id === x.accessor); + const xAxisFormatter = getFormat(xAxisColumn ? xAxisColumn.formatterMapping : undefined); + + let yAxisFormatter: ReturnType; + if (y.accessors.length === 1) { + const firstYAxisColumn = data.columns.find(({ id }) => id === y.accessors[0]); + yAxisFormatter = getFormat(firstYAxisColumn ? firstYAxisColumn.formatterMapping : undefined); + } else { + // TODO if there are multiple y axes, try to merge the formatters for the axis + yAxisFormatter = getFormat({ id: 'number' }); + } + return ( @@ -121,6 +133,7 @@ export function XYChart({ data, args }: XYChartProps) { position={x.position} title={x.title} showGridLines={x.showGridlines} + tickFormat={d => xAxisFormatter.convert(d)} /> yAxisFormatter.convert(d)} /> {seriesType === 'line' ? ( From 8c91d6cd0bc2e530b10a50341c85bbda762abeef Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 24 Jun 2019 09:27:42 +0200 Subject: [PATCH 02/17] add robustness --- .../interpreter/public/functions/esaggs.ts | 13 ++++++++++--- .../xy_visualization_plugin/xy_expression.test.tsx | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts index 090b3359c7a30..c5b0c148a1775 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts @@ -52,6 +52,15 @@ interface Arguments { type Return = Promise; +function getFormatter(aggConfig: any) { + if (aggConfig.params && aggConfig.params.field && aggConfig.params.field.format) { + return aggConfig.params.field.format.toJSON(); + } + if (aggConfig.type && aggConfig.type.getFormat()) { + return aggConfig.type.getFormat().toJSON(); + } +} + export const esaggs = (): ExpressionFunction => ({ name, type: 'kibana_datatable', @@ -118,9 +127,7 @@ export const esaggs = (): ExpressionFunction ({ id: column.id, name: column.name, - formatterMapping: column.aggConfig.params.field - ? column.aggConfig.params.field.format.toJSON() - : column.aggConfig.type.getFormat().toJSON(), + formatterMapping: getFormatter(column.aggConfig), })), }; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index f35fe1c68f07e..e8d2fccbf8c4a 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -17,8 +17,8 @@ import { xyChart, XYChart } from './xy_expression'; import React from 'react'; import { shallow } from 'enzyme'; import { XYArgs, LegendConfig, legendConfig, XConfig, xConfig, YConfig, yConfig } from './types'; -import { getFormat } from '../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; -import { KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; +import { getFormat } from '../../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; +import { KibanaDatatable } from 'src/legacy/core_plugins/interpreter/common'; function sampleArgs() { const data: KibanaDatatable = { From e692f9c4fc55391a0496015615883ec85371c65d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 24 Jun 2019 12:05:55 +0200 Subject: [PATCH 03/17] fix import path --- .../lens/public/xy_visualization_plugin/xy_expression.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index e2937152b6f89..cad99cd91669c 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -19,7 +19,7 @@ import { import { ExpressionFunction, KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; import { XYArgs } from './types'; import { RenderFunction } from './plugin'; -import { getFormat } from '../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; +import { getFormat } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; export interface XYChartProps { data: KibanaDatatable; From eeaa86d59e224c271479bfd209108c8db3517d2d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 24 Jun 2019 14:41:38 +0200 Subject: [PATCH 04/17] fix references in tests --- .../xy_expression.test.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index e8d2fccbf8c4a..507d12bf4cff9 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -4,20 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities', () => { - const formatterSpy = jest.fn(); - const getFormatSpy = jest.fn(() => { - return { convert: formatterSpy }; - }); - return { getFormat: getFormatSpy }; -}); +jest.mock( + '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities', + () => { + const formatterSpy = jest.fn(); + const getFormatSpy = jest.fn(() => { + return { convert: formatterSpy }; + }); + return { getFormat: getFormatSpy }; + } +); import { Position, Axis } from '@elastic/charts'; import { xyChart, XYChart } from './xy_expression'; import React from 'react'; import { shallow } from 'enzyme'; import { XYArgs, LegendConfig, legendConfig, XConfig, xConfig, YConfig, yConfig } from './types'; -import { getFormat } from '../../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; +import { getFormat } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; import { KibanaDatatable } from 'src/legacy/core_plugins/interpreter/common'; function sampleArgs() { From 508813339e579ccd9405666890305822414a6e18 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 29 Jul 2019 15:15:34 +0200 Subject: [PATCH 05/17] pass through scale type to xy visualization --- .../__snapshots__/xy_expression.test.tsx.snap | 9 +++- .../xy_visualization.test.ts.snap | 6 +++ .../xy_visualization_plugin/to_expression.ts | 39 +++++++++++++- .../public/xy_visualization_plugin/types.ts | 12 +++++ .../xy_expression.test.tsx | 10 +++- .../xy_visualization_plugin/xy_expression.tsx | 51 ++++++++++++++++--- .../xy_visualization_plugin/xy_suggestions.ts | 16 +++++- .../xy_visualization.test.ts | 9 +++- 8 files changed, 138 insertions(+), 14 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap index 71fb202a5cf3c..32c2ae079ab72 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap @@ -15,6 +15,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` id="x" position="bottom" showGridLines={false} + tickFormat={[Function]} title="" /> -`; \ No newline at end of file +`; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_visualization.test.ts.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_visualization.test.ts.snap index e27bcbf9c1d6f..aee7784e8acfb 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_visualization.test.ts.snap +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_visualization.test.ts.snap @@ -38,6 +38,12 @@ Object { "xAccessor": Array [ "a", ], + "xScaleType": Array [ + "linear", + ], + "yScaleType": Array [ + "linear", + ], }, "function": "lens_xy_layer", "type": "function", diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts index 2405b0a0d9ea1..77af9263e3bcc 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts @@ -5,8 +5,9 @@ */ import { Ast } from '@kbn/interpreter/common'; +import { ScaleType } from '@elastic/charts'; import { State, LayerConfig } from './types'; -import { FramePublicAPI } from '../types'; +import { FramePublicAPI, DataType } from '../types'; function xyTitles(layer: LayerConfig, frame: FramePublicAPI) { const defaults = { @@ -55,12 +56,44 @@ export const toExpression = (state: State, frame: FramePublicAPI): Ast | null => }), }; - return buildExpression(stateWithValidAccessors, xyTitles(state.layers[0], frame), frame); + const dataTypes: Record> = {}; + state.layers.forEach(layer => { + dataTypes[layer.layerId] = {}; + const datasource = frame.datasourceLayers[layer.layerId]; + datasource.getTableSpec().forEach(column => { + const operation = frame.datasourceLayers[layer.layerId].getOperationForColumnId( + column.columnId + ); + if (operation) { + dataTypes[layer.layerId][column.columnId] = operation.dataType; + } + }); + }); + + return buildExpression( + stateWithValidAccessors, + xyTitles(state.layers[0], frame), + dataTypes, + frame + ); }; +export function getScaleType(dataType: DataType) { + switch (dataType) { + case 'boolean': + case 'string': + return ScaleType.Ordinal; + case 'date': + return ScaleType.Time; + default: + return ScaleType.Linear; + } +} + export const buildExpression = ( state: State, { xTitle, yTitle }: { xTitle: string; yTitle: string }, + dataTypes: Record>, frame?: FramePublicAPI ): Ast => ({ type: 'expression', @@ -113,6 +146,8 @@ export const buildExpression = ( hide: [Boolean(layer.hide)], xAccessor: [layer.xAccessor], + yScaleType: [getScaleType(dataTypes[layer.layerId][layer.accessors[0]])], + xScaleType: [getScaleType(dataTypes[layer.layerId][layer.xAccessor])], splitAccessor: [layer.splitAccessor], seriesType: [layer.seriesType], accessors: layer.accessors, diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts index c5650fde3ddac..73a3a48b115db 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts @@ -129,6 +129,16 @@ export const layerConfig: ExpressionFunction< options: ['bar', 'line', 'area', 'bar_stacked', 'area_stacked'], help: 'The type of chart to display.', }, + xScaleType: { + options: ['ordinal', 'linear', 'time'], + help: 'The scale type of the x axis', + default: 'ordinal', + }, + yScaleType: { + options: ['log', 'sqrt', 'linear', 'time'], + help: 'The scale type of the y axes', + default: 'linear', + }, splitAccessor: { types: ['string'], help: 'The column to split by', @@ -164,6 +174,8 @@ export type LayerConfig = AxisConfig & { export type LayerArgs = LayerConfig & { columnToLabel?: string; // Actually a JSON key-value pair + yScaleType: 'time' | 'linear' | 'log' | 'sqrt'; + xScaleType: 'time' | 'linear' | 'ordinal'; }; // Arguments to XY chart expression, with computed properties diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index b21b3229efd57..5bd356a07150c 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -9,7 +9,7 @@ import { xyChart, XYChart } from './xy_expression'; import { LensMultiTable } from '../types'; import React from 'react'; import { shallow } from 'enzyme'; -import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerConfig } from './types'; +import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerArgs } from './types'; function sampleArgs() { const data: LensMultiTable = { @@ -40,6 +40,8 @@ function sampleArgs() { title: 'A and B', splitAccessor: 'd', columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}', + xScaleType: 'linear', + yScaleType: 'linear', }, ], }; @@ -62,13 +64,15 @@ describe('xy_expression', () => { }); test('layerConfig produces the correct arguments', () => { - const args: LayerConfig = { + const args: LayerArgs = { layerId: 'first', seriesType: 'line', xAccessor: 'c', accessors: ['a', 'b'], title: 'A and B', splitAccessor: 'd', + xScaleType: 'linear', + yScaleType: 'linear', }; expect(layerConfig.fn(null, args, {})).toEqual({ @@ -223,6 +227,8 @@ describe('xy_expression', () => { }, }; + args.layers[0].xScaleType = 'ordinal'; + const component = shallow(); expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Ordinal); }); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index de122bcb0dd24..170c659459135 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -16,13 +16,13 @@ import { AreaSeries, BarSeries, Position, - ScaleType, + niceTimeFormatter, } from '@elastic/charts'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, IconType } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { LensMultiTable } from '../types'; -import { XYArgs, SeriesType } from './types'; +import { XYArgs, SeriesType, LayerArgs } from './types'; import { RenderFunction } from '../interpreter_types'; import { chartTypeIcons } from './xy_config_panel'; @@ -100,6 +100,33 @@ function getIconForSeriesType(seriesType: SeriesType): IconType { return chartTypeIcons.find(chartTypeIcon => chartTypeIcon.id === seriesType)!.iconType; } +function getTickFormatter(layers: LayerArgs[], data: LensMultiTable) { + const isDateXAxis = Object.values(layers).every(layer => layer.xScaleType === 'time'); + + function ofAllXValues(fn: (...args: number[]) => number) { + return fn.apply( + undefined, + Object.values(layers).map( + layer => + data.tables && + data.tables[layer.layerId] && + fn.apply( + undefined, + data.tables[layer.layerId].rows.map(row => row[layer.xAccessor] as number) + ) + ) + ); + } + + if (!isDateXAxis) { + return (x: string) => x; + } else { + const minDate = ofAllXValues(Math.min); + const maxDate = ofAllXValues(Math.max); + return niceTimeFormatter([minDate, maxDate]); + } +} + export function XYChart({ data, args }: XYChartProps) { const { legend, layers, isHorizontal } = args; @@ -137,6 +164,7 @@ export function XYChart({ data, args }: XYChartProps) { title={args.xTitle} showGridLines={false} hide={layers[0].hide} + tickFormat={getTickFormatter(layers, data)} /> {layers.map( - ({ splitAccessor, seriesType, accessors, xAccessor, layerId, columnToLabel }, index) => { + ( + { + splitAccessor, + seriesType, + accessors, + xAccessor, + layerId, + columnToLabel, + yScaleType, + xScaleType, + }, + index + ) => { if (!data.tables[layerId] || data.tables[layerId].rows.length === 0) { return; } @@ -181,9 +221,8 @@ export function XYChart({ data, args }: XYChartProps) { xAccessor, yAccessors, data: rows, - xScaleType: - typeof rows[0][xAccessor] === 'number' ? ScaleType.Linear : ScaleType.Ordinal, - yScaleType: ScaleType.Linear, + xScaleType, + yScaleType, }; return seriesType === 'line' ? ( diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts index 54015633baeac..cffbeeba501d3 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts @@ -11,6 +11,7 @@ import { VisualizationSuggestion, TableSuggestionColumn, TableSuggestion, + DataType, } from '../types'; import { State } from './types'; import { generateId } from '../id_generator'; @@ -117,6 +118,14 @@ function getSuggestion( ], }; + const dataTypes: Record = {}; + + [xValue, ...yValues, splitBy].forEach(col => { + if (col) { + dataTypes[col.columnId] = col.operation.dataType; + } + }); + return { title, score: 1, @@ -126,13 +135,16 @@ function getSuggestion( previewExpression: buildExpression( { ...state, - layers: state.layers.map(layer => ({ ...layer, hide: true })), + layers: state.layers + .filter(layer => layer.layerId === layerId) + .map(layer => ({ ...layer, hide: true })), legend: { ...state.legend, isVisible: false, }, }, - { xTitle, yTitle } + { xTitle, yTitle }, + { [layerId]: dataTypes } ), }; } diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts index d8854764ff898..85cd9b079314c 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts @@ -93,8 +93,15 @@ describe('xy_visualization', () => { frame = createMockFramePublicAPI(); mockDatasource = createMockDatasource(); + mockDatasource.publicAPIMock.getTableSpec.mockReturnValue([ + { columnId: 'd' }, + { columnId: 'a' }, + { columnId: 'b' }, + { columnId: 'c' }, + ]); + mockDatasource.publicAPIMock.getOperationForColumnId.mockImplementation(col => { - return { label: `col_${col}` } as Operation; + return { label: `col_${col}`, dataType: 'number' } as Operation; }); frame.datasourceLayers = { From 76b25ff824e5c2a48647092b7224b6e525cc2044 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 8 Aug 2019 07:11:20 +0200 Subject: [PATCH 06/17] use same formats as in pipeline loader --- .../interpreter/public/functions/esaggs.ts | 42 +++++++++++++++---- .../expression.tsx | 18 +++++++- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts index 1d34591436daa..77a17162d71ce 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts @@ -36,6 +36,7 @@ import chrome from 'ui/chrome'; const courierRequestHandlerProvider = CourierRequestHandlerProvider; const courierRequestHandler = courierRequestHandlerProvider().handler; +import { AggConfig } from 'ui/vis'; import { ExpressionFunction } from '../../types'; import { KibanaContext, KibanaDatatable } from '../../common'; @@ -52,14 +53,37 @@ interface Arguments { type Return = Promise; -function getFormatter(aggConfig: any) { - if (aggConfig.params && aggConfig.params.field && aggConfig.params.field.format) { - return aggConfig.params.field.format.toJSON(); - } - if (aggConfig.type && aggConfig.type.getFormat()) { - return aggConfig.type.getFormat().toJSON(); - } -} +// TODO copied from build_pipeline, this should be unified +const createFormat = (agg: AggConfig) => { + const format: any = agg.params.field ? agg.params.field.format.toJSON() : {}; + const formats: any = { + date_range: () => ({ id: 'string' }), + percentile_ranks: () => ({ id: 'percent' }), + count: () => ({ id: 'number' }), + cardinality: () => ({ id: 'number' }), + date_histogram: () => ({ + id: 'date', + params: { + pattern: agg.buckets.getScaledDateFormat(), + }, + }), + terms: () => ({ + id: 'terms', + params: { + id: format.id, + otherBucketLabel: agg.params.otherBucketLabel, + missingBucketLabel: agg.params.missingBucketLabel, + ...format.params, + }, + }), + range: () => ({ + id: 'range', + params: { id: format.id, ...format.params }, + }), + }; + + return formats[agg.type.name] ? formats[agg.type.name]() : format; +}; export const esaggs = (): ExpressionFunction => ({ name, @@ -127,7 +151,7 @@ export const esaggs = (): ExpressionFunction ({ id: column.id, name: column.name, - formatterMapping: getFormatter(column.aggConfig), + formatterMapping: createFormat(column.aggConfig), })), }; diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx index 076bb1c20ac10..133e2644c6c29 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx @@ -11,6 +11,7 @@ import { EuiBasicTable } from '@elastic/eui'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; import { KibanaDatatable, LensMultiTable } from '../types'; import { RenderFunction } from '../interpreter_types'; +import { getFormat } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; export interface DatatableColumns { columnIds: string[]; @@ -121,6 +122,11 @@ export const datatableRenderer: RenderFunction = { function DatatableComponent(props: DatatableProps) { const [firstTable] = Object.values(props.data.tables); + const formatters: Record> = {}; + + firstTable.columns.forEach(column => { + formatters[column.id] = getFormat(column.formatterMapping || undefined); + }); return ( !!field)} - items={firstTable ? firstTable.rows : []} + items={ + firstTable + ? firstTable.rows.map(row => { + const formattedRow: Record = {}; + Object.entries(formatters).forEach(([columnId, formatter]) => { + formattedRow[columnId] = formatter.convert(row[columnId]); + }); + return formattedRow; + }) + : [] + } /> ); } From ef698ac41ed85f30b5e23adc191821f9a2fc4846 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 8 Aug 2019 07:46:57 +0200 Subject: [PATCH 07/17] fix bugs and make formats optional --- .../interpreter/public/functions/esaggs.ts | 21 ++++++++++++++----- .../indexpattern_plugin/to_expression.ts | 1 + x-pack/legacy/plugins/lens/public/types.ts | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts index 77a17162d71ce..15f7057e573a9 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts @@ -48,6 +48,7 @@ interface Arguments { index: string | null; metricsAtAllLevels: boolean; partialRows: boolean; + includeFormats: boolean; aggConfigs: string; } @@ -110,6 +111,11 @@ export const esaggs = (): ExpressionFunction ({ - id: column.id, - name: column.name, - formatterMapping: createFormat(column.aggConfig), - })), + columns: response.columns.map((column: any) => { + const cleanedColumn: KibanaDatatable['columns'][0] = { + id: column.id, + name: column.name, + }; + if (args.includeFormats) { + cleanedColumn.formatterMapping = createFormat(column.aggConfig); + } + return cleanedColumn; + }), }; return table; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts index 46095cd434335..e39222bc8059b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts @@ -74,6 +74,7 @@ function getExpressionForLayer( index="${indexPattern.id}" metricsAtAllLevels=false partialRows=false + includeFormats=true aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify(idMap)}'`; } diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index f2bffb96132d4..34aeb6fd4a148 100644 --- a/x-pack/legacy/plugins/lens/public/types.ts +++ b/x-pack/legacy/plugins/lens/public/types.ts @@ -172,7 +172,7 @@ export interface LensMultiTable { export interface KibanaDatatable { type: 'kibana_datatable'; rows: Array>; - columns: Array<{ id: string; name: string; formatterMapping: unknown; }>; + columns: Array<{ id: string; name: string; formatterMapping: unknown }>; } export interface VisualizationProps { From eaa93f61d5bc23d05a07977c03bc9788bcb6f2da Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 9 Aug 2019 16:14:12 +0200 Subject: [PATCH 08/17] clean up field formatters --- .../interpreter/public/functions/esaggs.ts | 42 ++-------- .../loader/pipeline_helpers/build_pipeline.ts | 39 +-------- .../loader/pipeline_helpers/utilities.ts | 71 +++++++++++++--- .../expression_types/kibana_datatable.ts | 3 +- .../expression.tsx | 20 ++--- .../datatable_visualization_plugin/plugin.tsx | 20 ++++- .../editor_frame_plugin/merge_tables.ts | 4 +- .../indexpattern_plugin/filter_ratio.ts | 3 +- .../indexpattern_plugin/indexpattern.test.tsx | 1 + .../indexpattern_plugin/to_expression.ts | 2 +- .../public/persistence/saved_object_store.ts | 2 +- x-pack/legacy/plugins/lens/public/types.ts | 10 +-- .../__snapshots__/xy_expression.test.tsx.snap | 76 +++++++++-------- .../public/xy_visualization_plugin/plugin.tsx | 17 +++- .../xy_expression.test.tsx | 81 ++++++++++--------- .../xy_visualization_plugin/xy_expression.tsx | 26 +++--- 16 files changed, 221 insertions(+), 196 deletions(-) diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts index 15f7057e573a9..c788d4a88d781 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts @@ -36,7 +36,7 @@ import chrome from 'ui/chrome'; const courierRequestHandlerProvider = CourierRequestHandlerProvider; const courierRequestHandler = courierRequestHandlerProvider().handler; -import { AggConfig } from 'ui/vis'; +import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { ExpressionFunction } from '../../types'; import { KibanaContext, KibanaDatatable } from '../../common'; @@ -48,44 +48,12 @@ interface Arguments { index: string | null; metricsAtAllLevels: boolean; partialRows: boolean; - includeFormats: boolean; + includeFormatHints: boolean; aggConfigs: string; } type Return = Promise; -// TODO copied from build_pipeline, this should be unified -const createFormat = (agg: AggConfig) => { - const format: any = agg.params.field ? agg.params.field.format.toJSON() : {}; - const formats: any = { - date_range: () => ({ id: 'string' }), - percentile_ranks: () => ({ id: 'percent' }), - count: () => ({ id: 'number' }), - cardinality: () => ({ id: 'number' }), - date_histogram: () => ({ - id: 'date', - params: { - pattern: agg.buckets.getScaledDateFormat(), - }, - }), - terms: () => ({ - id: 'terms', - params: { - id: format.id, - otherBucketLabel: agg.params.otherBucketLabel, - missingBucketLabel: agg.params.missingBucketLabel, - ...format.params, - }, - }), - range: () => ({ - id: 'range', - params: { id: format.id, ...format.params }, - }), - }; - - return formats[agg.type.name] ? formats[agg.type.name]() : format; -}; - export const esaggs = (): ExpressionFunction => ({ name, type: 'kibana_datatable', @@ -111,7 +79,7 @@ export const esaggs = (): ExpressionFunction { - const createFormat = (agg: AggConfig): SchemaFormat => { - const format: SchemaFormat = agg.params.field ? agg.params.field.format.toJSON() : {}; - const formats: any = { - date_range: () => ({ id: 'string' }), - percentile_ranks: () => ({ id: 'percent' }), - count: () => ({ id: 'number' }), - cardinality: () => ({ id: 'number' }), - date_histogram: () => ({ - id: 'date', - params: { - pattern: agg.buckets.getScaledDateFormat(), - }, - }), - terms: () => ({ - id: 'terms', - params: { - id: format.id, - otherBucketLabel: agg.params.otherBucketLabel, - missingBucketLabel: agg.params.missingBucketLabel, - ...format.params, - }, - }), - range: () => ({ - id: 'range', - params: { id: format.id, ...format.params }, - }), - }; - - return formats[agg.type.name] ? formats[agg.type.name]() : format; - }; - const createSchemaConfig = (accessor: number, agg: AggConfig): SchemaConfig => { if (agg.type.name === 'date_histogram') { agg.params.timeRange = timeRange; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index 20128eb5a3a64..e440d3678a6d9 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -28,12 +28,29 @@ import chrome from '../../../chrome'; // @ts-ignore import { fieldFormats } from '../../../registry/field_formats'; +interface TermsFieldFormatParams { + otherBucketLabel: string; + missingBucketLabel: string; + id: string; +} + +function isTermsFieldFormat( + serializedFieldFormat: SerializedFieldFormat +): serializedFieldFormat is SerializedFieldFormat { + return serializedFieldFormat.id === 'terms'; +} + +export interface SerializedFieldFormat { + id?: string; + params?: TParams; +} + const config = chrome.getUiSettingsClient(); const getConfig = (...args: any[]): any => config.get(...args); const getDefaultFieldFormat = () => ({ convert: identity }); -const getFieldFormat = (id: string, params: object) => { +const getFieldFormat = (id: string | undefined, params: object = {}) => { const Format = fieldFormats.byId[id]; if (Format) { return new Format(params, getConfig); @@ -42,7 +59,42 @@ const getFieldFormat = (id: string, params: object) => { } }; -export const getFormat = (mapping: any) => { +export type FieldFormat = any; + +export const createFormat = (agg: AggConfig): SerializedFieldFormat => { + const format: SerializedFieldFormat = agg.params.field ? agg.params.field.format.toJSON() : {}; + const formats: Record SerializedFieldFormat> = { + date_range: () => ({ id: 'string' }), + percentile_ranks: () => ({ id: 'percent' }), + count: () => ({ id: 'number' }), + cardinality: () => ({ id: 'number' }), + date_histogram: () => ({ + id: 'date', + params: { + pattern: agg.buckets.getScaledDateFormat(), + }, + }), + terms: () => ({ + id: 'terms', + params: { + id: format.id, + otherBucketLabel: agg.params.otherBucketLabel, + missingBucketLabel: agg.params.missingBucketLabel, + ...format.params, + }, + }), + range: () => ({ + id: 'range', + params: { id: format.id, ...format.params }, + }), + }; + + return formats[agg.type.name] ? formats[agg.type.name]() : format; +}; + +export type FormatFactory = (mapping?: SerializedFieldFormat) => FieldFormat; + +export const getFormat: FormatFactory = (mapping: SerializedFieldFormat = {}): FieldFormat => { if (!mapping) { return getDefaultFieldFormat(); } @@ -59,16 +111,17 @@ export const getFormat = (mapping: any) => { }); }); return new RangeFormat(); - } else if (id === 'terms') { + } else if (isTermsFieldFormat(mapping) && mapping.params) { + const params = mapping.params; return { getConverterFor: (type: string) => { - const format = getFieldFormat(mapping.params.id, mapping.params); + const format = getFieldFormat(params.id, mapping.params); return (val: string) => { if (val === '__other__') { - return mapping.params.otherBucketLabel; + return params.otherBucketLabel; } if (val === '__missing__') { - return mapping.params.missingBucketLabel; + return params.missingBucketLabel; } const parsedUrl = { origin: window.location.origin, @@ -79,12 +132,12 @@ export const getFormat = (mapping: any) => { }; }, convert: (val: string, type: string) => { - const format = getFieldFormat(mapping.params.id, mapping.params); + const format = getFieldFormat(params.id, mapping.params); if (val === '__other__') { - return mapping.params.otherBucketLabel; + return params.otherBucketLabel; } if (val === '__missing__') { - return mapping.params.missingBucketLabel; + return params.missingBucketLabel; } const parsedUrl = { origin: window.location.origin, diff --git a/src/plugins/data/common/expressions/expression_types/kibana_datatable.ts b/src/plugins/data/common/expressions/expression_types/kibana_datatable.ts index 169ce867f7080..f44819ebd8c3e 100644 --- a/src/plugins/data/common/expressions/expression_types/kibana_datatable.ts +++ b/src/plugins/data/common/expressions/expression_types/kibana_datatable.ts @@ -18,13 +18,14 @@ */ import { map } from 'lodash'; +import { SerializedFieldFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; const name = 'kibana_datatable'; interface Column { id: string; name: string; - formatterMapping?: unknown; + formatHint?: SerializedFieldFormat; } interface Row { diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx index 133e2644c6c29..0e53ee59761f5 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx @@ -8,10 +8,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable } from '@elastic/eui'; -import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; -import { KibanaDatatable, LensMultiTable } from '../types'; +import { ExpressionFunction, KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; +import { LensMultiTable } from '../types'; import { RenderFunction } from '../interpreter_types'; -import { getFormat } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; +import { FormatFactory } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; export interface DatatableColumns { columnIds: string[]; @@ -107,7 +107,9 @@ export const datatableColumns: ExpressionFunction< }, }; -export const datatableRenderer: RenderFunction = { +export const getDatatableRenderer = ( + formatFactory: FormatFactory +): RenderFunction => ({ name: 'lens_datatable_renderer', displayName: i18n.translate('xpack.lens.datatable.visualizationName', { defaultMessage: 'Datatable', @@ -116,16 +118,16 @@ export const datatableRenderer: RenderFunction = { validate: () => {}, reuseDomNode: true, render: async (domNode: Element, config: DatatableProps, _handlers: unknown) => { - ReactDOM.render(, domNode); + ReactDOM.render(, domNode); }, -}; +}); -function DatatableComponent(props: DatatableProps) { +function DatatableComponent(props: DatatableProps & { formatFactory: FormatFactory }) { const [firstTable] = Object.values(props.data.tables); - const formatters: Record> = {}; + const formatters: Record> = {}; firstTable.columns.forEach(column => { - formatters[column.id] = getFormat(column.formatterMapping || undefined); + formatters[column.id] = props.formatFactory(column.formatHint); }); return ( diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/plugin.tsx index 7bcddae13e1ac..52f4f99513e7a 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/plugin.tsx @@ -6,6 +6,7 @@ // import { Registry } from '@kbn/interpreter/target/common'; import { CoreSetup } from 'src/core/public'; +import { getFormat, FormatFactory } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { datatableVisualization } from './visualization'; import { @@ -13,19 +14,29 @@ import { functionsRegistry, } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { InterpreterSetup, RenderFunction } from '../interpreter_types'; -import { datatable, datatableColumns, datatableRenderer } from './expression'; +import { datatable, datatableColumns, getDatatableRenderer } from './expression'; export interface DatatableVisualizationPluginSetupPlugins { interpreter: InterpreterSetup; + // TODO this is a simulated NP plugin. + // Once field formatters are actually migrated, the actual shim can be used + fieldFormat: { + formatFactory: FormatFactory; + }; } class DatatableVisualizationPlugin { constructor() {} - setup(_core: CoreSetup | null, { interpreter }: DatatableVisualizationPluginSetupPlugins) { + setup( + _core: CoreSetup | null, + { interpreter, fieldFormat }: DatatableVisualizationPluginSetupPlugins + ) { interpreter.functionsRegistry.register(() => datatableColumns); interpreter.functionsRegistry.register(() => datatable); - interpreter.renderersRegistry.register(() => datatableRenderer as RenderFunction); + interpreter.renderersRegistry.register( + () => getDatatableRenderer(fieldFormat.formatFactory) as RenderFunction + ); return datatableVisualization; } @@ -41,5 +52,8 @@ export const datatableVisualizationSetup = () => renderersRegistry, functionsRegistry, }, + fieldFormat: { + formatFactory: getFormat, + }, }); export const datatableVisualizationStop = () => plugin.stop(); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.ts index c7747ace106fd..2b7e35876bb63 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.ts +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.ts @@ -5,8 +5,8 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; -import { LensMultiTable, KibanaDatatable } from '../types'; +import { ExpressionFunction, KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; +import { LensMultiTable } from '../types'; interface MergeTables { layerIds: string[]; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts index 1fe57f42fc987..854284f98a8d0 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts @@ -5,8 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; -import { KibanaDatatable } from '../types'; +import { ExpressionFunction, KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; interface FilterRatioKey { id: string; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx index b7d761b42030a..2ab26455a9f7a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx @@ -243,6 +243,7 @@ describe('IndexPattern Data Source', () => { index=\\"1\\" metricsAtAllLevels=false partialRows=false + includeFormatHints=true aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":1,\\"extended_bounds\\":{}}}]' | lens_rename_columns idMap='{\\"col-0-col1\\":\\"col1\\",\\"col-1-col2\\":\\"col2\\"}'" `); }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts index e39222bc8059b..71e8473aa6839 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts @@ -74,7 +74,7 @@ function getExpressionForLayer( index="${indexPattern.id}" metricsAtAllLevels=false partialRows=false - includeFormats=true + includeFormatHints=true aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify(idMap)}'`; } diff --git a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts index 5846565dd05a7..b87ca1aa4f868 100644 --- a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts +++ b/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectAttributes } from 'target/types/server'; import { Filter } from '@kbn/es-query'; import { Query } from 'src/plugins/data/common'; +import { SavedObjectAttributes } from 'src/core/server'; export interface Document { id?: string; diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index 34aeb6fd4a148..6deab82c5658e 100644 --- a/x-pack/legacy/plugins/lens/public/types.ts +++ b/x-pack/legacy/plugins/lens/public/types.ts @@ -6,7 +6,7 @@ import { Ast } from '@kbn/interpreter/common'; import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -import { Query } from 'src/plugins/data/common'; +import { Query, KibanaDatatable } from 'src/plugins/data/common'; import { DragContextState } from './drag_drop'; import { Document } from './persistence'; @@ -167,14 +167,6 @@ export interface LensMultiTable { tables: Record; } -// This is a temporary type definition, to be replaced with -// the official Kibana Datatable type definition. -export interface KibanaDatatable { - type: 'kibana_datatable'; - rows: Array>; - columns: Array<{ id: string; name: string; formatterMapping: unknown }>; -} - export interface VisualizationProps { dragDropContext: DragContextState; frame: FramePublicAPI; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap index ed1bbf8c696d6..deaf7254d6789 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap @@ -16,26 +16,26 @@ exports[`xy_expression XYChart component it renders area 1`] = ` position="bottom" showGridLines={false} tickFormat={[Function]} - title="C" + title="" /> -`; \ No newline at end of file +`; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx index b16c26f6dee45..81098b0a420b2 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx @@ -5,6 +5,7 @@ */ import { CoreSetup } from 'src/core/public'; +import { getFormat, FormatFactory } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { xyVisualization } from './xy_visualization'; import { @@ -12,23 +13,30 @@ import { functionsRegistry, } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { InterpreterSetup, RenderFunction } from '../interpreter_types'; -import { xyChart, xyChartRenderer } from './xy_expression'; +import { xyChart, getXyChartRenderer } from './xy_expression'; import { legendConfig, xConfig, layerConfig } from './types'; export interface XyVisualizationPluginSetupPlugins { interpreter: InterpreterSetup; + // TODO this is a simulated NP plugin. + // Once field formatters are actually migrated, the actual shim can be used + fieldFormat: { + formatFactory: FormatFactory; + }; } class XyVisualizationPlugin { constructor() {} - setup(_core: CoreSetup | null, { interpreter }: XyVisualizationPluginSetupPlugins) { + setup(_core: CoreSetup | null, { interpreter, fieldFormat }: XyVisualizationPluginSetupPlugins) { interpreter.functionsRegistry.register(() => legendConfig); interpreter.functionsRegistry.register(() => xConfig); interpreter.functionsRegistry.register(() => layerConfig); interpreter.functionsRegistry.register(() => xyChart); - interpreter.renderersRegistry.register(() => xyChartRenderer as RenderFunction); + interpreter.renderersRegistry.register( + () => getXyChartRenderer(fieldFormat.formatFactory) as RenderFunction + ); return xyVisualization; } @@ -44,5 +52,8 @@ export const xyVisualizationSetup = () => renderersRegistry, functionsRegistry, }, + fieldFormat: { + formatFactory: getFormat, + }, }); export const xyVisualizationStop = () => plugin.stop(); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index cc1321e2856eb..ac858e65e53cb 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -4,19 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock( - '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities', - () => { - const formatterSpy = jest.fn(); - const getFormatSpy = jest.fn(() => { - return { convert: formatterSpy }; - }); - return { getFormat: getFormatSpy }; - } -); - import { Axis } from '@elastic/charts'; -import { getFormat } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; import { AreaSeries, BarSeries, Position, LineSeries, Settings, ScaleType } from '@elastic/charts'; import { xyChart, XYChart } from './xy_expression'; import { LensMultiTable } from '../types'; @@ -34,10 +22,10 @@ function sampleArgs() { { id: 'a', name: 'a', - formatterMapping: { id: 'number', params: { pattern: '0,0.000' } }, + formatHint: { id: 'number', params: { pattern: '0,0.000' } }, }, - { id: 'b', name: 'b', formatterMapping: { id: 'number', params: { pattern: '000,0' } } }, - { id: 'c', name: 'c', formatterMapping: { id: 'string' } }, + { id: 'b', name: 'b', formatHint: { id: 'number', params: { pattern: '000,0' } } }, + { id: 'c', name: 'c', formatHint: { id: 'string' } }, ], rows: [{ a: 1, b: 2, c: 'I' }, { a: 1, b: 5, c: 'J' }], }, @@ -112,8 +100,13 @@ describe('xy_expression', () => { }); describe('XYChart component', () => { + let getFormatSpy: jest.Mock; + let convertSpy: jest.Mock; + beforeEach(() => { - jest.clearAllMocks(); + convertSpy = jest.fn(x => x); + getFormatSpy = jest.fn(); + getFormatSpy.mockReturnValue({ convert: convertSpy }); }); test('it renders line', () => { @@ -123,6 +116,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -135,6 +129,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -147,6 +142,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -159,6 +155,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -172,6 +169,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -185,6 +183,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -202,6 +201,7 @@ describe('xy_expression', () => { isHorizontal: true, layers: [{ ...args.layers[0], seriesType: 'bar_stacked' }], }} + formatFactory={getFormatSpy} /> ); expect(component).toMatchSnapshot(); @@ -213,28 +213,28 @@ describe('xy_expression', () => { test('it rewrites the rows based on provided labels', () => { const { data, args } = sampleArgs(); - const component = shallow(); + const component = shallow(); expect(component.find(LineSeries).prop('data')).toEqual([ - { 'Label A': 1, 'Label B': 2, c: 3 }, - { 'Label A': 1, 'Label B': 5, c: 4 }, + { 'Label A': 1, 'Label B': 2, c: 'I' }, + { 'Label A': 1, 'Label B': 5, c: 'J' }, ]); }); test('it uses labels as Y accessors', () => { const { data, args } = sampleArgs(); - const component = shallow(); + const component = shallow(); expect(component.find(LineSeries).prop('yAccessors')).toEqual(['Label A', 'Label B']); }); - test('it indicates a linear scale for a numeric X axis', () => { + test('it indicates an ordinal scale for a string X axis', () => { const { data, args } = sampleArgs(); - const component = shallow(); - expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Linear); + const component = shallow(); + expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Ordinal); }); - test('it indicates an ordinal scale for a string X axis', () => { + test('it indicates a linear scale for a numeric X axis', () => { const { args } = sampleArgs(); const data: LensMultiTable = { @@ -243,41 +243,44 @@ describe('xy_expression', () => { first: { type: 'kibana_datatable', columns: [{ id: 'a', name: 'a' }, { id: 'b', name: 'b' }, { id: 'c', name: 'c' }], - rows: [{ a: 1, b: 2, c: 'Hello' }, { a: 6, b: 5, c: 'World' }], + rows: [{ a: 1, b: 2, c: 3 }, { a: 6, b: 5, c: 9 }], }, }, }; - const component = shallow(); - expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Ordinal); + const component = shallow(); + expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Linear); }); test('it gets the formatter for the x axis', () => { const { data, args } = sampleArgs(); - shallow(); + shallow(); - expect(getFormat as jest.Mock).toHaveBeenCalledWith({ id: 'string' }); + expect(getFormatSpy).toHaveBeenCalledWith({ id: 'string' }); }); test('it gets a default formatter for y if there are multiple y accessors', () => { const { data, args } = sampleArgs(); - shallow(); + shallow(); - expect(getFormat as jest.Mock).toHaveBeenCalledTimes(2); - expect(getFormat as jest.Mock).toHaveBeenCalledWith({ id: 'number' }); + expect(getFormatSpy).toHaveBeenCalledTimes(2); + expect(getFormatSpy).toHaveBeenCalledWith({ id: 'number' }); }); test('it gets the formatter for the y axis if there is only one accessor', () => { const { data, args } = sampleArgs(); shallow( - + ); - - expect(getFormat as jest.Mock).toHaveBeenCalledTimes(2); - expect(getFormat as jest.Mock).toHaveBeenCalledWith({ + expect(getFormatSpy).toHaveBeenCalledTimes(2); + expect(getFormatSpy).toHaveBeenCalledWith({ id: 'number', params: { pattern: '0,0.000' }, }); @@ -286,7 +289,9 @@ describe('xy_expression', () => { test('it should pass the formatter function to the axis', () => { const { data, args } = sampleArgs(); - const instance = shallow(); + const instance = shallow( + + ); const tickFormatter = instance .find(Axis) @@ -294,7 +299,7 @@ describe('xy_expression', () => { .prop('tickFormat'); tickFormatter('I'); - expect(getFormat({}).convert as jest.Mock).toHaveBeenCalledWith('I'); + expect(convertSpy).toHaveBeenCalledWith('I'); }); }); }); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index 5cb83e49a0507..a247476a7d820 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -22,7 +22,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, IconType } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getFormat } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; +import { FormatFactory } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; import { LensMultiTable } from '../types'; import { XYArgs, SeriesType, visualizationTypes } from './types'; import { RenderFunction } from '../interpreter_types'; @@ -86,7 +86,7 @@ export interface XYChartProps { args: XYArgs; } -export const xyChartRenderer: RenderFunction = { +export const getXyChartRenderer = (formatFactory: FormatFactory): RenderFunction => ({ name: 'lens_xy_chart_renderer', displayName: 'XY Chart', help: 'X/Y Chart Renderer', @@ -95,18 +95,24 @@ export const xyChartRenderer: RenderFunction = { render: async (domNode: Element, config: XYChartProps, _handlers: unknown) => { ReactDOM.render( - + , domNode ); }, -}; +}); function getIconForSeriesType(seriesType: SeriesType): IconType { return visualizationTypes.find(c => c.id === seriesType)!.icon || 'empty'; } -export function XYChart({ data, args }: XYChartProps) { +export function XYChart({ + data, + args, + formatFactory, +}: XYChartProps & { + formatFactory: FormatFactory; +}) { const { legend, layers, isHorizontal } = args; if (Object.values(data.tables).every(table => table.rows.length === 0)) { @@ -130,18 +136,18 @@ export function XYChart({ data, args }: XYChartProps) { const xAxisColumn = Object.values(data.tables)[0].columns.find( ({ id }) => id === layers[0].xAccessor - )!; - const xAxisFormatter = getFormat(xAxisColumn ? xAxisColumn.formatterMapping : undefined); + ); + const xAxisFormatter = formatFactory(xAxisColumn && xAxisColumn.formatHint); - let yAxisFormatter: ReturnType; + let yAxisFormatter: ReturnType; if (layers.length === 1 && layers[0].accessors.length === 1) { const firstYAxisColumn = Object.values(data.tables)[0].columns.find( ({ id }) => id === layers[0].accessors[0] ); - yAxisFormatter = getFormat(firstYAxisColumn ? firstYAxisColumn.formatterMapping : undefined); + yAxisFormatter = formatFactory(firstYAxisColumn && firstYAxisColumn.formatHint); } else { // TODO if there are multiple y axes, try to merge the formatters for the axis - yAxisFormatter = getFormat({ id: 'number' }); + yAxisFormatter = formatFactory({ id: 'number' }); } return ( From 554857954addb35eb6a78762134abf3f9bb1343e Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sun, 11 Aug 2019 11:20:23 +0200 Subject: [PATCH 09/17] fix type error --- .../visualize/loader/pipeline_helpers/build_pipeline.ts | 3 ++- .../public/visualize/loader/pipeline_helpers/utilities.ts | 6 +----- .../common/expressions/expression_types/kibana_datatable.ts | 6 +++++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts index 54540256737b8..22e41dbe757b4 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts @@ -23,7 +23,8 @@ import { setBounds } from 'ui/agg_types/buckets/date_histogram'; import { SearchSource } from 'ui/courier'; import { AggConfig, Vis, VisParams, VisState } from 'ui/vis'; import moment from 'moment'; -import { SerializedFieldFormat, createFormat } from './utilities'; +import { SerializedFieldFormat } from 'src/plugins/data/common'; +import { createFormat } from './utilities'; interface SchemaConfigParams { precision?: number; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index e440d3678a6d9..cec5ca39712d9 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -20,6 +20,7 @@ import { i18n } from '@kbn/i18n'; import { identity } from 'lodash'; import { AggConfig, Vis } from 'ui/vis'; +import { SerializedFieldFormat } from 'src/plugins/data/common'; // @ts-ignore import { FieldFormat } from '../../../../field_formats/field_format'; // @ts-ignore @@ -40,11 +41,6 @@ function isTermsFieldFormat( return serializedFieldFormat.id === 'terms'; } -export interface SerializedFieldFormat { - id?: string; - params?: TParams; -} - const config = chrome.getUiSettingsClient(); const getConfig = (...args: any[]): any => config.get(...args); diff --git a/src/plugins/data/common/expressions/expression_types/kibana_datatable.ts b/src/plugins/data/common/expressions/expression_types/kibana_datatable.ts index f44819ebd8c3e..571e9ec7ff1e9 100644 --- a/src/plugins/data/common/expressions/expression_types/kibana_datatable.ts +++ b/src/plugins/data/common/expressions/expression_types/kibana_datatable.ts @@ -18,10 +18,14 @@ */ import { map } from 'lodash'; -import { SerializedFieldFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; const name = 'kibana_datatable'; +export interface SerializedFieldFormat { + id?: string; + params?: TParams; +} + interface Column { id: string; name: string; From 498f836c40d324e0fba61c7ba57f9f3be48e5327 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sun, 11 Aug 2019 11:27:34 +0200 Subject: [PATCH 10/17] clean up formatters --- .../xy_visualization_plugin/xy_expression.test.tsx | 1 - .../public/xy_visualization_plugin/xy_expression.tsx | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index ac858e65e53cb..a3d8a3b3f0133 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -279,7 +279,6 @@ describe('xy_expression', () => { formatFactory={getFormatSpy} /> ); - expect(getFormatSpy).toHaveBeenCalledTimes(2); expect(getFormatSpy).toHaveBeenCalledWith({ id: 'number', params: { pattern: '0,0.000' }, diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index a247476a7d820..37680bbe8e3d9 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -134,20 +134,21 @@ export function XYChart({ ); } + // use formatting hint of first x axis column to format ticks const xAxisColumn = Object.values(data.tables)[0].columns.find( ({ id }) => id === layers[0].xAccessor ); const xAxisFormatter = formatFactory(xAxisColumn && xAxisColumn.formatHint); - let yAxisFormatter: ReturnType; + // use default number formatter for y axis and use formatting hint if there is just a single y column + let yAxisFormatter = formatFactory({ id: 'number' }); if (layers.length === 1 && layers[0].accessors.length === 1) { const firstYAxisColumn = Object.values(data.tables)[0].columns.find( ({ id }) => id === layers[0].accessors[0] ); - yAxisFormatter = formatFactory(firstYAxisColumn && firstYAxisColumn.formatHint); - } else { - // TODO if there are multiple y axes, try to merge the formatters for the axis - yAxisFormatter = formatFactory({ id: 'number' }); + if (firstYAxisColumn && firstYAxisColumn.formatHint) { + yAxisFormatter = formatFactory(firstYAxisColumn.formatHint); + } } return ( From 903a300020f08b49f7911dd07e01a33d8bff9de9 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 12 Aug 2019 15:44:46 +0200 Subject: [PATCH 11/17] fix tests --- .../xy_expression.test.tsx | 37 ++++++++++--------- .../xy_visualization_plugin/xy_expression.tsx | 2 - 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index a3bbfa3def647..56797b1e04dc4 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -49,7 +49,7 @@ function sampleArgs() { title: 'A and B', splitAccessor: 'd', columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}', - xScaleType: 'linear', + xScaleType: 'ordinal', yScaleType: 'linear', }, ], @@ -231,29 +231,30 @@ describe('xy_expression', () => { expect(component.find(LineSeries).prop('yAccessors')).toEqual(['Label A', 'Label B']); }); - test('it indicates an ordinal scale for a string X axis', () => { + test('it set the scale of the x axis according to the args prop', () => { const { data, args } = sampleArgs(); - const component = shallow(); + const component = shallow( + + ); expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Ordinal); }); - test('it indicates a linear scale for a numeric X axis', () => { - const { args } = sampleArgs(); - - const data: LensMultiTable = { - type: 'lens_multitable', - tables: { - first: { - type: 'kibana_datatable', - columns: [{ id: 'a', name: 'a' }, { id: 'b', name: 'b' }, { id: 'c', name: 'c' }], - rows: [{ a: 1, b: 2, c: 3 }, { a: 6, b: 5, c: 9 }], - }, - }, - }; + test('it set the scale of the y axis according to the args prop', () => { + const { data, args } = sampleArgs(); - const component = shallow(); - expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Linear); + const component = shallow( + + ); + expect(component.find(LineSeries).prop('yScaleType')).toEqual(ScaleType.Sqrt); }); test('it gets the formatter for the x axis', () => { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index 2a47fe23db456..5f014f89ddada 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -16,7 +16,6 @@ import { AreaSeries, BarSeries, Position, - niceTimeFormatter, } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; @@ -106,7 +105,6 @@ function getIconForSeriesType(seriesType: SeriesType): IconType { return visualizationTypes.find(c => c.id === seriesType)!.icon || 'empty'; } - export function XYChart({ data, args, From 2003324a6e76ea8cf95dce3ef29fc871b4b21196 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 12 Aug 2019 17:59:52 +0200 Subject: [PATCH 12/17] enable histogram mode --- .../indexpattern_plugin/indexpattern.tsx | 10 ++-- .../operation_definitions/date_histogram.tsx | 2 + .../indexpattern_plugin/operations.test.ts | 1 + .../public/indexpattern_plugin/operations.ts | 2 +- x-pack/legacy/plugins/lens/public/types.ts | 1 + .../__snapshots__/xy_expression.test.tsx.snap | 14 +++++ .../xy_visualization.test.ts.snap | 3 + .../public/xy_visualization_plugin/plugin.tsx | 35 +++++++++-- .../xy_visualization_plugin/to_expression.ts | 4 ++ .../public/xy_visualization_plugin/types.ts | 6 ++ .../xy_expression.test.tsx | 59 +++++++++++++++++-- .../xy_visualization_plugin/xy_expression.tsx | 12 +++- 12 files changed, 130 insertions(+), 19 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx index c2e937a256c41..0052cfe79157e 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -14,6 +14,7 @@ import { DimensionPriority, Operation, DatasourceLayerPanelProps, + OperationMetadata, } from '../types'; import { Query } from '../../../../../../src/legacy/core_plugins/data/public/query'; import { getIndexPatterns } from './loader'; @@ -42,11 +43,7 @@ export type IndexPatternColumn = | CountIndexPatternColumn | FilterRatioIndexPatternColumn; -export interface BaseIndexPatternColumn { - label: string; - dataType: DataType; - isBucketed: boolean; - +export interface BaseIndexPatternColumn extends Operation { // Private operationType: OperationType; suggestedPriority?: DimensionPriority; @@ -146,11 +143,12 @@ export type IndexPatternPrivateState = IndexPatternPersistedState & { }; export function columnToOperation(column: IndexPatternColumn): Operation { - const { dataType, label, isBucketed } = column; + const { dataType, label, isBucketed, isHistogram } = column; return { label, dataType, isBucketed, + isHistogram, }; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx index da436a849dc51..c447863b4bc45 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx @@ -50,6 +50,7 @@ export const dateHistogramOperation: OperationDefinition { "operationMetaData": Object { "dataType": "date", "isBucketed": true, + "isHistogram": true, }, "operations": Array [ Object { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts index 4b9224df45757..4cbbb66777599 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts @@ -130,7 +130,7 @@ export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) { > = {}; const addToMap = (operation: OperationFieldTuple) => (operationMetadata: OperationMetadata) => { - const key = `${operationMetadata.dataType}-${operationMetadata.isBucketed ? 'true' : ''}`; + const key = JSON.stringify(operationMetadata); if (operationByMetadata[key]) { operationByMetadata[key].operations.push(operation); diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index 6deab82c5658e..4d036122fc56b 100644 --- a/x-pack/legacy/plugins/lens/public/types.ts +++ b/x-pack/legacy/plugins/lens/public/types.ts @@ -158,6 +158,7 @@ export interface OperationMetadata { // A bucketed operation is grouped by duplicate values, otherwise each row is // treated as unique isBucketed: boolean; + isHistogram?: boolean; // Extra meta-information like cardinality, color } diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap index deaf7254d6789..7127b4345f77b 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap @@ -40,6 +40,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` }, ] } + enableHistogramMode={false} id="Label D" key="0" splitSeriesAccessors={ @@ -48,6 +49,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` ] } stackAccessors={Array []} + timeZone="UTC" xAccessor="c" xScaleType="ordinal" yAccessors={ @@ -101,6 +103,7 @@ exports[`xy_expression XYChart component it renders bar 1`] = ` }, ] } + enableHistogramMode={false} id="Label D" key="0" splitSeriesAccessors={ @@ -109,6 +112,7 @@ exports[`xy_expression XYChart component it renders bar 1`] = ` ] } stackAccessors={Array []} + timeZone="UTC" xAccessor="c" xScaleType="ordinal" yAccessors={ @@ -162,6 +166,7 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = ` }, ] } + enableHistogramMode={false} id="Label D" key="0" splitSeriesAccessors={ @@ -170,6 +175,7 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = ` ] } stackAccessors={Array []} + timeZone="UTC" xAccessor="c" xScaleType="ordinal" yAccessors={ @@ -223,6 +229,7 @@ exports[`xy_expression XYChart component it renders line 1`] = ` }, ] } + enableHistogramMode={false} id="Label D" key="0" splitSeriesAccessors={ @@ -231,6 +238,7 @@ exports[`xy_expression XYChart component it renders line 1`] = ` ] } stackAccessors={Array []} + timeZone="UTC" xAccessor="c" xScaleType="ordinal" yAccessors={ @@ -284,6 +292,7 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = ` }, ] } + enableHistogramMode={false} id="Label D" key="0" splitSeriesAccessors={ @@ -296,6 +305,7 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = ` "c", ] } + timeZone="UTC" xAccessor="c" xScaleType="ordinal" yAccessors={ @@ -349,6 +359,7 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = ` }, ] } + enableHistogramMode={false} id="Label D" key="0" splitSeriesAccessors={ @@ -361,6 +372,7 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = ` "c", ] } + timeZone="UTC" xAccessor="c" xScaleType="ordinal" yAccessors={ @@ -414,6 +426,7 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] = }, ] } + enableHistogramMode={false} id="Label D" key="0" splitSeriesAccessors={ @@ -426,6 +439,7 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] = "c", ] } + timeZone="UTC" xAccessor="c" xScaleType="ordinal" yAccessors={ diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_visualization.test.ts.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_visualization.test.ts.snap index aee7784e8acfb..b034e73ba914d 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_visualization.test.ts.snap +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_visualization.test.ts.snap @@ -23,6 +23,9 @@ Object { "hide": Array [ false, ], + "isHistogram": Array [ + false, + ], "layerId": Array [ "first", ], diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx index 81098b0a420b2..0f5ca8cea243a 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx @@ -4,20 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup } from 'src/core/public'; +import { CoreSetup, UiSettingsClientContract } from 'src/core/public'; import { getFormat, FormatFactory } from 'ui/visualize/loader/pipeline_helpers/utilities'; -import { xyVisualization } from './xy_visualization'; - +import chrome, { Chrome } from 'ui/chrome'; +import moment from 'moment-timezone'; import { renderersRegistry, functionsRegistry, } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; + +import { xyVisualization } from './xy_visualization'; import { InterpreterSetup, RenderFunction } from '../interpreter_types'; import { xyChart, getXyChartRenderer } from './xy_expression'; import { legendConfig, xConfig, layerConfig } from './types'; +const detectedTimezone = moment.tz.guess(); export interface XyVisualizationPluginSetupPlugins { interpreter: InterpreterSetup; + chrome: Chrome; // TODO this is a simulated NP plugin. // Once field formatters are actually migrated, the actual shim can be used fieldFormat: { @@ -25,17 +29,37 @@ export interface XyVisualizationPluginSetupPlugins { }; } +function getTimeZone(uiSettings: UiSettingsClientContract) { + const configuredTimeZone = uiSettings.get('dateFormat:tz'); + if (configuredTimeZone === 'Browser') { + return moment.tz.guess(); + } + + return configuredTimeZone; +} + class XyVisualizationPlugin { constructor() {} - setup(_core: CoreSetup | null, { interpreter, fieldFormat }: XyVisualizationPluginSetupPlugins) { + setup( + _core: CoreSetup | null, + { + interpreter, + fieldFormat: { formatFactory }, + chrome: { getUiSettingsClient }, + }: XyVisualizationPluginSetupPlugins + ) { interpreter.functionsRegistry.register(() => legendConfig); interpreter.functionsRegistry.register(() => xConfig); interpreter.functionsRegistry.register(() => layerConfig); interpreter.functionsRegistry.register(() => xyChart); interpreter.renderersRegistry.register( - () => getXyChartRenderer(fieldFormat.formatFactory) as RenderFunction + () => + getXyChartRenderer({ + formatFactory, + timeZone: getTimeZone(getUiSettingsClient()), + }) as RenderFunction ); return xyVisualization; @@ -55,5 +79,6 @@ export const xyVisualizationSetup = () => fieldFormat: { formatFactory: getFormat, }, + chrome, }); export const xyVisualizationStop = () => plugin.stop(); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts index 77af9263e3bcc..f7e308521793b 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts @@ -133,6 +133,9 @@ export const buildExpression = ( }); } + const xAxisOperation = + frame && frame.datasourceLayers[layer.layerId].getOperationForColumnId(layer.xAccessor); + return { type: 'expression', chain: [ @@ -148,6 +151,7 @@ export const buildExpression = ( xAccessor: [layer.xAccessor], yScaleType: [getScaleType(dataTypes[layer.layerId][layer.accessors[0]])], xScaleType: [getScaleType(dataTypes[layer.layerId][layer.xAccessor])], + isHistogram: [Boolean(xAxisOperation && xAxisOperation.isHistogram)], splitAccessor: [layer.splitAccessor], seriesType: [layer.seriesType], accessors: layer.accessors, diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts index d1014ee2bd076..c7c600172a2b9 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts @@ -136,6 +136,11 @@ export const layerConfig: ExpressionFunction< help: 'The scale type of the x axis', default: 'ordinal', }, + isHistogram: { + types: ['boolean'], + default: false, + help: 'Whether to layout the chart as a histogram', + }, yScaleType: { options: ['log', 'sqrt', 'linear', 'time'], help: 'The scale type of the y axes', @@ -178,6 +183,7 @@ export type LayerArgs = LayerConfig & { columnToLabel?: string; // Actually a JSON key-value pair yScaleType: 'time' | 'linear' | 'log' | 'sqrt'; xScaleType: 'time' | 'linear' | 'ordinal'; + isHistogram: boolean; }; // Arguments to XY chart expression, with computed properties diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index 56797b1e04dc4..4bd209d290dbb 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -51,6 +51,7 @@ function sampleArgs() { columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}', xScaleType: 'ordinal', yScaleType: 'linear', + isHistogram: false, }, ], }; @@ -82,6 +83,7 @@ describe('xy_expression', () => { splitAccessor: 'd', xScaleType: 'linear', yScaleType: 'linear', + isHistogram: false, }; expect(layerConfig.fn(null, args, {})).toEqual({ @@ -121,6 +123,7 @@ describe('xy_expression', () => { data={data} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'line' }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component).toMatchSnapshot(); @@ -134,6 +137,7 @@ describe('xy_expression', () => { data={data} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar' }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component).toMatchSnapshot(); @@ -147,6 +151,7 @@ describe('xy_expression', () => { data={data} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area' }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component).toMatchSnapshot(); @@ -160,6 +165,7 @@ describe('xy_expression', () => { data={data} args={{ ...args, isHorizontal: true, layers: [{ ...args.layers[0], seriesType: 'bar' }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component).toMatchSnapshot(); @@ -174,6 +180,7 @@ describe('xy_expression', () => { data={data} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_stacked' }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component).toMatchSnapshot(); @@ -188,6 +195,7 @@ describe('xy_expression', () => { data={data} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area_stacked' }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component).toMatchSnapshot(); @@ -206,6 +214,7 @@ describe('xy_expression', () => { layers: [{ ...args.layers[0], seriesType: 'bar_stacked' }], }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component).toMatchSnapshot(); @@ -214,10 +223,26 @@ describe('xy_expression', () => { expect(component.find(Settings).prop('rotation')).toEqual(90); }); + test('it passes time zone and histogram mode to the series', () => { + const { data, args } = sampleArgs(); + const component = shallow( + + ); + expect(component.find(LineSeries).prop('timeZone')).toEqual('CEST'); + expect(component.find(LineSeries).prop('enableHistogramMode')).toEqual(true); + }); + test('it rewrites the rows based on provided labels', () => { const { data, args } = sampleArgs(); - const component = shallow(); + const component = shallow( + + ); expect(component.find(LineSeries).prop('data')).toEqual([ { 'Label A': 1, 'Label B': 2, c: 'I' }, { 'Label A': 1, 'Label B': 5, c: 'J' }, @@ -227,7 +252,9 @@ describe('xy_expression', () => { test('it uses labels as Y accessors', () => { const { data, args } = sampleArgs(); - const component = shallow(); + const component = shallow( + + ); expect(component.find(LineSeries).prop('yAccessors')).toEqual(['Label A', 'Label B']); }); @@ -239,6 +266,7 @@ describe('xy_expression', () => { data={data} args={{ ...args, layers: [{ ...args.layers[0], xScaleType: 'ordinal' }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Ordinal); @@ -252,6 +280,7 @@ describe('xy_expression', () => { data={data} args={{ ...args, layers: [{ ...args.layers[0], yScaleType: 'sqrt' }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(component.find(LineSeries).prop('yScaleType')).toEqual(ScaleType.Sqrt); @@ -260,7 +289,14 @@ describe('xy_expression', () => { test('it gets the formatter for the x axis', () => { const { data, args } = sampleArgs(); - shallow(); + shallow( + + ); expect(getFormatSpy).toHaveBeenCalledWith({ id: 'string' }); }); @@ -268,7 +304,14 @@ describe('xy_expression', () => { test('it gets a default formatter for y if there are multiple y accessors', () => { const { data, args } = sampleArgs(); - shallow(); + shallow( + + ); expect(getFormatSpy).toHaveBeenCalledTimes(2); expect(getFormatSpy).toHaveBeenCalledWith({ id: 'number' }); @@ -282,6 +325,7 @@ describe('xy_expression', () => { data={{ ...data }} args={{ ...args, layers: [{ ...args.layers[0], accessors: ['a'] }] }} formatFactory={getFormatSpy} + timeZone="UTC" /> ); expect(getFormatSpy).toHaveBeenCalledWith({ @@ -294,7 +338,12 @@ describe('xy_expression', () => { const { data, args } = sampleArgs(); const instance = shallow( - + ); const tickFormatter = instance diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index 5f014f89ddada..eb9b394f7c8e7 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -85,7 +85,10 @@ export interface XYChartProps { args: XYArgs; } -export const getXyChartRenderer = (formatFactory: FormatFactory): RenderFunction => ({ +export const getXyChartRenderer = (dependencies: { + formatFactory: FormatFactory; + timeZone: string; +}): RenderFunction => ({ name: 'lens_xy_chart_renderer', displayName: 'XY Chart', help: 'X/Y Chart Renderer', @@ -94,7 +97,7 @@ export const getXyChartRenderer = (formatFactory: FormatFactory): RenderFunction render: async (domNode: Element, config: XYChartProps, _handlers: unknown) => { ReactDOM.render( - + , domNode ); @@ -109,8 +112,10 @@ export function XYChart({ data, args, formatFactory, + timeZone, }: XYChartProps & { formatFactory: FormatFactory; + timeZone: string; }) { const { legend, layers, isHorizontal } = args; @@ -188,6 +193,7 @@ export function XYChart({ columnToLabel, yScaleType, xScaleType, + isHistogram, }, index ) => { @@ -225,6 +231,8 @@ export function XYChart({ data: rows, xScaleType, yScaleType, + enableHistogramMode: isHistogram, + timeZone, }; return seriesType === 'line' ? ( From cebfa9a01949dcbd7572f739b0a6793bec6c499e Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 13 Aug 2019 09:20:21 +0200 Subject: [PATCH 13/17] pass additional information about columns --- .../indexpattern_plugin/indexpattern.tsx | 3 +- .../operation_definitions/count.tsx | 2 + .../operation_definitions/date_histogram.tsx | 2 + .../operation_definitions/filter_ratio.tsx | 2 + .../operation_definitions/metrics.tsx | 2 + .../operation_definitions/terms.test.tsx | 2 + .../operation_definitions/terms.tsx | 2 + .../indexpattern_plugin/operations.test.ts | 3 + x-pack/legacy/plugins/lens/public/types.ts | 2 +- .../xy_visualization_plugin/to_expression.ts | 29 ++++++---- .../xy_expression.test.tsx | 57 +++++++++++++++++-- .../xy_visualization_plugin/xy_expression.tsx | 2 +- 12 files changed, 90 insertions(+), 18 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx index 0052cfe79157e..6bc0e231bf01a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -143,12 +143,13 @@ export type IndexPatternPrivateState = IndexPatternPersistedState & { }; export function columnToOperation(column: IndexPatternColumn): Operation { - const { dataType, label, isBucketed, isHistogram } = column; + const { dataType, label, isBucketed, isHistogram, scale } = column; return { label, dataType, isBucketed, isHistogram, + scale, }; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/count.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/count.tsx index 20b96583e125c..0cb4838faa12c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/count.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/count.tsx @@ -19,6 +19,7 @@ export const countOperation: OperationDefinition = { { dataType: 'number', isBucketed: false, + scale: 'ratio', }, ]; }, @@ -31,6 +32,7 @@ export const countOperation: OperationDefinition = { operationType: 'count', suggestedPriority, isBucketed: false, + scale: 'ratio', }; }, toEsAggsConfig: (column, columnId) => ({ diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx index c447863b4bc45..c7e043dd4e972 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx @@ -51,6 +51,7 @@ export const dateHistogramOperation: OperationDefinition( { dataType: 'number', isBucketed: false, + scale: 'ratio', }, ]; } @@ -59,6 +60,7 @@ function buildMetricOperation( suggestedPriority, sourceField: field ? field.name : '', isBucketed: false, + scale: 'ratio', } as T; }, toEsAggsConfig: (column, columnId) => ({ diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx index b1ee1dd4de2d2..840db10ebc096 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx @@ -88,6 +88,7 @@ describe('terms', () => { { dataType: 'string', isBucketed: true, + scale: 'ordinal', }, ]); @@ -102,6 +103,7 @@ describe('terms', () => { { dataType: 'boolean', isBucketed: true, + scale: 'ordinal', }, ]); }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.tsx index 97e006ff3df32..ca0455c9372da 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.tsx @@ -53,6 +53,7 @@ export const termsOperation: OperationDefinition = { { dataType: type, isBucketed: true, + scale: 'ordinal', }, ]; } @@ -80,6 +81,7 @@ export const termsOperation: OperationDefinition = { label: ofName(field.name), dataType: field.type as DataType, operationType: 'terms', + scale: 'ordinal', suggestedPriority, sourceField: field.name, isBucketed: true, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts index ba3a8b974643e..e8547fd2eb745 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts @@ -233,6 +233,7 @@ describe('getOperationTypesForField', () => { "operationMetaData": Object { "dataType": "string", "isBucketed": true, + "scale": "ordinal", }, "operations": Array [ Object { @@ -247,6 +248,7 @@ describe('getOperationTypesForField', () => { "dataType": "date", "isBucketed": true, "isHistogram": true, + "scale": "interval", }, "operations": Array [ Object { @@ -260,6 +262,7 @@ describe('getOperationTypesForField', () => { "operationMetaData": Object { "dataType": "number", "isBucketed": false, + "scale": "ratio", }, "operations": Array [ Object { diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index 4d036122fc56b..136b894df6301 100644 --- a/x-pack/legacy/plugins/lens/public/types.ts +++ b/x-pack/legacy/plugins/lens/public/types.ts @@ -159,7 +159,7 @@ export interface OperationMetadata { // treated as unique isBucketed: boolean; isHistogram?: boolean; - + scale?: 'ordinal' | 'interval' | 'ratio'; // Extra meta-information like cardinality, color } diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts index f7e308521793b..f7cab08801bc5 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts @@ -7,7 +7,7 @@ import { Ast } from '@kbn/interpreter/common'; import { ScaleType } from '@elastic/charts'; import { State, LayerConfig } from './types'; -import { FramePublicAPI, DataType } from '../types'; +import { FramePublicAPI, DataType, OperationMetadata } from '../types'; function xyTitles(layer: LayerConfig, frame: FramePublicAPI) { const defaults = { @@ -56,16 +56,16 @@ export const toExpression = (state: State, frame: FramePublicAPI): Ast | null => }), }; - const dataTypes: Record> = {}; + const metadata: Record> = {}; state.layers.forEach(layer => { - dataTypes[layer.layerId] = {}; + metadata[layer.layerId] = {}; const datasource = frame.datasourceLayers[layer.layerId]; datasource.getTableSpec().forEach(column => { const operation = frame.datasourceLayers[layer.layerId].getOperationForColumnId( column.columnId ); if (operation) { - dataTypes[layer.layerId][column.columnId] = operation.dataType; + metadata[layer.layerId][column.columnId] = operation; } }); }); @@ -73,13 +73,22 @@ export const toExpression = (state: State, frame: FramePublicAPI): Ast | null => return buildExpression( stateWithValidAccessors, xyTitles(state.layers[0], frame), - dataTypes, + metadata, frame ); }; -export function getScaleType(dataType: DataType) { - switch (dataType) { +export function getScaleType(metadata: OperationMetadata) { + // use scale information if available + if (metadata.scale === 'ordinal') { + return ScaleType.Ordinal; + } + if (metadata.scale === 'interval' || metadata.scale === 'ratio') { + return metadata.dataType === 'date' ? ScaleType.Time : ScaleType.Linear; + } + + // fall back to data type if necessary + switch (metadata.dataType) { case 'boolean': case 'string': return ScaleType.Ordinal; @@ -93,7 +102,7 @@ export function getScaleType(dataType: DataType) { export const buildExpression = ( state: State, { xTitle, yTitle }: { xTitle: string; yTitle: string }, - dataTypes: Record>, + metadata: Record>, frame?: FramePublicAPI ): Ast => ({ type: 'expression', @@ -149,8 +158,8 @@ export const buildExpression = ( hide: [Boolean(layer.hide)], xAccessor: [layer.xAccessor], - yScaleType: [getScaleType(dataTypes[layer.layerId][layer.accessors[0]])], - xScaleType: [getScaleType(dataTypes[layer.layerId][layer.xAccessor])], + yScaleType: [getScaleType(metadata[layer.layerId][layer.accessors[0]])], + xScaleType: [getScaleType(metadata[layer.layerId][layer.xAccessor])], isHistogram: [Boolean(xAxisOperation && xAxisOperation.isHistogram)], splitAccessor: [layer.splitAccessor], seriesType: [layer.seriesType], diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index 4bd209d290dbb..7eab6ce109e3d 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -223,18 +223,65 @@ describe('xy_expression', () => { expect(component.find(Settings).prop('rotation')).toEqual(90); }); - test('it passes time zone and histogram mode to the series', () => { + test('it passes time zone to the series', () => { const { data, args } = sampleArgs(); + const component = shallow( + + ); + expect(component.find(LineSeries).prop('timeZone')).toEqual('CEST'); + }); + + test('it applies histogram mode to the series for single series', () => { + const { data, args } = sampleArgs(); + const firstLayer: LayerArgs = { ...args.layers[0], seriesType: 'bar', isHistogram: true }; + delete firstLayer.splitAccessor; const component = shallow( ); - expect(component.find(LineSeries).prop('timeZone')).toEqual('CEST'); - expect(component.find(LineSeries).prop('enableHistogramMode')).toEqual(true); + expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(true); + }); + + test('it applies histogram mode to the series for stacked series', () => { + const { data, args } = sampleArgs(); + const component = shallow( + + ); + expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(true); + }); + + test('it does not apply histogram mode for splitted series', () => { + const { data, args } = sampleArgs(); + const component = shallow( + + ); + expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(false); }); test('it rewrites the rows based on provided labels', () => { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index eb9b394f7c8e7..0ab051bc15971 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -231,7 +231,7 @@ export function XYChart({ data: rows, xScaleType, yScaleType, - enableHistogramMode: isHistogram, + enableHistogramMode: isHistogram && (seriesType.includes('stacked') || !splitAccessor), timeZone, }; From 39e25b5ba199a10b8875fb3b862f05693946b93d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 13 Aug 2019 11:21:24 +0200 Subject: [PATCH 14/17] fix type errors --- .../lens/public/indexpattern_plugin/indexpattern.tsx | 3 +-- .../lens/public/xy_visualization_plugin/to_expression.ts | 2 +- .../public/xy_visualization_plugin/xy_expression.test.tsx | 1 + .../lens/public/xy_visualization_plugin/xy_suggestions.ts | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx index 6bc0e231bf01a..b9438f7249673 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -14,7 +14,6 @@ import { DimensionPriority, Operation, DatasourceLayerPanelProps, - OperationMetadata, } from '../types'; import { Query } from '../../../../../../src/legacy/core_plugins/data/public/query'; import { getIndexPatterns } from './loader'; @@ -29,7 +28,7 @@ import { import { isDraggedField } from './utils'; import { LayerPanel } from './layerpanel'; -import { Datasource, DataType } from '..'; +import { Datasource } from '..'; export type OperationType = IndexPatternColumn['operationType']; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts index f7cab08801bc5..48cd1802f2a5d 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts @@ -7,7 +7,7 @@ import { Ast } from '@kbn/interpreter/common'; import { ScaleType } from '@elastic/charts'; import { State, LayerConfig } from './types'; -import { FramePublicAPI, DataType, OperationMetadata } from '../types'; +import { FramePublicAPI, OperationMetadata } from '../types'; function xyTitles(layer: LayerConfig, frame: FramePublicAPI) { const defaults = { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index c27b8acaf69a7..7eab6ce109e3d 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -183,6 +183,7 @@ describe('xy_expression', () => { timeZone="UTC" /> ); + expect(component).toMatchSnapshot(); expect(component.find(BarSeries)).toHaveLength(1); expect(component.find(BarSeries).prop('stackAccessors')).toHaveLength(1); }); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts index 81d8b0891206e..7ec9f9a68ad83 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts @@ -12,7 +12,7 @@ import { VisualizationSuggestion, TableSuggestionColumn, TableSuggestion, - DataType, + OperationMetadata, } from '../types'; import { State, SeriesType } from './types'; import { generateId } from '../id_generator'; @@ -141,11 +141,11 @@ function getSuggestion( ], }; - const dataTypes: Record = {}; + const metadata: Record = {}; [xValue, ...yValues, splitBy].forEach(col => { if (col) { - dataTypes[col.columnId] = col.operation.dataType; + metadata[col.columnId] = col.operation; } }); @@ -168,7 +168,7 @@ function getSuggestion( }, }, { xTitle, yTitle }, - { [layerId]: dataTypes } + { [layerId]: metadata } ), }; } From c1508828580c8d54f26d27d5fae564dba7b8c14a Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 13 Aug 2019 17:06:42 +0200 Subject: [PATCH 15/17] default scale type --- .../xy_visualization_plugin/to_expression.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts index 48cd1802f2a5d..33ea1c308d6d0 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts @@ -56,7 +56,7 @@ export const toExpression = (state: State, frame: FramePublicAPI): Ast | null => }), }; - const metadata: Record> = {}; + const metadata: Record> = {}; state.layers.forEach(layer => { metadata[layer.layerId] = {}; const datasource = frame.datasourceLayers[layer.layerId]; @@ -64,9 +64,7 @@ export const toExpression = (state: State, frame: FramePublicAPI): Ast | null => const operation = frame.datasourceLayers[layer.layerId].getOperationForColumnId( column.columnId ); - if (operation) { - metadata[layer.layerId][column.columnId] = operation; - } + metadata[layer.layerId][column.columnId] = operation; }); }); @@ -78,7 +76,11 @@ export const toExpression = (state: State, frame: FramePublicAPI): Ast | null => ); }; -export function getScaleType(metadata: OperationMetadata) { +export function getScaleType(metadata: OperationMetadata | null, defaultScale: ScaleType) { + if (!metadata) { + return defaultScale; + } + // use scale information if available if (metadata.scale === 'ordinal') { return ScaleType.Ordinal; @@ -102,7 +104,7 @@ export function getScaleType(metadata: OperationMetadata) { export const buildExpression = ( state: State, { xTitle, yTitle }: { xTitle: string; yTitle: string }, - metadata: Record>, + metadata: Record>, frame?: FramePublicAPI ): Ast => ({ type: 'expression', @@ -158,8 +160,12 @@ export const buildExpression = ( hide: [Boolean(layer.hide)], xAccessor: [layer.xAccessor], - yScaleType: [getScaleType(metadata[layer.layerId][layer.accessors[0]])], - xScaleType: [getScaleType(metadata[layer.layerId][layer.xAccessor])], + yScaleType: [ + getScaleType(metadata[layer.layerId][layer.accessors[0]], ScaleType.Ordinal), + ], + xScaleType: [ + getScaleType(metadata[layer.layerId][layer.xAccessor], ScaleType.Linear), + ], isHistogram: [Boolean(xAxisOperation && xAxisOperation.isHistogram)], splitAccessor: [layer.splitAccessor], seriesType: [layer.seriesType], From 7fd18f52d307a24c0cdd545f27f322f18b2eb310 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 14 Aug 2019 20:41:24 +0200 Subject: [PATCH 16/17] include format hints for filter ratio expression --- .../plugins/lens/public/indexpattern_plugin/to_expression.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts index 71e8473aa6839..ab06f94117163 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts @@ -65,6 +65,7 @@ function getExpressionForLayer( index="${indexPattern.id}" metricsAtAllLevels=false partialRows=false + includeFormatHints=true aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify( idMap )}' | ${filterRatios.map(([id]) => `lens_calculate_filter_ratio id=${id}`).join(' | ')}`; From 0c2d9d65fb60371b469555d870102d55d62c6478 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 15 Aug 2019 11:37:05 +0200 Subject: [PATCH 17/17] simplify operation metadata --- .../lens/public/indexpattern_plugin/indexpattern.tsx | 3 +-- .../operation_definitions/date_histogram.tsx | 2 -- .../lens/public/indexpattern_plugin/operations.test.ts | 1 - x-pack/legacy/plugins/lens/public/types.ts | 1 - .../lens/public/xy_visualization_plugin/to_expression.ts | 9 ++++++++- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx index 7079eafb1f105..16202ec50f28d 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -143,12 +143,11 @@ export type IndexPatternPrivateState = IndexPatternPersistedState & { }; export function columnToOperation(column: IndexPatternColumn): Operation { - const { dataType, label, isBucketed, isHistogram, scale } = column; + const { dataType, label, isBucketed, scale } = column; return { label, dataType, isBucketed, - isHistogram, scale, }; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx index c7e043dd4e972..e454c700bb8db 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx @@ -50,7 +50,6 @@ export const dateHistogramOperation: OperationDefinition { "operationMetaData": Object { "dataType": "date", "isBucketed": true, - "isHistogram": true, "scale": "interval", }, "operations": Array [ diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index 136b894df6301..3f448f00a8d75 100644 --- a/x-pack/legacy/plugins/lens/public/types.ts +++ b/x-pack/legacy/plugins/lens/public/types.ts @@ -158,7 +158,6 @@ export interface OperationMetadata { // A bucketed operation is grouped by duplicate values, otherwise each row is // treated as unique isBucketed: boolean; - isHistogram?: boolean; scale?: 'ordinal' | 'interval' | 'ratio'; // Extra meta-information like cardinality, color } diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts index 33ea1c308d6d0..e4e0e57b7926a 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts @@ -147,6 +147,13 @@ export const buildExpression = ( const xAxisOperation = frame && frame.datasourceLayers[layer.layerId].getOperationForColumnId(layer.xAccessor); + const isHistogramDimension = Boolean( + xAxisOperation && + xAxisOperation.isBucketed && + xAxisOperation.scale && + xAxisOperation.scale !== 'ordinal' + ); + return { type: 'expression', chain: [ @@ -166,7 +173,7 @@ export const buildExpression = ( xScaleType: [ getScaleType(metadata[layer.layerId][layer.xAccessor], ScaleType.Linear), ], - isHistogram: [Boolean(xAxisOperation && xAxisOperation.isHistogram)], + isHistogram: [isHistogramDimension], splitAccessor: [layer.splitAccessor], seriesType: [layer.seriesType], accessors: layer.accessors,