diff --git a/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx index 904c11d2f29de..6574d504bfe35 100644 --- a/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; import { EditorFrame } from './editor_frame'; -import { Visualization, Datasource, DatasourcePublicAPI } from '../../types'; +import { Visualization, Datasource, DatasourcePublicAPI, DatasourceSuggestion } from '../../types'; import { act } from 'react-dom/test-utils'; import { createMockVisualization, createMockDatasource } from '../mock_extensions'; @@ -16,6 +16,17 @@ import { createMockVisualization, createMockDatasource } from '../mock_extension // datasources to be processed by its callers. const waitForPromises = () => new Promise(resolve => setTimeout(resolve)); +function generateSuggestion(datasourceSuggestionId = 1, state = {}): DatasourceSuggestion { + return { + state: {}, + table: { + columns: [], + datasourceSuggestionId: 1, + isMultiRow: true, + }, + }; +} + describe('editor_frame', () => { let mockVisualization: Visualization; let mockDatasource: Datasource; @@ -475,13 +486,13 @@ describe('editor_frame', () => { ...mockVisualization, getSuggestions: () => [ { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.5, state: {}, title: 'Suggestion2', }, { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.8, state: {}, title: 'Suggestion1', @@ -492,13 +503,13 @@ describe('editor_frame', () => { ...mockVisualization, getSuggestions: () => [ { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.4, state: {}, title: 'Suggestion4', }, { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.45, state: {}, title: 'Suggestion3', @@ -509,7 +520,7 @@ describe('editor_frame', () => { datasourceMap={{ testDatasource: { ...mockDatasource, - getDatasourceSuggestionsFromCurrentState: () => [{ state: {}, tableColumns: [] }], + getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()], }, }} initialDatasourceId="testDatasource" @@ -540,7 +551,7 @@ describe('editor_frame', () => { ...mockVisualization, getSuggestions: () => [ { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.8, state: suggestionVisState, title: 'Suggestion1', @@ -552,9 +563,7 @@ describe('editor_frame', () => { datasourceMap={{ testDatasource: { ...mockDatasource, - getDatasourceSuggestionsFromCurrentState: () => [ - { state: newDatasourceState, tableColumns: [] }, - ], + getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()], }, }} initialDatasourceId="testDatasource" @@ -595,13 +604,13 @@ describe('editor_frame', () => { ...mockVisualization, getSuggestions: () => [ { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.2, state: {}, title: 'Suggestion1', }, { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.8, state: suggestionVisState, title: 'Suggestion2', @@ -613,8 +622,8 @@ describe('editor_frame', () => { datasourceMap={{ testDatasource: { ...mockDatasource, - getDatasourceSuggestionsForField: () => [{ state: {}, tableColumns: [] }], - getDatasourceSuggestionsFromCurrentState: () => [{ state: {}, tableColumns: [] }], + getDatasourceSuggestionsForField: () => [generateSuggestion()], + getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()], }, }} initialDatasourceId="testDatasource" @@ -648,13 +657,13 @@ describe('editor_frame', () => { ...mockVisualization, getSuggestions: () => [ { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.2, state: {}, title: 'Suggestion1', }, { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.6, state: {}, title: 'Suggestion2', @@ -665,7 +674,7 @@ describe('editor_frame', () => { ...mockVisualization2, getSuggestions: () => [ { - tableIndex: 0, + datasourceSuggestionId: 0, score: 0.8, state: suggestionVisState, title: 'Suggestion3', @@ -676,8 +685,8 @@ describe('editor_frame', () => { datasourceMap={{ testDatasource: { ...mockDatasource, - getDatasourceSuggestionsForField: () => [{ state: {}, tableColumns: [] }], - getDatasourceSuggestionsFromCurrentState: () => [{ state: {}, tableColumns: [] }], + getDatasourceSuggestionsForField: () => [generateSuggestion()], + getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()], }, }} initialDatasourceId="testDatasource" diff --git a/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts index f79a4b1000991..437faa2779f3e 100644 --- a/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts @@ -6,19 +6,24 @@ import { getSuggestions } from './suggestion_helpers'; import { createMockVisualization } from '../mock_extensions'; -import { TableColumn } from '../../types'; +import { TableSuggestion } from '../../types'; + +const generateSuggestion = (datasourceSuggestionId: number = 1, state = {}) => ({ + state, + table: { datasourceSuggestionId, columns: [], isMultiRow: false }, +}); describe('suggestion helpers', () => { it('should return suggestions array', () => { const mockVisualization = createMockVisualization(); const suggestedState = {}; const suggestions = getSuggestions( - [{ state: {}, tableColumns: [] }], + [generateSuggestion()], { vis1: { ...mockVisualization, getSuggestions: () => [ - { tableIndex: 0, score: 0.5, title: 'Test', state: suggestedState }, + { datasourceSuggestionId: 0, score: 0.5, title: 'Test', state: suggestedState }, ], }, }, @@ -33,18 +38,20 @@ describe('suggestion helpers', () => { const mockVisualization1 = createMockVisualization(); const mockVisualization2 = createMockVisualization(); const suggestions = getSuggestions( - [{ state: {}, tableColumns: [] }], + [generateSuggestion()], { vis1: { ...mockVisualization1, getSuggestions: () => [ - { tableIndex: 0, score: 0.5, title: 'Test', state: {} }, - { tableIndex: 0, score: 0.5, title: 'Test2', state: {} }, + { datasourceSuggestionId: 0, score: 0.5, title: 'Test', state: {} }, + { datasourceSuggestionId: 0, score: 0.5, title: 'Test2', state: {} }, ], }, vis2: { ...mockVisualization2, - getSuggestions: () => [{ tableIndex: 0, score: 0.5, title: 'Test3', state: {} }], + getSuggestions: () => [ + { datasourceSuggestionId: 0, score: 0.5, title: 'Test3', state: {} }, + ], }, }, 'vis1', @@ -57,18 +64,20 @@ describe('suggestion helpers', () => { const mockVisualization1 = createMockVisualization(); const mockVisualization2 = createMockVisualization(); const suggestions = getSuggestions( - [{ state: {}, tableColumns: [] }], + [generateSuggestion()], { vis1: { ...mockVisualization1, getSuggestions: () => [ - { tableIndex: 0, score: 0.2, title: 'Test', state: {} }, - { tableIndex: 0, score: 0.8, title: 'Test2', state: {} }, + { datasourceSuggestionId: 0, score: 0.2, title: 'Test', state: {} }, + { datasourceSuggestionId: 0, score: 0.8, title: 'Test2', state: {} }, ], }, vis2: { ...mockVisualization2, - getSuggestions: () => [{ tableIndex: 0, score: 0.6, title: 'Test3', state: {} }], + getSuggestions: () => [ + { datasourceSuggestionId: 0, score: 0.6, title: 'Test3', state: {} }, + ], }, }, 'vis1', @@ -82,10 +91,10 @@ describe('suggestion helpers', () => { it('should call all suggestion getters with all available data tables', () => { const mockVisualization1 = createMockVisualization(); const mockVisualization2 = createMockVisualization(); - const table1: TableColumn[] = []; - const table2: TableColumn[] = []; + const table1: TableSuggestion = { datasourceSuggestionId: 0, columns: [], isMultiRow: true }; + const table2: TableSuggestion = { datasourceSuggestionId: 1, columns: [], isMultiRow: true }; getSuggestions( - [{ state: {}, tableColumns: table1 }, { state: {}, tableColumns: table2 }], + [{ state: {}, table: table1 }, { state: {}, table: table2 }], { vis1: mockVisualization1, vis2: mockVisualization2, @@ -105,18 +114,20 @@ describe('suggestion helpers', () => { const tableState1 = {}; const tableState2 = {}; const suggestions = getSuggestions( - [{ state: tableState1, tableColumns: [] }, { state: tableState2, tableColumns: [] }], + [generateSuggestion(1, tableState1), generateSuggestion(1, tableState2)], { vis1: { ...mockVisualization1, getSuggestions: () => [ - { tableIndex: 0, score: 0.3, title: 'Test', state: {} }, - { tableIndex: 1, score: 0.2, title: 'Test2', state: {} }, + { datasourceSuggestionId: 0, score: 0.3, title: 'Test', state: {} }, + { datasourceSuggestionId: 1, score: 0.2, title: 'Test2', state: {} }, ], }, vis2: { ...mockVisualization2, - getSuggestions: () => [{ tableIndex: 1, score: 0.1, title: 'Test3', state: {} }], + getSuggestions: () => [ + { datasourceSuggestionId: 1, score: 0.1, title: 'Test3', state: {} }, + ], }, }, 'vis1', @@ -132,7 +143,7 @@ describe('suggestion helpers', () => { const mockVisualization2 = createMockVisualization(); const currentState = {}; getSuggestions( - [{ state: {}, tableColumns: [] }, { state: {}, tableColumns: [] }], + [generateSuggestion(1), generateSuggestion(2)], { vis1: mockVisualization1, vis2: mockVisualization2, diff --git a/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts index c6f5d4d4538f3..459f5d89fb9c3 100644 --- a/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts @@ -29,7 +29,7 @@ export function getSuggestions( activeVisualizationId: string | null, visualizationState: unknown ): Suggestion[] { - const datasourceTables = datasourceTableSuggestions.map(({ tableColumns }) => tableColumns); + const datasourceTables = datasourceTableSuggestions.map(({ table }) => table); return ( Object.entries(visualizationMap) @@ -39,7 +39,7 @@ export function getSuggestions( tables: datasourceTables, state: visualizationId === activeVisualizationId ? visualizationState : undefined, }) - .map(({ tableIndex: datasourceSuggestionId, ...suggestion }) => ({ + .map(({ datasourceSuggestionId, ...suggestion }) => ({ ...suggestion, visualizationId, datasourceState: datasourceTableSuggestions[datasourceSuggestionId].state, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index f77822191c6fd..9932f8b3c703c 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -11,6 +11,7 @@ export interface EditorFrameInstance { mount: (element: Element) => void; unmount: () => void; } + export interface EditorFrameSetup { createInstance: (options: EditorFrameOptions) => EditorFrameInstance; // generic type on the API functions to pull the "unknown vs. specific type" error into the implementation @@ -26,9 +27,15 @@ export interface TableColumn { operation: Operation; } +export interface TableSuggestion { + datasourceSuggestionId: number; + isMultiRow: boolean; + columns: TableColumn[]; +} + export interface DatasourceSuggestion { state: T; - tableColumns: TableColumn[]; + table: TableSuggestion; } /** @@ -130,7 +137,7 @@ export interface VisualizationProps { export interface SuggestionRequest { // It is up to the Visualization to rank these tables - tables: TableColumn[][]; + tables: TableSuggestion[]; state?: T; // State is only passed if the visualization is active } @@ -138,7 +145,7 @@ export interface VisualizationSuggestion { score: number; title: string; state: T; - tableIndex: number; + datasourceSuggestionId: number; } export interface Visualization { diff --git a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index bc5c513bb0f1b..2a9fcd2fc4e33 100644 --- a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -31,19 +31,16 @@ function sampleArgs() { seriesType: 'line', title: 'My fanci line chart', legend: { - type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top, }, y: { - type: 'lens_xy_yConfig', accessors: ['a', 'b'], position: Position.Left, showGridlines: false, title: 'A and B', }, x: { - type: 'lens_xy_xConfig', accessor: 'c', position: Position.Bottom, showGridlines: false, 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 f52e6f69beb27..4adcd7ce7a1c5 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 @@ -152,9 +152,9 @@ export const xConfig: ContextFunction<'lens_xy_xConfig', null, XConfig, XConfigR export interface XYArgs { seriesType: 'bar' | 'line' | 'area'; title: string; - legend: LegendConfigResult; - y: YConfigResult; - x: XConfigResult; + legend: LegendConfig; + y: YConfig; + x: XConfig; splitSeriesAccessors: string[]; stackAccessors: string[]; } diff --git a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts new file mode 100644 index 0000000000000..bc68cf206d573 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts @@ -0,0 +1,233 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getSuggestions } from './xy_suggestions'; +import { TableColumn, VisualizationSuggestion } from '../types'; +import { XYArgs } from './xy_expression'; + +describe('xy_suggestions', () => { + function numCol(columnId: string): TableColumn { + return { + columnId, + operation: { + dataType: 'number', + id: `avg_${columnId}`, + label: `Avg ${columnId}`, + isBucketed: false, + }, + }; + } + + function strCol(columnId: string): TableColumn { + return { + columnId, + operation: { + dataType: 'string', + id: `terms_${columnId}`, + label: `Top 5 ${columnId}`, + isBucketed: true, + }, + }; + } + + function dateCol(columnId: string): TableColumn { + return { + columnId, + operation: { + dataType: 'date', + id: `date_histogram_${columnId}`, + isBucketed: true, + label: `${columnId} histogram`, + }, + }; + } + + // Helper that plucks out the important part of a suggestion for + // most test assertions + function suggestionSubset(suggestion: VisualizationSuggestion) { + const { seriesType, splitSeriesAccessors, stackAccessors, x, y } = suggestion.state; + + return { + seriesType, + splitSeriesAccessors, + stackAccessors, + x: x.accessor, + y: y.accessors, + }; + } + + test('ignores invalid combinations', () => { + const unknownCol = () => { + const str = strCol('foo'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return { ...str, operation: { ...str.operation, dataType: 'wonkies' } } as any; + }; + + expect( + getSuggestions({ + tables: [ + { datasourceSuggestionId: 0, isMultiRow: true, columns: [dateCol('a')] }, + { datasourceSuggestionId: 1, isMultiRow: true, columns: [strCol('foo'), strCol('bar')] }, + { datasourceSuggestionId: 2, isMultiRow: false, columns: [strCol('foo'), numCol('bar')] }, + { datasourceSuggestionId: 3, isMultiRow: true, columns: [unknownCol(), numCol('bar')] }, + ], + }) + ).toEqual([]); + }); + + test('suggests a basic x y chart with date on x', () => { + const [suggestion, ...rest] = getSuggestions({ + tables: [ + { + datasourceSuggestionId: 0, + isMultiRow: true, + columns: [numCol('bytes'), dateCol('date')], + }, + ], + }); + + expect(rest.length).toEqual(0); + expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(` +Object { + "seriesType": "line", + "splitSeriesAccessors": Array [], + "stackAccessors": Array [], + "x": "date", + "y": Array [ + "bytes", + ], +} +`); + }); + + test('suggests a split x y chart with date on x', () => { + const [suggestion, ...rest] = getSuggestions({ + tables: [ + { + datasourceSuggestionId: 1, + isMultiRow: true, + columns: [numCol('price'), numCol('quantity'), dateCol('date'), strCol('product')], + }, + ], + }); + + expect(rest.length).toEqual(0); + expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(` +Object { + "seriesType": "line", + "splitSeriesAccessors": Array [ + "product", + ], + "stackAccessors": Array [], + "x": "date", + "y": Array [ + "price", + "quantity", + ], +} +`); + }); + + test('supports multiple suggestions', () => { + const [s1, s2, ...rest] = getSuggestions({ + tables: [ + { + datasourceSuggestionId: 0, + isMultiRow: true, + columns: [numCol('price'), dateCol('date')], + }, + { + datasourceSuggestionId: 1, + isMultiRow: true, + columns: [numCol('count'), strCol('country')], + }, + ], + }); + + expect(rest.length).toEqual(0); + expect([suggestionSubset(s1), suggestionSubset(s2)]).toMatchInlineSnapshot(` +Array [ + Object { + "seriesType": "line", + "splitSeriesAccessors": Array [], + "stackAccessors": Array [], + "x": "date", + "y": Array [ + "price", + ], + }, + Object { + "seriesType": "bar", + "splitSeriesAccessors": Array [], + "stackAccessors": Array [], + "x": "country", + "y": Array [ + "count", + ], + }, +] +`); + }); + + test('handles two numeric values', () => { + const [suggestion] = getSuggestions({ + tables: [ + { + datasourceSuggestionId: 1, + isMultiRow: true, + columns: [numCol('quantity'), numCol('price')], + }, + ], + }); + + expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(` +Object { + "seriesType": "bar", + "splitSeriesAccessors": Array [], + "stackAccessors": Array [], + "x": "quantity", + "y": Array [ + "price", + ], +} +`); + }); + + test('handles unbucketed suggestions', () => { + const [suggestion] = getSuggestions({ + tables: [ + { + datasourceSuggestionId: 1, + isMultiRow: true, + columns: [ + numCol('num votes'), + { + columnId: 'mybool', + operation: { + dataType: 'boolean', + id: 'mybool', + isBucketed: false, + label: 'Yes / No', + }, + }, + ], + }, + ], + }); + + expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(` +Object { + "seriesType": "bar", + "splitSeriesAccessors": Array [], + "stackAccessors": Array [], + "x": "mybool", + "y": Array [ + "num votes", + ], +} +`); + }); +}); diff --git a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts new file mode 100644 index 0000000000000..f1b49a2318424 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { partition } from 'lodash'; +import { Position } from '@elastic/charts'; +import { XYArgs } from './xy_expression'; +import { SuggestionRequest, VisualizationSuggestion, TableColumn, TableSuggestion } from '../types'; + +const columnSortOrder = { + date: 0, + string: 1, + boolean: 2, + number: 3, +}; + +/** + * Generate suggestions for the xy chart. + * + * @param opts + */ +export function getSuggestions( + opts: SuggestionRequest +): Array> { + return opts.tables + .filter( + ({ isMultiRow, columns }) => + // We only render line charts for multi-row queries. We require at least + // two columns: one for x and at least one for y, and y columns must be numeric. + // We reject any datasource suggestions which have a column of an unknown type. + isMultiRow && + columns.length > 1 && + columns.some(col => col.operation.dataType === 'number') && + !columns.some(col => !columnSortOrder.hasOwnProperty(col.operation.dataType)) + ) + .map(table => getSuggestionForColumns(table)); +} + +function getSuggestionForColumns(table: TableSuggestion): VisualizationSuggestion { + const [buckets, values] = partition( + prioritizeColumns(table.columns), + col => col.operation.isBucketed + ); + + if (buckets.length >= 1) { + const [x, splitBy] = buckets; + return getSuggestion(table.datasourceSuggestionId, x, values, splitBy); + } else { + const [x, ...yValues] = values; + return getSuggestion(table.datasourceSuggestionId, x, yValues); + } +} + +// This shuffles columns around so that the left-most column defualts to: +// date, string, boolean, then number, in that priority. We then use this +// order to pluck out the x column, and the split / stack column. +function prioritizeColumns(columns: TableColumn[]) { + return [...columns].sort( + (a, b) => columnSortOrder[a.operation.dataType] - columnSortOrder[b.operation.dataType] + ); +} + +function getSuggestion( + datasourceSuggestionId: number, + xValue: TableColumn, + yValues: TableColumn[], + splitBy?: TableColumn +): VisualizationSuggestion { + const yTitle = yValues.map(col => col.operation.label).join(' & '); + const xTitle = xValue.operation.label; + const isDate = xValue.operation.dataType === 'date'; + + // TODO: Localize the title, label, etc + const preposition = isDate ? 'over' : 'of'; + const title = `${yTitle} ${preposition} ${xTitle}`; + return { + title, + score: 1, + datasourceSuggestionId, + state: { + title, + legend: { isVisible: true, position: Position.Right }, + seriesType: isDate ? 'line' : 'bar', + splitSeriesAccessors: splitBy && isDate ? [splitBy.columnId] : [], + stackAccessors: splitBy && !isDate ? [splitBy.columnId] : [], + x: { + accessor: xValue.columnId, + position: Position.Bottom, + showGridlines: false, + title: xTitle, + }, + y: { + accessors: yValues.map(col => col.columnId), + position: Position.Left, + showGridlines: false, + title: yTitle, + }, + }, + }; +} diff --git a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts index a750d810d116c..51996de5e0352 100644 --- a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts @@ -4,40 +4,68 @@ * you may not use this file except in compliance with the Elastic License. */ -import { xyVisualization, XyVisualizationPersistedState } from './xy_visualization'; +import { xyVisualization, State } from './xy_visualization'; +import { Position } from '@elastic/charts'; -describe('IndexPattern Data Source', () => { - let persistedState: XyVisualizationPersistedState; - - beforeEach(() => { - persistedState = { - roles: [], - }; - }); +function exampleState(): State { + return { + legend: { position: Position.Bottom, isVisible: true }, + seriesType: 'area', + splitSeriesAccessors: [], + stackAccessors: [], + title: 'Foo', + x: { + accessor: 'a', + position: Position.Bottom, + showGridlines: true, + title: 'Baz', + }, + y: { + accessors: ['b', 'c'], + position: Position.Left, + showGridlines: true, + title: 'Bar', + }, + }; +} +describe('IndexPattern Data Source', () => { describe('#initialize', () => { it('loads default state', () => { - expect(xyVisualization.initialize()).toEqual({ - roles: [], - }); + expect(xyVisualization.initialize()).toMatchInlineSnapshot(` +Object { + "legend": Object { + "isVisible": true, + "position": "right", + }, + "seriesType": "line", + "splitSeriesAccessors": Array [], + "stackAccessors": Array [], + "title": "Empty line chart", + "x": Object { + "accessor": "", + "position": "bottom", + "showGridlines": false, + "title": "Uknown", + }, + "y": Object { + "accessors": Array [], + "position": "left", + "showGridlines": false, + "title": "Uknown", + }, +} +`); }); it('loads from persisted state', () => { - expect(xyVisualization.initialize(persistedState)).toEqual({ - roles: [], - }); + expect(xyVisualization.initialize(exampleState())).toEqual(exampleState()); }); }); describe('#getPersistableState', () => { it('persists the state as given', () => { - expect( - xyVisualization.getPersistableState({ - roles: [], - }) - ).toEqual({ - roles: [], - }); + expect(xyVisualization.getPersistableState(exampleState())).toEqual(exampleState()); }); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx index b5a6111c9570f..dbe678ad6ffa4 100644 --- a/x-pack/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx @@ -6,20 +6,40 @@ import React from 'react'; import { render } from 'react-dom'; +import { Position } from '@elastic/charts'; import { Visualization, Operation } from '../types'; +import { getSuggestions } from './xy_suggestions'; +import { XYArgs } from './xy_expression'; import { NativeRenderer } from '../native_renderer'; -export interface XyVisualizationState { - roles: string[]; -} +export type State = XYArgs; +export type PersistableState = XYArgs; -export type XyVisualizationPersistedState = XyVisualizationState; +export const xyVisualization: Visualization = { + getSuggestions, -export const xyVisualization: Visualization = { - initialize() { - return { - roles: [], - }; + initialize(state) { + return ( + state || { + title: 'Empty line chart', + legend: { isVisible: true, position: Position.Right }, + seriesType: 'line', + splitSeriesAccessors: [], + stackAccessors: [], + x: { + accessor: '', + position: Position.Bottom, + showGridlines: false, + title: 'Uknown', + }, + y: { + accessors: [], + position: Position.Left, + showGridlines: false, + title: 'Uknown', + }, + } + ); }, getPersistableState(state) { @@ -51,7 +71,5 @@ export const xyVisualization: Visualization [], - toExpression: state => '', };