From 73d01056dda2840cf52dd6765689e5c30d029c62 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 25 Jun 2019 14:24:43 +0200 Subject: [PATCH 1/4] add basic suggestion rendering --- .../expressions/expression_renderer.tsx | 3 + .../visualization.tsx | 1 + .../editor_frame/editor_frame.tsx | 1 + .../editor_frame/expression_helpers.ts | 33 +++++--- .../editor_frame/index.scss | 2 + .../editor_frame/suggestion_helpers.test.ts | 80 ++++++++++++++++--- .../editor_frame/suggestion_helpers.ts | 3 + .../editor_frame/suggestion_panel.scss | 19 +++++ .../editor_frame/suggestion_panel.tsx | 52 +++++++++--- .../editor_frame/workspace_panel.scss | 3 + .../editor_frame/workspace_panel.tsx | 1 + x-pack/legacy/plugins/lens/public/index.scss | 3 +- x-pack/legacy/plugins/lens/public/types.ts | 2 + .../xy_visualization_plugin/to_expression.ts | 75 +++++++++++++++++ .../public/xy_visualization_plugin/types.ts | 6 ++ .../xy_expression.scss | 3 +- .../xy_visualization_plugin/xy_expression.tsx | 2 + .../xy_visualization_plugin/xy_suggestions.ts | 49 ++++++++---- .../xy_visualization.tsx | 66 +-------------- 19 files changed, 289 insertions(+), 115 deletions(-) create mode 100644 x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/index.scss create mode 100644 x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.scss create mode 100644 x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.scss create mode 100644 x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts diff --git a/src/legacy/core_plugins/data/public/expressions/expression_renderer.tsx b/src/legacy/core_plugins/data/public/expressions/expression_renderer.tsx index 78fb43772504c..9c6443e8a254d 100644 --- a/src/legacy/core_plugins/data/public/expressions/expression_renderer.tsx +++ b/src/legacy/core_plugins/data/public/expressions/expression_renderer.tsx @@ -37,6 +37,7 @@ export type ExpressionRendererProps = Pick< * this callback is called with the given result. */ onRenderFailure?: (result: Result) => void; + className?: string; }; export type ExpressionRenderer = React.FC; @@ -44,6 +45,7 @@ export type ExpressionRenderer = React.FC; export const createRenderer = (run: ExpressionRunner): ExpressionRenderer => ({ expression, onRenderFailure, + className, ...options }: ExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); @@ -63,6 +65,7 @@ export const createRenderer = (run: ExpressionRunner): ExpressionRenderer => ({ return (
{ mountpoint.current = el; }} diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx index 1cd7993922f6d..6d1004ff32f9f 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx @@ -174,6 +174,7 @@ export const datatableVisualization: Visualization< label: col.operation.label, })), }, + previewIcon: 'visTable', }; }); }, diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx index 696949ca41c81..edd549f916d07 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx @@ -133,6 +133,7 @@ export function EditorFrame(props: EditorFrameProps) { visualizationState={state.visualization.state} visualizationMap={props.visualizationMap} dispatch={dispatch} + ExpressionRenderer={props.ExpressionRenderer} /> } /> diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/expression_helpers.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/expression_helpers.ts index ad6cdb0d7e4ee..8566035c29bf2 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/expression_helpers.ts +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/expression_helpers.ts @@ -7,21 +7,12 @@ import { Ast, fromExpression } from '@kbn/interpreter/common'; import { Visualization, Datasource, DatasourcePublicAPI } from '../../types'; -export function buildExpression( - visualization: Visualization | null, - visualizationState: unknown, +export function prependDatasourceExpression( + visualizationExpression: Ast | string | null, datasource: Datasource, - datasourceState: unknown, - datasourcePublicAPI: DatasourcePublicAPI + datasourceState: unknown ): Ast | null { - if (visualization === null) { - return null; - } const datasourceExpression = datasource.toExpression(datasourceState); - const visualizationExpression = visualization.toExpression( - visualizationState, - datasourcePublicAPI - ); if (datasourceExpression === null || visualizationExpression === null) { return null; @@ -40,3 +31,21 @@ export function buildExpression( chain: [...parsedDatasourceExpression.chain, ...parsedVisualizationExpression.chain], }; } + +export function buildExpression( + visualization: Visualization | null, + visualizationState: unknown, + datasource: Datasource, + datasourceState: unknown, + datasourcePublicAPI: DatasourcePublicAPI +): Ast | null { + if (visualization === null) { + return null; + } + const visualizationExpression = visualization.toExpression( + visualizationState, + datasourcePublicAPI + ); + + return prependDatasourceExpression(visualizationExpression, datasource, datasourceState); +} diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/index.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/index.scss new file mode 100644 index 0000000000000..182e36df61797 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/index.scss @@ -0,0 +1,2 @@ +@import './workspace_panel.scss'; +@import './suggestion_panel.scss'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts index 850cdfc2b3c0f..ac30bc2adae3e 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts @@ -23,7 +23,13 @@ describe('suggestion helpers', () => { vis1: { ...mockVisualization, getSuggestions: () => [ - { datasourceSuggestionId: 0, score: 0.5, title: 'Test', state: suggestedState }, + { + datasourceSuggestionId: 0, + score: 0.5, + title: 'Test', + state: suggestedState, + previewIcon: 'empty', + }, ], }, }, @@ -43,14 +49,32 @@ describe('suggestion helpers', () => { vis1: { ...mockVisualization1, getSuggestions: () => [ - { datasourceSuggestionId: 0, score: 0.5, title: 'Test', state: {} }, - { datasourceSuggestionId: 0, score: 0.5, title: 'Test2', state: {} }, + { + datasourceSuggestionId: 0, + score: 0.5, + title: 'Test', + state: {}, + previewIcon: 'empty', + }, + { + datasourceSuggestionId: 0, + score: 0.5, + title: 'Test2', + state: {}, + previewIcon: 'empty', + }, ], }, vis2: { ...mockVisualization2, getSuggestions: () => [ - { datasourceSuggestionId: 0, score: 0.5, title: 'Test3', state: {} }, + { + datasourceSuggestionId: 0, + score: 0.5, + title: 'Test3', + state: {}, + previewIcon: 'empty', + }, ], }, }, @@ -69,14 +93,32 @@ describe('suggestion helpers', () => { vis1: { ...mockVisualization1, getSuggestions: () => [ - { datasourceSuggestionId: 0, score: 0.2, title: 'Test', state: {} }, - { datasourceSuggestionId: 0, score: 0.8, title: 'Test2', state: {} }, + { + datasourceSuggestionId: 0, + score: 0.2, + title: 'Test', + state: {}, + previewIcon: 'empty', + }, + { + datasourceSuggestionId: 0, + score: 0.8, + title: 'Test2', + state: {}, + previewIcon: 'empty', + }, ], }, vis2: { ...mockVisualization2, getSuggestions: () => [ - { datasourceSuggestionId: 0, score: 0.6, title: 'Test3', state: {} }, + { + datasourceSuggestionId: 0, + score: 0.6, + title: 'Test3', + state: {}, + previewIcon: 'empty', + }, ], }, }, @@ -119,14 +161,32 @@ describe('suggestion helpers', () => { vis1: { ...mockVisualization1, getSuggestions: () => [ - { datasourceSuggestionId: 0, score: 0.3, title: 'Test', state: {} }, - { datasourceSuggestionId: 1, score: 0.2, title: 'Test2', state: {} }, + { + datasourceSuggestionId: 0, + score: 0.3, + title: 'Test', + state: {}, + previewIcon: 'empty', + }, + { + datasourceSuggestionId: 1, + score: 0.2, + title: 'Test2', + state: {}, + previewIcon: 'empty', + }, ], }, vis2: { ...mockVisualization2, getSuggestions: () => [ - { datasourceSuggestionId: 1, score: 0.1, title: 'Test3', state: {} }, + { + datasourceSuggestionId: 1, + score: 0.1, + title: 'Test3', + state: {}, + previewIcon: 'empty', + }, ], }, }, diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts index 459f5d89fb9c3..a9886387c6c74 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Ast } from '@kbn/interpreter/common'; import { Visualization, DatasourceSuggestion } from '../../types'; import { Action } from './state_management'; @@ -13,6 +14,8 @@ export interface Suggestion { score: number; title: string; state: unknown; + previewExpression?: Ast | string; + previewIcon: string; } /** diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.scss new file mode 100644 index 0000000000000..d87d8737f3b99 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.scss @@ -0,0 +1,19 @@ +.lnsSidebar__suggestions { + > * { + margin-top: $euiSizeS; + } +} + +.lnsSidebar__suggestionIcon { + width: 100%; + min-height: 120px; + display: flex; + align-items: center; + justify-content: center; + padding: $euiSize; +} + + .lnsSuggestionChartWrapper { + height: 100px; + pointer-events: none; + } \ No newline at end of file diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx index 9d9730db37651..0ba6f17ba5b59 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx @@ -7,9 +7,12 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiIcon, EuiTitle, EuiPanel } from '@elastic/eui'; import { Action } from './state_management'; import { Datasource, Visualization } from '../../types'; import { getSuggestions, toSwitchAction } from './suggestion_helpers'; +import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/data/public'; +import { prependDatasourceExpression } from './expression_helpers'; export interface SuggestionPanelProps { activeDatasource: Datasource; @@ -18,6 +21,7 @@ export interface SuggestionPanelProps { visualizationMap: Record; visualizationState: unknown; dispatch: (action: Action) => void; + ExpressionRenderer: ExpressionRenderer; } export function SuggestionPanel({ @@ -27,6 +31,7 @@ export function SuggestionPanel({ visualizationMap, visualizationState, dispatch, + ExpressionRenderer: ExpressionRendererComponent, }: SuggestionPanelProps) { const datasourceSuggestions = activeDatasource.getDatasourceSuggestionsFromCurrentState( datasourceState @@ -40,26 +45,51 @@ export function SuggestionPanel({ ); return ( - <> -

- -

+
+ +

+ +

+
{suggestions.map((suggestion, index) => { + const previewExpression = suggestion.previewExpression + ? prependDatasourceExpression( + suggestion.previewExpression, + activeDatasource, + suggestion.datasourceState + ) + : null; return ( - + +

{suggestion.title}

+
+ {previewExpression ? ( + { + // TODO error handling + }} + /> + ) : ( +
+ +
+ )} + ); })} - +
); } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.scss new file mode 100644 index 0000000000000..03c3534e1e12b --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.scss @@ -0,0 +1,3 @@ + .lnsChartWrapper { + height: 500px; + } \ No newline at end of file diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx index dc9b5ffce6b49..6368fc6b4984b 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx @@ -137,6 +137,7 @@ export function WorkspacePanel({ } else { return ( { setExpressionError(e); diff --git a/x-pack/legacy/plugins/lens/public/index.scss b/x-pack/legacy/plugins/lens/public/index.scss index 6e5372e233a0d..12f384081fe7a 100644 --- a/x-pack/legacy/plugins/lens/public/index.scss +++ b/x-pack/legacy/plugins/lens/public/index.scss @@ -3,4 +3,5 @@ @import './drag_drop/drag_drop.scss'; @import './xy_visualization_plugin/xy_expression.scss'; -@import './indexpattern_plugin/indexpattern'; \ No newline at end of file +@import './indexpattern_plugin/indexpattern'; +@import './editor_frame_plugin/editor_frame/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index 367d1bdd99c79..6be550a2342cd 100644 --- a/x-pack/legacy/plugins/lens/public/types.ts +++ b/x-pack/legacy/plugins/lens/public/types.ts @@ -154,6 +154,8 @@ export interface VisualizationSuggestion { title: string; state: T; datasourceSuggestionId: number; + previewExpression?: Ast | string; + previewIcon: string; } export interface Visualization { 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 new file mode 100644 index 0000000000000..999b02a75472f --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/to_expression.ts @@ -0,0 +1,75 @@ +/* + * 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 { Ast } from '@kbn/interpreter/common'; +import { State } from './types'; + +export const toExpression = (state: State): Ast => ({ + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_xy_chart', + arguments: { + seriesType: [state.seriesType], + title: [state.title], + legend: [ + { + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_xy_legendConfig', + arguments: { + isVisible: [state.legend.isVisible], + position: [state.legend.position], + }, + }, + ], + }, + ], + x: [ + { + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_xy_xConfig', + arguments: { + title: [state.x.title], + showGridlines: [state.x.showGridlines], + position: [state.x.position], + accessor: [state.x.accessor], + hide: [Boolean(state.x.hide)], + }, + }, + ], + }, + ], + y: [ + { + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_xy_yConfig', + arguments: { + title: [state.y.title], + showGridlines: [state.y.showGridlines], + position: [state.y.position], + accessors: state.y.accessors, + hide: [Boolean(state.x.hide)], + }, + }, + ], + }, + ], + splitSeriesAccessors: state.splitSeriesAccessors, + stackAccessors: state.stackAccessors, + }, + }, + ], +}); 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 2f5d587ff892c..c9ac526a3bbed 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 @@ -53,6 +53,7 @@ interface AxisConfig { title: string; showGridlines: boolean; position: Position; + hide?: boolean; } const axisConfig: { [key in keyof AxisConfig]: ArgumentType } = { @@ -69,6 +70,11 @@ const axisConfig: { [key in keyof AxisConfig]: ArgumentType } = options: [Position.Top, Position.Right, Position.Bottom, Position.Left], help: 'The position of the axis', }, + hide: { + types: ['boolean'], + default: false, + help: 'Show / hide axis', + }, }; export interface YConfig extends AxisConfig { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.scss b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.scss index 93986078f68b1..9ba7326af6a56 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.scss +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.scss @@ -1,4 +1,3 @@ .lnsChart { - // TODO style this dependent on the screen height (see POC) - height: 500px; + height: 100%; } \ No newline at end of file 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 a60040f5555dc..ede24e8ffb42a 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 @@ -121,6 +121,7 @@ export function XYChart({ data, args }: XYChartProps) { position={x.position} title={x.title} showGridLines={x.showGridlines} + hide={x.hide} /> {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 28ef677e49644..167d72f715ded 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 @@ -8,6 +8,7 @@ import { partition } from 'lodash'; import { Position } from '@elastic/charts'; import { SuggestionRequest, VisualizationSuggestion, TableColumn, TableSuggestion } from '../types'; import { State } from './types'; +import { toExpression } from './to_expression'; const columnSortOrder = { date: 0, @@ -75,28 +76,46 @@ function getSuggestion( // TODO: Localize the title, label, etc const preposition = isDate ? 'over' : 'of'; const title = `${yTitle} ${preposition} ${xTitle}`; + const state: 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, + }, + }; + 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] : [], + state, + previewIcon: isDate ? 'visLine' : 'visBar', + previewExpression: toExpression({ + ...state, x: { - accessor: xValue.columnId, - position: Position.Bottom, - showGridlines: false, - title: xTitle, + ...state.x, + hide: true, }, y: { - accessors: yValues.map(col => col.columnId), - position: Position.Left, - showGridlines: false, - title: yTitle, + ...state.y, + hide: true, }, - }, + legend: { + ...state.legend, + isVisible: false, + }, + }), }; } diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx index d7f2f978cc01a..efb9cc7fc1518 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx @@ -12,6 +12,7 @@ import { getSuggestions } from './xy_suggestions'; import { XYConfigPanel } from './xy_config_panel'; import { Visualization } from '../types'; import { State, PersistableState } from './types'; +import { toExpression } from './to_expression'; export const xyVisualization: Visualization = { getSuggestions, @@ -50,68 +51,5 @@ export const xyVisualization: Visualization = { domElement ), - toExpression: state => ({ - type: 'expression', - chain: [ - { - type: 'function', - function: 'lens_xy_chart', - arguments: { - seriesType: [state.seriesType], - title: [state.title], - legend: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'lens_xy_legendConfig', - arguments: { - isVisible: [state.legend.isVisible], - position: [state.legend.position], - }, - }, - ], - }, - ], - x: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'lens_xy_xConfig', - arguments: { - title: [state.x.title], - showGridlines: [state.x.showGridlines], - position: [state.x.position], - accessor: [state.x.accessor], - }, - }, - ], - }, - ], - y: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'lens_xy_yConfig', - arguments: { - title: [state.y.title], - showGridlines: [state.y.showGridlines], - position: [state.y.position], - accessors: state.y.accessors, - }, - }, - ], - }, - ], - splitSeriesAccessors: state.splitSeriesAccessors, - stackAccessors: state.stackAccessors, - }, - }, - ], - }), + toExpression, }; From cade82272e860e1f048a4fc503c2eb9a25579b6b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 25 Jun 2019 15:33:48 +0200 Subject: [PATCH 2/4] add tests around suggestion expression rendering --- .../__mocks__/suggestion_helpers.ts | 11 ++ .../editor_frame/editor_frame.test.tsx | 15 +- .../editor_frame/suggestion_panel.test.tsx | 180 ++++++++++++++++++ .../editor_frame/suggestion_panel.tsx | 104 +++++++--- .../editor_frame/workspace_panel.test.tsx | 3 + .../xy_visualization.test.ts.snap | 6 + .../xy_suggestions.test.ts | 21 ++ 7 files changed, 310 insertions(+), 30 deletions(-) create mode 100644 x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/__mocks__/suggestion_helpers.ts create mode 100644 x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/__mocks__/suggestion_helpers.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/__mocks__/suggestion_helpers.ts new file mode 100644 index 0000000000000..94d162aa5f1b0 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/__mocks__/suggestion_helpers.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +const actual = jest.requireActual('../suggestion_helpers'); + +jest.spyOn(actual, 'getSuggestions'); + +export const { getSuggestions, toSwitchAction } = actual; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx index 8d67c632ea5c9..90e66336e2880 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx @@ -529,6 +529,7 @@ Object { score: 1, datasourceSuggestionId: 0, state: initialState, + previewIcon: 'empty', }, ]); @@ -627,12 +628,14 @@ Object { score: 0.5, state: {}, title: 'Suggestion2', + previewIcon: 'empty', }, { datasourceSuggestionId: 0, score: 0.8, state: {}, title: 'Suggestion1', + previewIcon: 'empty', }, ], }, @@ -644,12 +647,14 @@ Object { score: 0.4, state: {}, title: 'Suggestion4', + previewIcon: 'empty', }, { datasourceSuggestionId: 0, score: 0.45, state: {}, title: 'Suggestion3', + previewIcon: 'empty', }, ], }, @@ -670,7 +675,7 @@ Object { // TODO why is this necessary? instance.update(); - const suggestions = instance.find('[data-test-subj="suggestion"]'); + const suggestions = instance.find('[data-test-subj="suggestion-title"]'); expect(suggestions.map(el => el.text())).toEqual([ 'Suggestion1', 'Suggestion2', @@ -693,6 +698,7 @@ Object { score: 0.8, state: suggestionVisState, title: 'Suggestion1', + previewIcon: 'empty', }, ], }, @@ -716,7 +722,7 @@ Object { instance.update(); act(() => { - instance.find('[data-test-subj="suggestion"]').simulate('click'); + instance.find('[data-test-subj="suggestion-title"]').simulate('click'); }); expect(mockVisualization.renderConfigPanel).toHaveBeenCalledTimes(1); @@ -747,12 +753,14 @@ Object { score: 0.2, state: {}, title: 'Suggestion1', + previewIcon: 'empty', }, { datasourceSuggestionId: 0, score: 0.8, state: suggestionVisState, title: 'Suggestion2', + previewIcon: 'empty', }, ], }, @@ -801,12 +809,14 @@ Object { score: 0.2, state: {}, title: 'Suggestion1', + previewIcon: 'empty', }, { datasourceSuggestionId: 0, score: 0.6, state: {}, title: 'Suggestion2', + previewIcon: 'empty', }, ], }, @@ -818,6 +828,7 @@ Object { score: 0.8, state: suggestionVisState, title: 'Suggestion3', + previewIcon: 'empty', }, ], }, diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx new file mode 100644 index 0000000000000..e3409a8eca7e9 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx @@ -0,0 +1,180 @@ +/* + * 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 React from 'react'; +import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; +import { Visualization } from '../../types'; +import { + createMockVisualization, + createMockDatasource, + createExpressionRendererMock, + DatasourceMock, +} from '../mocks'; +import { ExpressionRenderer } from 'src/legacy/core_plugins/data/public'; +import { SuggestionPanel, SuggestionPanelProps } from './suggestion_panel'; +import { getSuggestions, Suggestion } from './suggestion_helpers'; +import { fromExpression } from '@kbn/interpreter/target/common'; +import { EuiIcon } from '@elastic/eui'; + +jest.mock('./suggestion_helpers'); + +describe('suggestion_panel', () => { + let mockVisualization: Visualization; + let mockDatasource: DatasourceMock; + + let expressionRendererMock: ExpressionRenderer; + let dispatchMock: jest.Mock; + + const suggestion1State = { suggestion1: true }; + const suggestion2State = { suggestion2: true }; + + let defaultProps: SuggestionPanelProps; + + beforeEach(() => { + mockVisualization = createMockVisualization(); + mockDatasource = createMockDatasource(); + expressionRendererMock = createExpressionRendererMock(); + dispatchMock = jest.fn(); + + (getSuggestions as jest.Mock).mockReturnValue([ + { + datasourceState: {}, + previewIcon: 'empty', + score: 0.5, + state: suggestion1State, + visualizationId: 'vis', + title: 'Suggestion1', + }, + { + datasourceState: {}, + previewIcon: 'empty', + score: 0.5, + state: suggestion2State, + visualizationId: 'vis', + title: 'Suggestion2', + }, + ] as Suggestion[]); + + defaultProps = { + activeDatasource: mockDatasource, + datasourceState: {}, + activeVisualizationId: 'vis', + visualizationMap: { + vis: mockVisualization, + }, + visualizationState: {}, + dispatch: dispatchMock, + ExpressionRenderer: expressionRendererMock, + }; + }); + + it('should list passed in suggestions', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="suggestion-title"]').map(el => el.text())).toEqual([ + 'Suggestion1', + 'Suggestion2', + ]); + }); + + it('should dispatch visualization switch action if suggestion is clicked', () => { + const wrapper = mount(); + + wrapper + .find('[data-test-subj="suggestion-title"]') + .first() + .simulate('click'); + + expect(dispatchMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'SWITCH_VISUALIZATION', + initialState: suggestion1State, + }) + ); + }); + + it('should render preview expression if there is one', () => { + (getSuggestions as jest.Mock).mockReturnValue([ + { + datasourceState: {}, + previewIcon: 'empty', + score: 0.5, + state: suggestion1State, + visualizationId: 'vis', + title: 'Suggestion1', + }, + { + datasourceState: {}, + previewIcon: 'empty', + score: 0.5, + state: suggestion2State, + visualizationId: 'vis', + title: 'Suggestion2', + previewExpression: 'test | expression', + }, + ] as Suggestion[]); + + mockDatasource.toExpression.mockReturnValue('datasource_expression'); + + mount(); + + expect(expressionRendererMock).toHaveBeenCalledTimes(1); + const passedExpression = fromExpression( + (expressionRendererMock as jest.Mock).mock.calls[0][0].expression + ); + expect(passedExpression).toMatchInlineSnapshot(` +Object { + "chain": Array [ + Object { + "arguments": Object {}, + "function": "datasource_expression", + "type": "function", + }, + Object { + "arguments": Object {}, + "function": "test", + "type": "function", + }, + Object { + "arguments": Object {}, + "function": "expression", + "type": "function", + }, + ], + "type": "expression", +} +`); + }); + + it('should render render icon if there is no preview expression', () => { + (getSuggestions as jest.Mock).mockReturnValue([ + { + datasourceState: {}, + previewIcon: 'visTable', + score: 0.5, + state: suggestion1State, + visualizationId: 'vis', + title: 'Suggestion1', + }, + { + datasourceState: {}, + previewIcon: 'empty', + score: 0.5, + state: suggestion2State, + visualizationId: 'vis', + title: 'Suggestion2', + previewExpression: 'test | expression', + }, + ] as Suggestion[]); + + mockDatasource.toExpression.mockReturnValue('datasource_expression'); + + const wrapper = mount(); + + expect(wrapper.find(EuiIcon)).toHaveLength(1); + expect(wrapper.find(EuiIcon).prop('type')).toEqual('visTable'); + }); +}); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx index 0ba6f17ba5b59..9be220cd3f11f 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiIcon, EuiTitle, EuiPanel } from '@elastic/eui'; +import { EuiIcon, EuiTitle, EuiPanel, EuiIconTip } from '@elastic/eui'; +import { toExpression } from '@kbn/interpreter/common'; +import { i18n } from '@kbn/i18n'; import { Action } from './state_management'; import { Datasource, Visualization } from '../../types'; -import { getSuggestions, toSwitchAction } from './suggestion_helpers'; +import { getSuggestions, toSwitchAction, Suggestion } from './suggestion_helpers'; import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/data/public'; import { prependDatasourceExpression } from './expression_helpers'; @@ -24,6 +26,70 @@ export interface SuggestionPanelProps { ExpressionRenderer: ExpressionRenderer; } +const SuggestionPreview = ({ + suggestion, + dispatch, + previewExpression, + ExpressionRenderer: ExpressionRendererComponent, +}: { + suggestion: Suggestion; + dispatch: (action: Action) => void; + ExpressionRenderer: ExpressionRenderer; + previewExpression?: string; +}) => { + const [expressionError, setExpressionError] = useState(false); + + useEffect( + () => { + setExpressionError(false); + }, + [previewExpression] + ); + + return ( + { + dispatch(toSwitchAction(suggestion)); + }} + > + +

{suggestion.title}

+
+ {expressionError ? ( +
+ +
+ ) : previewExpression ? ( + { + // eslint-disable-next-line no-console + console.error(`Failed to render preview: `, e); + setExpressionError(true); + }} + /> + ) : ( +
+ +
+ )} +
+ ); +}; + export function SuggestionPanel({ activeDatasource, datasourceState, @@ -63,31 +129,13 @@ export function SuggestionPanel({ ) : null; return ( - { - dispatch(toSwitchAction(suggestion)); - }} - > - -

{suggestion.title}

-
- {previewExpression ? ( - { - // TODO error handling - }} - /> - ) : ( -
- -
- )} -
+ ); })}
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx index 1294cbb2aa498..837f6c3646fa0 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx @@ -314,6 +314,7 @@ Object { title: 'my title', state: {}, datasourceSuggestionId: 0, + previewIcon: 'empty', }, ]); @@ -365,12 +366,14 @@ Object { isFirst: true, }, datasourceSuggestionId: 1, + previewIcon: 'empty', }, { score: 0.5, title: 'second suggestion', state: {}, datasourceSuggestionId: 0, + previewIcon: 'empty', }, ]); 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 678272922f013..b41b97f10b2a8 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 @@ -40,6 +40,9 @@ Object { "accessor": Array [ "a", ], + "hide": Array [ + false, + ], "position": Array [ "bottom", ], @@ -66,6 +69,9 @@ Object { "b", "c", ], + "hide": Array [ + false, + ], "position": Array [ "left", ], diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts index b034c9fe78b27..52c026bbc4df0 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts @@ -7,6 +7,7 @@ import { getSuggestions } from './xy_suggestions'; import { TableColumn, VisualizationSuggestion } from '../types'; import { State } from './types'; +import { Ast } from '@kbn/interpreter/target/common'; describe('xy_suggestions', () => { function numCol(columnId: string): TableColumn { @@ -230,4 +231,24 @@ Object { } `); }); + + test('adds a preview expression with disabled axes and legend', () => { + const [suggestion] = getSuggestions({ + tables: [ + { + datasourceSuggestionId: 0, + isMultiRow: true, + columns: [numCol('bytes'), dateCol('date')], + }, + ], + }); + + const expression = suggestion.previewExpression! as Ast; + + expect( + (expression.chain[0].arguments.legend[0] as Ast).chain[0].arguments.isVisible[0] + ).toBeFalsy(); + expect((expression.chain[0].arguments.x[0] as Ast).chain[0].arguments.hide[0]).toBeTruthy(); + expect((expression.chain[0].arguments.y[0] as Ast).chain[0].arguments.hide[0]).toBeTruthy(); + }); }); From bf2b35dbad6b6eb9d87375aad8db3c7de8bdab45 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 26 Jun 2019 10:14:49 +0200 Subject: [PATCH 3/4] PR review fixes --- .../public/datatable_visualization_plugin/visualization.tsx | 2 +- .../editor_frame_plugin/editor_frame/suggestion_panel.scss | 6 ++++-- .../lens/public/xy_visualization_plugin/to_expression.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx index 6d1004ff32f9f..d4d9476240e68 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx @@ -158,7 +158,7 @@ export const datatableVisualization: Visualization< > { return tables.map(table => { const title = i18n.translate('xpack.lens.datatable.visualizationOf', { - defaultMessage: 'Table: ${operations}', + defaultMessage: 'Table: {operations}', values: { operations: table.columns.map(col => col.operation.label).join(' & '), }, diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.scss index d87d8737f3b99..2139b314ae314 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.scss +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.scss @@ -4,9 +4,11 @@ } } +$suggestionHeight: 120px; + .lnsSidebar__suggestionIcon { width: 100%; - min-height: 120px; + height: $suggestionHeight; display: flex; align-items: center; justify-content: center; @@ -14,6 +16,6 @@ } .lnsSuggestionChartWrapper { - height: 100px; + height: $suggestionHeight; pointer-events: none; } \ No newline at end of file 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 999b02a75472f..8da060a9c6b66 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 @@ -61,7 +61,7 @@ export const toExpression = (state: State): Ast => ({ showGridlines: [state.y.showGridlines], position: [state.y.position], accessors: state.y.accessors, - hide: [Boolean(state.x.hide)], + hide: [Boolean(state.y.hide)], }, }, ], From d4e3cf9e5d6787fcff8f108ac9ab1f8ca65c19bb Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 26 Jun 2019 10:45:52 +0200 Subject: [PATCH 4/4] fix missing datasource arguments --- .../editor_frame/config_panel_wrapper.tsx | 27 ++++++++++--------- .../editor_frame/suggestion_panel.test.tsx | 1 + 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/config_panel_wrapper.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/config_panel_wrapper.tsx index 677b37beab190..1341266ba56a5 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/config_panel_wrapper.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/config_panel_wrapper.tsx @@ -23,18 +23,21 @@ function getSuggestedVisualizationState( visualization: Visualization, datasource: DatasourcePublicAPI ) { - const suggestions = visualization.getSuggestions({ - tables: [ - { - datasourceSuggestionId: 0, - isMultiRow: true, - columns: datasource.getTableSpec().map(col => ({ - ...col, - operation: datasource.getOperationForColumnId(col.columnId)!, - })), - }, - ], - }); + const suggestions = visualization.getSuggestions( + { + tables: [ + { + datasourceSuggestionId: 0, + isMultiRow: true, + columns: datasource.getTableSpec().map(col => ({ + ...col, + operation: datasource.getOperationForColumnId(col.columnId)!, + })), + }, + ], + }, + datasource + ); if (!suggestions.length) { return visualization.initialize(datasource); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx index e3409a8eca7e9..70e2366d38cc8 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx @@ -68,6 +68,7 @@ describe('suggestion_panel', () => { visualizationState: {}, dispatch: dispatchMock, ExpressionRenderer: expressionRendererMock, + datasourcePublicAPI: mockDatasource.publicAPIMock, }; });