From 33802fd436805337e8b00a5e48fec86b6dba44d3 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Wed, 19 Jun 2019 16:23:13 -0400 Subject: [PATCH 01/25] Lens basic metric visualization --- .../plugins/lens/public/app_plugin/plugin.tsx | 4 + .../metric_visualization_plugin/index.ts | 7 ++ .../metric_config_panel.test.tsx | 103 ++++++++++++++++++ .../metric_config_panel.tsx | 54 +++++++++ .../metric_expression.test.tsx | 75 +++++++++++++ .../metric_expression.tsx | 97 +++++++++++++++++ .../metric_suggestions.test.ts | 91 ++++++++++++++++ .../metric_suggestions.ts | 40 +++++++ .../metric_visualization.test.ts | 76 +++++++++++++ .../metric_visualization.tsx | 50 +++++++++ .../metric_visualization_plugin/plugin.tsx | 71 ++++++++++++ .../metric_visualization_plugin/types.ts | 13 +++ 12 files changed, 681 insertions(+) create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/index.ts create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/plugin.tsx create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/types.ts diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 857cee9adbc64..6d2de3ac92034 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { editorFrameSetup, editorFrameStop } from '../editor_frame_plugin'; import { indexPatternDatasourceSetup, indexPatternDatasourceStop } from '../indexpattern_plugin'; import { xyVisualizationSetup, xyVisualizationStop } from '../xy_visualization_plugin'; +import { metricVisualizationSetup, metricVisualizationStop } from '../metric_visualization_plugin'; import { App } from './app'; import { EditorFrameInstance } from '../types'; @@ -21,10 +22,12 @@ export class AppPlugin { // entry point to the app we have no choice until the new platform is ready const indexPattern = indexPatternDatasourceSetup(); const xyVisualization = xyVisualizationSetup(); + const metricVisualization = metricVisualizationSetup(); const editorFrame = editorFrameSetup(); editorFrame.registerDatasource('indexpattern', indexPattern); editorFrame.registerVisualization('xy', xyVisualization); + editorFrame.registerVisualization('metric', metricVisualization); this.instance = editorFrame.createInstance({}); @@ -39,6 +42,7 @@ export class AppPlugin { // TODO this will be handled by the plugin platform itself indexPatternDatasourceStop(); xyVisualizationStop(); + metricVisualizationStop(); editorFrameStop(); } } diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/index.ts b/x-pack/plugins/lens/public/metric_visualization_plugin/index.ts new file mode 100644 index 0000000000000..f75dce9b7507f --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export * from './plugin'; diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx new file mode 100644 index 0000000000000..956edeb84bc8c --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx @@ -0,0 +1,103 @@ +/* + * 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 { ReactWrapper } from 'enzyme'; +import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; +import { MetricConfigPanel } from './metric_config_panel'; +import { DatasourcePublicAPI, DatasourceDimensionPanelProps, Operation } from '../types'; +import { State } from './types'; +import { NativeRendererProps } from '../native_renderer'; + +describe('MetricConfigPanel', () => { + const dragDropContext = { dragging: undefined, setDragging: jest.fn() }; + + function mockDatasource(): DatasourcePublicAPI { + return { + duplicateColumn: () => [], + getOperationForColumnId: () => null, + generateColumnId: () => 'TESTID', + getTableSpec: () => [], + moveColumnTo: () => {}, + removeColumnInTableSpec: () => [], + renderDimensionPanel: () => {}, + }; + } + + function testState(): State { + return { + title: 'Test Metric', + accessor: 'foo', + }; + } + + function testSubj(component: ReactWrapper, subj: string) { + return component + .find(`[data-test-subj="${subj}"]`) + .first() + .props(); + } + + test('allows editing the chart title', () => { + const testSetTitle = (title: string) => { + const setState = jest.fn(); + const component = mount( + + ); + + (testSubj(component, 'lnsMetric_title').onChange as Function)({ target: { value: title } }); + + expect(setState).toHaveBeenCalledTimes(1); + return setState.mock.calls[0][0]; + }; + + expect(testSetTitle('Hoi')).toMatchObject({ + title: 'Hoi', + }); + expect(testSetTitle('There!')).toMatchObject({ + title: 'There!', + }); + }); + + test('the value dimension panel only accepts singular numeric operations', () => { + const datasource = { + ...mockDatasource(), + renderDimensionPanel: jest.fn(), + }; + const state = testState(); + const component = mount( + + ); + + const panel = testSubj(component, 'lns_metric_valueDimensionPanel'); + const nativeProps = (panel as NativeRendererProps).nativeProps; + const { columnId, filterOperations } = nativeProps; + const exampleOperation: Operation = { + dataType: 'number', + id: 'foo', + isBucketed: false, + label: 'bar', + }; + const ops: Operation[] = [ + { ...exampleOperation, dataType: 'number' }, + { ...exampleOperation, dataType: 'string' }, + { ...exampleOperation, dataType: 'boolean' }, + { ...exampleOperation, dataType: 'date' }, + ]; + expect(columnId).toEqual('shazm'); + expect(ops.filter(filterOperations)).toEqual([{ ...exampleOperation, dataType: 'number' }]); + }); +}); diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx new file mode 100644 index 0000000000000..082bf519326c1 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx @@ -0,0 +1,54 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui'; +import { State } from './types'; +import { VisualizationProps, Operation } from '../types'; +import { NativeRenderer } from '../native_renderer'; + +export function MetricConfigPanel(props: VisualizationProps) { + const { state, datasource, setState } = props; + + return ( + + + setState({ ...state, title: e.target.value })} + aria-label={i18n.translate('xpack.lens.metric.chartTitleAriaLabel', { + defaultMessage: 'Title', + })} + /> + + + + !op.isBucketed && op.dataType === 'number', + }} + /> + + + ); +} diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx new file mode 100644 index 0000000000000..8eca60f63d5d0 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -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 { metricChart, MetricChart } from './metric_expression'; +import { KibanaDatatable } from '../types'; +import React from 'react'; +import { shallow } from 'enzyme'; +import { MetricConfig } from './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 }], + }; + + const args: MetricConfig = { + title: 'My fanci metric chart', + accessor: 'a', + }; + + return { data, args }; +} + +describe('metric_expression', () => { + describe('metricChart', () => { + test('it renders with the specified data and args', () => { + const { data, args } = sampleArgs(); + + expect(metricChart.fn(data, args, {})).toEqual({ + type: 'render', + as: 'lens_metric_chart_renderer', + value: { data, args }, + }); + }); + }); + + describe('MetricChart component', () => { + test('it renders the title and value', () => { + const { data, args } = sampleArgs(); + + expect(shallow()).toMatchInlineSnapshot(` + + +
+ 1 +
+ + My fanci metric chart + +
+
+`); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx new file mode 100644 index 0000000000000..eb558131774d8 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -0,0 +1,97 @@ +/* + * 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 ReactDOM from 'react-dom'; +import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { MetricConfig } from './types'; +import { KibanaDatatable } from '../types'; +import { RenderFunction } from './plugin'; + +export interface MetricChartProps { + data: KibanaDatatable; + args: MetricConfig; +} + +export interface MetricRender { + type: 'render'; + as: 'lens_metric_chart_renderer'; + value: MetricChartProps; +} + +export const metricChart: ExpressionFunction< + 'lens_metric_chart', + KibanaDatatable, + MetricConfig, + MetricRender +> = ({ + name: 'lens_metric_chart', + type: 'render', + help: 'A metric chart', + args: { + title: { + types: ['string'], + help: 'The chart title.', + }, + accessor: { + types: ['string'], + help: 'The column whose value is being displayed', + }, + }, + context: { + types: ['kibana_datatable'], + }, + fn(data: KibanaDatatable, args: MetricChartProps) { + return { + type: 'render', + as: 'lens_metric_chart_renderer', + value: { + data, + args, + }, + }; + }, + // TODO the typings currently don't support custom type args. As soon as they do, this can be removed +} as unknown) as ExpressionFunction< + 'lens_metric_chart', + KibanaDatatable, + MetricConfig, + MetricRender +>; + +export interface MetricChartProps { + data: KibanaDatatable; + args: MetricConfig; +} + +export const metricChartRenderer: RenderFunction = { + name: 'lens_metric_chart_renderer', + displayName: 'Metric Chart', + help: 'Metric Chart Renderer', + validate: () => {}, + reuseDomNode: true, + render: async (domNode: Element, config: MetricChartProps, _handlers: unknown) => { + ReactDOM.render(, domNode); + }, +}; + +export function MetricChart({ data, args }: MetricChartProps) { + const { title, accessor } = args; + const row = data.rows[0] as { [k: string]: number }; + // TODO: Use field formatters here... + const value = Number(Number(row[accessor]).toFixed(3)).toString(); + + return ( + + + {/* TODO: Auto-scale the text on resizes */} +
{value}
+ {title} +
+
+ ); +} diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts new file mode 100644 index 0000000000000..1294746a91214 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts @@ -0,0 +1,91 @@ +/* + * 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 './metric_suggestions'; +import { TableColumn } from '../types'; + +describe('metric_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`, + }, + }; + } + + 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: true, columns: [numCol('bar')] }, + { datasourceSuggestionId: 3, isMultiRow: true, columns: [unknownCol(), numCol('bar')] }, + { datasourceSuggestionId: 4, isMultiRow: false, columns: [numCol('bar'), numCol('baz')] }, + ], + }) + ).toEqual([]); + }); + + test('suggests a basic metric chart', () => { + const [suggestion, ...rest] = getSuggestions({ + tables: [ + { + datasourceSuggestionId: 0, + isMultiRow: false, + columns: [numCol('bytes')], + }, + ], + }); + + expect(rest).toHaveLength(0); + expect(suggestion).toMatchInlineSnapshot(` +Object { + "datasourceSuggestionId": 0, + "score": 1, + "state": Object { + "accessor": "bytes", + "title": "Avg bytes", + }, + "title": "Avg bytes", +} +`); + }); +}); diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts new file mode 100644 index 0000000000000..3e5898d180ad4 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts @@ -0,0 +1,40 @@ +/* + * 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 { SuggestionRequest, VisualizationSuggestion, TableSuggestion } from '../types'; +import { State } from './types'; + +/** + * Generate suggestions for the metric chart. + * + * @param opts + */ +export function getSuggestions( + opts: SuggestionRequest +): Array> { + return opts.tables + .filter( + ({ isMultiRow, columns }) => + // We only render metric charts for single-row queries. We require a single, numeric column. + !isMultiRow && columns.length === 1 && columns[0].operation.dataType === 'number' + ) + .map(table => getSuggestion(table)); +} + +function getSuggestion(table: TableSuggestion): VisualizationSuggestion { + const col = table.columns[0]; + const title = col.operation.label; + + return { + title, + score: 1, + datasourceSuggestionId: table.datasourceSuggestionId, + state: { + title, + accessor: col.columnId, + }, + }; +} diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts new file mode 100644 index 0000000000000..2deac417b28c2 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts @@ -0,0 +1,76 @@ +/* + * 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 { metricVisualization } from './metric_visualization'; +import { DatasourcePublicAPI } from '../types'; +import { State } from './types'; +import { createMockDatasource } from '../editor_frame_plugin/mocks'; + +function exampleState(): State { + return { + title: 'Foo', + accessor: 'a', + }; +} + +describe('metric_visualization', () => { + describe('#initialize', () => { + it('loads default state', () => { + const mockDatasource = createMockDatasource(); + mockDatasource.publicAPIMock.generateColumnId + .mockReturnValue('test-id1') + .mockReturnValueOnce('test-id2'); + const initialState = metricVisualization.initialize(mockDatasource.publicAPIMock); + + expect(initialState.accessor).toBeDefined(); + expect(initialState.title).toBeDefined(); + + expect(initialState).toMatchInlineSnapshot(` +Object { + "accessor": "test-id2", + "title": "Empty Metric Chart", +} +`); + }); + + it('loads from persisted state', () => { + expect( + metricVisualization.initialize(createMockDatasource().publicAPIMock, exampleState()) + ).toEqual(exampleState()); + }); + }); + + describe('#getPersistableState', () => { + it('persists the state as given', () => { + expect(metricVisualization.getPersistableState(exampleState())).toEqual(exampleState()); + }); + }); + + describe('#toExpression', () => { + it('should map to a valid AST', () => { + expect(metricVisualization.toExpression(exampleState(), {} as DatasourcePublicAPI)) + .toMatchInlineSnapshot(` +Object { + "chain": Array [ + Object { + "arguments": Object { + "accessor": Array [ + "a", + ], + "title": Array [ + "Foo", + ], + }, + "function": "lens_metric_chart", + "type": "function", + }, + ], + "type": "expression", +} +`); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx new file mode 100644 index 0000000000000..9f4b232f3fe04 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx @@ -0,0 +1,50 @@ +/* + * 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 { render } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; +import { getSuggestions } from './metric_suggestions'; +import { MetricConfigPanel } from './metric_config_panel'; +import { Visualization } from '../types'; +import { State, PersistableState } from './types'; + +export const metricVisualization: Visualization = { + getSuggestions, + + initialize(datasource, state) { + return ( + state || { + title: 'Empty Metric Chart', + accessor: datasource.generateColumnId(), + } + ); + }, + + getPersistableState: state => state, + + renderConfigPanel: (domElement, props) => + render( + + + , + domElement + ), + + toExpression: state => ({ + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_metric_chart', + arguments: { + title: [state.title], + accessor: [state.accessor], + }, + }, + ], + }), +}; diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/plugin.tsx b/x-pack/plugins/lens/public/metric_visualization_plugin/plugin.tsx new file mode 100644 index 0000000000000..1028fbf4ec8b6 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/plugin.tsx @@ -0,0 +1,71 @@ +/* + * 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 { Registry } from '@kbn/interpreter/target/common'; +import { CoreSetup } from 'src/core/public'; +import { metricVisualization } from './metric_visualization'; +import { + renderersRegistry, + functionsRegistry, + // @ts-ignore untyped dependency +} from '../../../../../src/legacy/core_plugins/interpreter/public/registries'; +import { ExpressionFunction } from '../../../../../src/legacy/core_plugins/interpreter/public'; +import { metricChart, metricChartRenderer } from './metric_expression'; + +// TODO these are intermediary types because interpreter is not typed yet +// They can get replaced by references to the real interfaces as soon as they +// are available +interface RenderHandlers { + done: () => void; + onDestroy: (fn: () => void) => void; +} + +export interface RenderFunction { + name: string; + displayName: string; + help: string; + validate: () => void; + reuseDomNode: boolean; + render: (domNode: Element, data: T, handlers: RenderHandlers) => void; +} + +export interface InterpreterSetup { + renderersRegistry: Registry; + functionsRegistry: Registry< + ExpressionFunction, + ExpressionFunction + >; +} + +export interface MetricVisualizationPluginSetupPlugins { + interpreter: InterpreterSetup; +} + +class MetricVisualizationPlugin { + constructor() {} + + setup(_core: CoreSetup | null, { interpreter }: MetricVisualizationPluginSetupPlugins) { + interpreter.functionsRegistry.register(() => metricChart); + + interpreter.renderersRegistry.register(() => metricChartRenderer as RenderFunction); + + return metricVisualization; + } + + stop() {} +} + +const plugin = new MetricVisualizationPlugin(); + +export const metricVisualizationSetup = () => + plugin.setup(null, { + interpreter: { + renderersRegistry, + functionsRegistry, + }, + }); + +export const metricVisualizationStop = () => plugin.stop(); diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/types.ts b/x-pack/plugins/lens/public/metric_visualization_plugin/types.ts new file mode 100644 index 0000000000000..42f93089896c8 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/types.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export interface MetricConfig { + accessor: string; + title: string; +} + +export type State = MetricConfig; +export type PersistableState = MetricConfig; From f5c809a14860563e0f00d9cd955e77f8611ebc84 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 24 Jun 2019 14:33:10 -0400 Subject: [PATCH 02/25] Fix merge issues, localize expression help text --- .../metric_visualization_plugin/index.ts | 0 .../metric_config_panel.test.tsx | 8 +--- .../metric_config_panel.tsx | 0 .../metric_expression.test.tsx | 0 .../metric_expression.tsx | 2 +- .../metric_suggestions.test.ts | 0 .../metric_suggestions.ts | 0 .../metric_visualization.test.ts | 0 .../metric_visualization.tsx | 0 .../metric_visualization_plugin/plugin.tsx | 2 +- .../metric_visualization_plugin/types.ts | 0 .../public/xy_visualization_plugin/types.ts | 29 +++++++++++---- .../xy_visualization_plugin/xy_expression.tsx | 37 ++++++++++++++----- 13 files changed, 54 insertions(+), 24 deletions(-) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/index.ts (100%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx (93%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx (100%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx (100%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx (97%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts (100%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts (100%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts (100%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx (100%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/plugin.tsx (95%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/types.ts (100%) diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/index.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/index.ts similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/index.ts rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/index.ts diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx similarity index 93% rename from x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx index 956edeb84bc8c..0c38c86b51c43 100644 --- a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx @@ -11,19 +11,15 @@ import { MetricConfigPanel } from './metric_config_panel'; import { DatasourcePublicAPI, DatasourceDimensionPanelProps, Operation } from '../types'; import { State } from './types'; import { NativeRendererProps } from '../native_renderer'; +import { createMockDatasource } from '../editor_frame_plugin/mocks'; describe('MetricConfigPanel', () => { const dragDropContext = { dragging: undefined, setDragging: jest.fn() }; function mockDatasource(): DatasourcePublicAPI { return { - duplicateColumn: () => [], - getOperationForColumnId: () => null, + ...createMockDatasource().publicAPIMock, generateColumnId: () => 'TESTID', - getTableSpec: () => [], - moveColumnTo: () => {}, - removeColumnInTableSpec: () => [], - renderDimensionPanel: () => {}, }; } diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx similarity index 97% rename from x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index eb558131774d8..638c33a28e47c 100644 --- a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -81,7 +81,7 @@ export const metricChartRenderer: RenderFunction = { export function MetricChart({ data, args }: MetricChartProps) { const { title, accessor } = args; - const row = data.rows[0] as { [k: string]: number }; + const row = data.rows[0]; // TODO: Use field formatters here... const value = Number(Number(row[accessor]).toFixed(3)).toString(); diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/plugin.tsx similarity index 95% rename from x-pack/plugins/lens/public/metric_visualization_plugin/plugin.tsx rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/plugin.tsx index 1028fbf4ec8b6..4344dc0e21b3b 100644 --- a/x-pack/plugins/lens/public/metric_visualization_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/plugin.tsx @@ -12,7 +12,7 @@ import { functionsRegistry, // @ts-ignore untyped dependency } from '../../../../../src/legacy/core_plugins/interpreter/public/registries'; -import { ExpressionFunction } from '../../../../../src/legacy/core_plugins/interpreter/public'; +import { ExpressionFunction } from '../../../../../../src/legacy/core_plugins/interpreter/public'; import { metricChart, metricChartRenderer } from './metric_expression'; // TODO these are intermediary types because interpreter is not typed yet diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/types.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/types.ts rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts 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..5f7773e6c3ad8 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 @@ -5,6 +5,7 @@ */ import { Position } from '@elastic/charts'; +import { i18n } from '@kbn/i18n'; import { ExpressionFunction, ArgumentType, @@ -33,12 +34,16 @@ export const legendConfig: ExpressionFunction< args: { isVisible: { types: ['boolean'], - help: 'Specifies whether or not the legend is visible.', + help: i18n.translate('xpack.lens.xyChart.isVisible.help', { + defaultMessage: 'Specifies whether or not the legend is visible.', + }), }, position: { types: ['string'], options: [Position.Top, Position.Right, Position.Bottom, Position.Left], - help: 'Specifies the legend position.', + help: i18n.translate('xpack.lens.xyChart.position.help', { + defaultMessage: 'Specifies the legend position.', + }), }, }, fn: function fn(_context: unknown, args: LegendConfig) { @@ -58,16 +63,22 @@ interface AxisConfig { const axisConfig: { [key in keyof AxisConfig]: ArgumentType } = { title: { types: ['string'], - help: 'The axis title', + help: i18n.translate('xpack.lens.xyChart.title.help', { + defaultMessage: 'The axis title', + }), }, showGridlines: { types: ['boolean'], - help: 'Show / hide axis grid lines.', + help: i18n.translate('xpack.lens.xyChart.showGridlines.help', { + defaultMessage: 'Show / hide axis grid lines.', + }), }, position: { types: ['string'], options: [Position.Top, Position.Right, Position.Bottom, Position.Left], - help: 'The position of the axis', + help: i18n.translate('xpack.lens.xyChart.axisPosition.help', { + defaultMessage: 'The position of the axis', + }), }, }; @@ -89,7 +100,9 @@ export const yConfig: ExpressionFunction<'lens_xy_yConfig', null, YConfig, YConf ...axisConfig, accessors: { types: ['string'], - help: 'The columns to display on the y axis.', + help: i18n.translate('xpack.lens.xyChart.accessors.help', { + defaultMessage: 'The columns to display on the y axis.', + }), multi: true, }, }, @@ -119,7 +132,9 @@ export const xConfig: ExpressionFunction<'lens_xy_xConfig', null, XConfig, XConf ...axisConfig, accessor: { types: ['string'], - help: 'The column to display on the x axis.', + help: i18n.translate('xpack.lens.xyChart.accessor.help', { + defaultMessage: 'The column to display on the x axis.', + }), }, }, fn: function fn(_context: unknown, args: XConfig) { 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 9b2b9290b54f1..0f436ed291aa3 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 @@ -17,6 +17,7 @@ import { BarSeries, } from '@elastic/charts'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; +import { i18n } from '@kbn/i18n'; import { XYArgs } from './types'; import { KibanaDatatable } from '../types'; import { RenderFunction } from './plugin'; @@ -35,38 +36,54 @@ export interface XYRender { export const xyChart: ExpressionFunction<'lens_xy_chart', KibanaDatatable, XYArgs, XYRender> = ({ name: 'lens_xy_chart', type: 'render', - help: 'An X/Y chart', + help: i18n.translate('xpack.lens.xyChart.help', { + defaultMessage: 'An X/Y chart', + }), args: { seriesType: { types: ['string'], options: ['bar', 'line', 'area'], - help: 'The type of chart to display.', + help: i18n.translate('xpack.lens.xyChart.seriesType.help', { + defaultMessage: 'The type of chart to display.', + }), }, title: { types: ['string'], - help: 'The char title.', + help: i18n.translate('xpack.lens.xyChart.chartTitle.help', { + defaultMessage: 'The chart title.', + }), }, legend: { types: ['lens_xy_legendConfig'], - help: 'Configure the chart legend.', + help: i18n.translate('xpack.lens.xyChart.legend.help', { + defaultMessage: 'Configure the chart legend.', + }), }, y: { types: ['lens_xy_yConfig'], - help: 'The y axis configuration', + help: i18n.translate('xpack.lens.xyChart.yConfig.help', { + defaultMessage: 'The y axis configuration', + }), }, x: { types: ['lens_xy_xConfig'], - help: 'The x axis configuration', + help: i18n.translate('xpack.lens.xyChart.xConfig.help', { + defaultMessage: 'The x axis configuration', + }), }, splitSeriesAccessors: { types: ['string'], multi: true, - help: 'The columns used to split the series.', + help: i18n.translate('xpack.lens.xyChart.splitSeriesAccessors.help', { + defaultMessage: 'The columns used to split the series.', + }), }, stackAccessors: { types: ['string'], multi: true, - help: 'The columns used to stack the series.', + help: i18n.translate('xpack.lens.xyChart.stackAccessors.help', { + defaultMessage: 'The columns used to stack the series.', + }), }, }, context: { @@ -93,7 +110,9 @@ export interface XYChartProps { export const xyChartRenderer: RenderFunction = { name: 'lens_xy_chart_renderer', displayName: 'XY Chart', - help: 'X/Y Chart Renderer', + help: i18n.translate('xpack.lens.xyChart.renderer.help', { + defaultMessage: 'X/Y Chart Renderer', + }), validate: () => {}, reuseDomNode: true, render: async (domNode: Element, config: XYChartProps, _handlers: unknown) => { From c159b4ebd79c4e96de8ae6f7d003e8dd1359f4c1 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Thu, 20 Jun 2019 13:12:05 -0400 Subject: [PATCH 03/25] Add auto-scaling to the lens metric visualization --- .../metric_expression.tsx | 16 +-- .../auto_scale.test.tsx | 59 +++++++++++ .../auto_scale.tsx | 100 ++++++++++++++++++ 3 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx create mode 100644 x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index 638c33a28e47c..e7c33837d34a9 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { MetricConfig } from './types'; import { KibanaDatatable } from '../types'; import { RenderFunction } from './plugin'; +import { AutoScale } from './auto_scale'; export interface MetricChartProps { data: KibanaDatatable; @@ -86,12 +87,13 @@ export function MetricChart({ data, args }: MetricChartProps) { const value = Number(Number(row[accessor]).toFixed(3)).toString(); return ( - - - {/* TODO: Auto-scale the text on resizes */} -
{value}
- {title} -
-
+ + + +
{value}
+ {title} +
+
+
); } diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx b/x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx new file mode 100644 index 0000000000000..c373203056ae1 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx @@ -0,0 +1,59 @@ +/* + * 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 { computeScale, AutoScale } from './auto_scale'; +import { render } from 'enzyme'; + +const mockElement = (clientWidth = 100, clientHeight = 200) => ({ + clientHeight, + clientWidth, +}); + +describe('AutoScale', () => { + describe('computeScale', () => { + it('is 1 if any element is null', () => { + expect(computeScale(null, null)).toBe(1); + expect(computeScale(mockElement(), null)).toBe(1); + expect(computeScale(null, mockElement())).toBe(1); + }); + + it('is never over 1', () => { + expect(computeScale(mockElement(2000, 2000), mockElement(1000, 1000))).toBe(1); + }); + + it('is the lesser of the x or y scale', () => { + expect(computeScale(mockElement(1000, 2000), mockElement(3000, 8000))).toBe(0.25); + expect(computeScale(mockElement(2000, 3000), mockElement(4000, 3200))).toBe(0.5); + }); + }); + + describe('AutoScale', () => { + it('renders', () => { + expect( + render( + +

Hoi!

+
+ ) + ).toMatchInlineSnapshot(` +
+
+

+ Hoi! +

+
+
+`); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx b/x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx new file mode 100644 index 0000000000000..fa7a04c2b3a51 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx @@ -0,0 +1,100 @@ +/* + * 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'; + +interface Props { + children: React.ReactNode | React.ReactNode[]; +} + +interface State { + scale: number; +} + +export class AutoScale extends React.Component { + private child: Element | null = null; + private parent: Element | null = null; + + constructor(props: Props) { + super(props); + + this.state = { scale: 1 }; + } + + setParent(el: Element | null) { + if (this.parent !== el) { + this.parent = el; + setTimeout(() => this.scale()); + } + } + + setChild(el: Element | null) { + if (this.child !== el) { + this.child = el; + setTimeout(() => this.scale()); + } + } + + scale() { + const scale = computeScale(this.parent, this.child); + + // Prevent an infinite render loop + if (this.state.scale !== scale) { + this.setState({ scale }); + } + } + + render() { + const { children } = this.props; + const { scale } = this.state; + + return ( +
this.setParent(el)} + style={{ + display: 'flex', + justifyContent: 'center', + position: 'relative', + }} + > +
this.setChild(el)} + style={{ + transform: `scale(${scale})`, + position: 'relative', + }} + > + {children} +
+
+ ); + } +} + +interface ClientDimensionable { + clientWidth: number; + clientHeight: number; +} + +/** + * computeScale computes the ratio by which the child needs to shrink in order + * to fit into the parent. This function is only exported for testing purposes. + */ +export function computeScale( + parent: ClientDimensionable | null, + child: ClientDimensionable | null +) { + if (!parent || !child) { + return 1; + } + + const scaleX = parent.clientWidth / child.clientWidth; + const scaleY = parent.clientHeight / child.clientHeight; + + return Math.min(1, Math.min(scaleX, scaleY)); +} From 356e9b6ef0141b22a3bf1407f5dad51cd261d81d Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Thu, 20 Jun 2019 13:18:25 -0400 Subject: [PATCH 04/25] Fix unit tests broken by autoscale --- .../metric_expression.test.tsx | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index 8eca60f63d5d0..bd0f8ba80bb40 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -43,32 +43,34 @@ describe('metric_expression', () => { const { data, args } = sampleArgs(); expect(shallow()).toMatchInlineSnapshot(` - - + -
- 1 -
- - My fanci metric chart - -
-
+
+ 1 +
+ + My fanci metric chart + + + + `); }); }); From 6aea5dcb57d6587b29e45f7420bf587cdc37a2ba Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 24 Jun 2019 14:43:27 -0400 Subject: [PATCH 05/25] Move autoscale to the new Lens folder --- .../lens/public/metric_visualization_plugin/auto_scale.test.tsx | 0 .../lens/public/metric_visualization_plugin/auto_scale.tsx | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx (100%) rename x-pack/{ => legacy}/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx (100%) diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx diff --git a/x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx rename to x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx From 2dc623fbaafd339b3d28437722531248777ec7d5 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Wed, 17 Jul 2019 14:21:44 -0400 Subject: [PATCH 06/25] Add metric preview icon --- .../metric_suggestions.test.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts index 1294746a91214..6feadddf8d9a9 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts @@ -77,15 +77,16 @@ describe('metric_suggestions', () => { expect(rest).toHaveLength(0); expect(suggestion).toMatchInlineSnapshot(` -Object { - "datasourceSuggestionId": 0, - "score": 1, - "state": Object { - "accessor": "bytes", - "title": "Avg bytes", - }, - "title": "Avg bytes", -} -`); + Object { + "datasourceSuggestionId": 0, + "previewIcon": "visMetric", + "score": 1, + "state": Object { + "accessor": "bytes", + "title": "Avg bytes", + }, + "title": "Avg bytes", + } + `); }); }); From bae8a6cc97bcf8b420548d290be6c9af6e207cae Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Wed, 17 Jul 2019 14:33:38 -0400 Subject: [PATCH 07/25] Fix metric vis tests --- .../metric_visualization.test.ts | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts index 6abf703613a55..62c5c44107766 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts @@ -10,6 +10,8 @@ import { State } from './types'; import { createMockDatasource } from '../editor_frame_plugin/mocks'; import { generateId } from '../id_generator'; +jest.mock('../id_generator'); + function exampleState(): State { return { title: 'Foo', @@ -21,18 +23,18 @@ describe('metric_visualization', () => { describe('#initialize', () => { it('loads default state', () => { const mockDatasource = createMockDatasource(); - (generateId as jest.Mock).mockReturnValueOnce('test-id1').mockReturnValueOnce('test-id2'); + (generateId as jest.Mock).mockReturnValueOnce('test-id1'); const initialState = metricVisualization.initialize(mockDatasource.publicAPIMock); expect(initialState.accessor).toBeDefined(); expect(initialState.title).toBeDefined(); expect(initialState).toMatchInlineSnapshot(` -Object { - "accessor": "test-id2", - "title": "Empty Metric Chart", -} -`); + Object { + "accessor": "test-id1", + "title": "Empty Metric Chart", + } + `); }); it('loads from persisted state', () => { @@ -52,24 +54,24 @@ Object { it('should map to a valid AST', () => { expect(metricVisualization.toExpression(exampleState(), {} as DatasourcePublicAPI)) .toMatchInlineSnapshot(` -Object { - "chain": Array [ - Object { - "arguments": Object { - "accessor": Array [ - "a", - ], - "title": Array [ - "Foo", - ], - }, - "function": "lens_metric_chart", - "type": "function", - }, - ], - "type": "expression", -} -`); + Object { + "chain": Array [ + Object { + "arguments": Object { + "accessor": Array [ + "a", + ], + "title": Array [ + "Foo", + ], + }, + "function": "lens_metric_chart", + "type": "function", + }, + ], + "type": "expression", + } + `); }); }); }); From 4ee8f632de55808ade8438a4a69efeecf000b53e Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Wed, 17 Jul 2019 14:39:37 -0400 Subject: [PATCH 08/25] Fix metric plugin imports --- .../plugins/lens/public/metric_visualization_plugin/plugin.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/plugin.tsx index 4344dc0e21b3b..8e55c988ade30 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/plugin.tsx @@ -10,8 +10,7 @@ import { metricVisualization } from './metric_visualization'; import { renderersRegistry, functionsRegistry, - // @ts-ignore untyped dependency -} from '../../../../../src/legacy/core_plugins/interpreter/public/registries'; +} from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { ExpressionFunction } from '../../../../../../src/legacy/core_plugins/interpreter/public'; import { metricChart, metricChartRenderer } from './metric_expression'; From 218cf00faa31e7c5fdf7c5fb97204066c27aea70 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Wed, 17 Jul 2019 15:30:59 -0400 Subject: [PATCH 09/25] Use the operation label as the metric title --- .../metric_config_panel.test.tsx | 27 ------------------ .../metric_config_panel.tsx | 22 ++------------- .../metric_suggestions.test.ts | 1 - .../metric_suggestions.ts | 1 - .../metric_visualization.test.ts | 23 +++++++++------ .../metric_visualization.tsx | 28 ++++++++++--------- .../metric_visualization_plugin/types.ts | 1 - 7 files changed, 32 insertions(+), 71 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx index 14ee0ab048b57..a755cbc690e45 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx @@ -22,7 +22,6 @@ describe('MetricConfigPanel', () => { function testState(): State { return { - title: 'Test Metric', accessor: 'foo', }; } @@ -34,32 +33,6 @@ describe('MetricConfigPanel', () => { .props(); } - test('allows editing the chart title', () => { - const testSetTitle = (title: string) => { - const setState = jest.fn(); - const component = mount( - - ); - - (testSubj(component, 'lnsMetric_title').onChange as Function)({ target: { value: title } }); - - expect(setState).toHaveBeenCalledTimes(1); - return setState.mock.calls[0][0]; - }; - - expect(testSetTitle('Hoi')).toMatchObject({ - title: 'Hoi', - }); - expect(testSetTitle('There!')).toMatchObject({ - title: 'There!', - }); - }); - test('the value dimension panel only accepts singular numeric operations', () => { const datasource = { ...mockDatasource(), diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx index 082bf519326c1..aa60d79f4ebb1 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx @@ -6,34 +6,16 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui'; +import { EuiForm, EuiFormRow } from '@elastic/eui'; import { State } from './types'; import { VisualizationProps, Operation } from '../types'; import { NativeRenderer } from '../native_renderer'; export function MetricConfigPanel(props: VisualizationProps) { - const { state, datasource, setState } = props; + const { state, datasource } = props; return ( - - setState({ ...state, title: e.target.value })} - aria-label={i18n.translate('xpack.lens.metric.chartTitleAriaLabel', { - defaultMessage: 'Title', - })} - /> - - { "score": 1, "state": Object { "accessor": "bytes", - "title": "Avg bytes", }, "title": "Avg bytes", } diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts index 8d2c05cdbbdcc..d99f0fbeaa114 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts @@ -34,7 +34,6 @@ function getSuggestion(table: TableSuggestion): VisualizationSuggestion { datasourceSuggestionId: table.datasourceSuggestionId, previewIcon: 'visMetric', state: { - title, accessor: col.columnId, }, }; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts index 62c5c44107766..409eee06185bb 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts @@ -5,16 +5,15 @@ */ import { metricVisualization } from './metric_visualization'; -import { DatasourcePublicAPI } from '../types'; import { State } from './types'; import { createMockDatasource } from '../editor_frame_plugin/mocks'; import { generateId } from '../id_generator'; +import { DatasourcePublicAPI } from '../types'; jest.mock('../id_generator'); function exampleState(): State { return { - title: 'Foo', accessor: 'a', }; } @@ -27,12 +26,9 @@ describe('metric_visualization', () => { const initialState = metricVisualization.initialize(mockDatasource.publicAPIMock); expect(initialState.accessor).toBeDefined(); - expect(initialState.title).toBeDefined(); - expect(initialState).toMatchInlineSnapshot(` Object { "accessor": "test-id1", - "title": "Empty Metric Chart", } `); }); @@ -52,8 +48,19 @@ describe('metric_visualization', () => { describe('#toExpression', () => { it('should map to a valid AST', () => { - expect(metricVisualization.toExpression(exampleState(), {} as DatasourcePublicAPI)) - .toMatchInlineSnapshot(` + const datasource: DatasourcePublicAPI = { + ...createMockDatasource().publicAPIMock, + getOperationForColumnId(_: string) { + return { + id: 'a', + dataType: 'number', + isBucketed: false, + label: 'shazm', + }; + }, + }; + + expect(metricVisualization.toExpression(exampleState(), datasource)).toMatchInlineSnapshot(` Object { "chain": Array [ Object { @@ -62,7 +69,7 @@ describe('metric_visualization', () => { "a", ], "title": Array [ - "Foo", + "shazm", ], }, "function": "lens_metric_chart", diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx index 60f006398b086..d871d30080891 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx @@ -19,7 +19,6 @@ export const metricVisualization: Visualization = { initialize(_, state) { return ( state || { - title: 'Empty Metric Chart', accessor: generateId(), } ); @@ -35,17 +34,20 @@ export const metricVisualization: Visualization = { domElement ), - toExpression: state => ({ - type: 'expression', - chain: [ - { - type: 'function', - function: 'lens_metric_chart', - arguments: { - title: [state.title], - accessor: [state.accessor], + toExpression(state, datasource) { + const operation = datasource.getOperationForColumnId(state.accessor); + return { + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_metric_chart', + arguments: { + title: [(operation && operation.label) || ''], + accessor: [state.accessor], + }, }, - }, - ], - }), + ], + }; + }, }; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts index 42f93089896c8..cd02d36577262 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts @@ -6,7 +6,6 @@ export interface MetricConfig { accessor: string; - title: string; } export type State = MetricConfig; From 59e19d9ed6ead96c2b21c31ba96d398be2e705b1 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Thu, 8 Aug 2019 20:44:31 -0400 Subject: [PATCH 10/25] Add metric suggestions, fix tests --- .../plugins/lens/public/app_plugin/plugin.tsx | 4 +- .../editor_frame/editor_frame.test.tsx | 7 +- .../editor_frame/suggestion_helpers.test.ts | 5 +- .../editor_frame/suggestion_helpers.ts | 41 ++-- .../indexpattern_plugin/indexpattern.test.tsx | 2 +- ...stions.tsx => indexpattern_suggestions.ts} | 205 ++++++++++++++++-- .../metric_config_panel.test.tsx | 15 +- .../metric_config_panel.tsx | 11 +- .../metric_expression.test.tsx | 76 ++++--- .../metric_expression.tsx | 19 +- .../metric_suggestions.test.ts | 40 ++-- .../metric_suggestions.ts | 1 + .../metric_visualization.test.ts | 31 ++- .../metric_visualization.tsx | 31 ++- .../metric_visualization_plugin/types.ts | 10 +- 15 files changed, 362 insertions(+), 136 deletions(-) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{indexpattern_suggestions.tsx => indexpattern_suggestions.ts} (62%) diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 7502346d42dec..59dcdda6cd3a4 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; import { HashRouter, Switch, Route, RouteComponentProps } from 'react-router-dom'; -import chrome, { Chrome } from 'ui/chrome'; +import chrome from 'ui/chrome'; import { localStorage } from 'ui/storage/storage_service'; import { QueryBar } from '../../../../../../src/legacy/core_plugins/data/public/query'; import { editorFrameSetup, editorFrameStop } from '../editor_frame_plugin'; @@ -38,9 +38,9 @@ export class AppPlugin { const store = new SavedObjectIndexStore(chrome!.getSavedObjectsClient()); editorFrame.registerDatasource('indexpattern', indexPattern); - editorFrame.registerVisualization(metricVisualization); editorFrame.registerVisualization(xyVisualization); editorFrame.registerVisualization(datatableVisualization); + editorFrame.registerVisualization(metricVisualization); this.instance = editorFrame.createInstance({}); 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 7d9ff8918ab29..01ba2f36cf1c7 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 @@ -1120,12 +1120,7 @@ describe('editor_frame', () => { // TODO why is this necessary? instance.update(); const suggestions = instance.find('[data-test-subj="suggestion-title"]'); - expect(suggestions.map(el => el.text())).toEqual([ - 'Suggestion1', - 'Suggestion2', - 'Suggestion3', - 'Suggestion4', - ]); + expect(suggestions.map(el => el.text())).toEqual(['Suggestion1', 'Suggestion3']); }); it('should switch to suggested visualization', async () => { 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 a1408d9851398..9d5317f05a4f9 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 @@ -85,7 +85,8 @@ describe('suggestion helpers', () => { 'vis1', {} ); - expect(suggestions).toHaveLength(3); + expect(suggestions).toHaveLength(2); + expect(suggestions.map(s => s.visualizationId)).toEqual(['vis2', 'vis1']); }); it('should rank the visualizations by score', () => { @@ -129,9 +130,9 @@ describe('suggestion helpers', () => { 'vis1', {} ); + expect(suggestions.length).toEqual(2); expect(suggestions[0].score).toBe(0.8); expect(suggestions[1].score).toBe(0.6); - expect(suggestions[2].score).toBe(0.2); }); it('should call all suggestion getters with all available data tables', () => { 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 f2d1db4eb636c..d8e7a676058e7 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 @@ -5,6 +5,7 @@ */ import { Ast } from '@kbn/interpreter/common'; +import _ from 'lodash'; import { Visualization, DatasourceSuggestion, TableSuggestion } from '../../types'; import { Action } from './state_management'; @@ -34,24 +35,28 @@ export function getSuggestions( ): Suggestion[] { const datasourceTables: TableSuggestion[] = datasourceTableSuggestions.map(({ table }) => table); - return Object.entries(visualizationMap) - .map(([visualizationId, visualization]) => { - return visualization - .getSuggestions({ - tables: datasourceTables, - state: visualizationId === activeVisualizationId ? visualizationState : undefined, - }) - .map(({ datasourceSuggestionId, ...suggestion }) => ({ - ...suggestion, - visualizationId, - datasourceState: datasourceTableSuggestions.find( - datasourceSuggestion => - datasourceSuggestion.table.datasourceSuggestionId === datasourceSuggestionId - )!.state, - })); - }) - .reduce((globalList, currentList) => [...globalList, ...currentList], []) - .sort(({ score: scoreA }, { score: scoreB }) => scoreB - scoreA); + const suggestions = Object.entries(visualizationMap).map(([visualizationId, visualization]) => { + return visualization + .getSuggestions({ + tables: datasourceTables, + state: visualizationId === activeVisualizationId ? visualizationState : undefined, + }) + .map(({ datasourceSuggestionId, ...suggestion }) => ({ + ...suggestion, + visualizationId, + datasourceState: datasourceTableSuggestions.find( + datasourceSuggestion => + datasourceSuggestion.table.datasourceSuggestionId === datasourceSuggestionId + )!.state, + })); + }); + + return _(suggestions) + .flatten() + .sortBy(({ score }) => score) + .reverse() + .uniq(({ visualizationId }) => visualizationId) + .value() as Suggestion[]; } export function toSwitchAction(suggestion: Suggestion): Action { 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..adda7217c97a2 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 @@ -960,7 +960,7 @@ describe('IndexPattern Data Source', () => { indexPatterns: expectedIndexPatterns, }, table: { - datasourceSuggestionId: 0, + datasourceSuggestionId: 3, isMultiRow: true, columns: [ { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts similarity index 62% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts index 01373967d072f..9030d66e51b18 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts @@ -13,10 +13,147 @@ import { IndexPatternLayer, IndexPatternPrivateState, IndexPattern, + IndexPatternColumn, } from './indexpattern'; import { buildColumn, getOperationTypesForField } from './operations'; import { hasField } from './utils'; +// A layer structure that makes suggestion generation a bit cleaner +interface Layer { + id: string; + columns: IndexPatternColumn[]; + indexPatternId: string; + columnMap: Record; +} + +// The options passed into the suggestion generation functions. +interface SuggestionContext { + indexPatterns: Record; + layers: Layer[]; + datasourceSuggestionId: number; +} + +function isMetric(column: IndexPatternColumn) { + return !column.isBucketed && column.dataType === 'number'; +} + +// Generate a single metric suggestion, if there isn't one already in the layers +function suggestMetric({ + indexPatterns, + layers, + datasourceSuggestionId, +}: SuggestionContext): DatasourceSuggestion | undefined { + // If we already have something like a metric, we don't need + // to suggest a new one. + if (layers.some(l => l.columns.length === 1 && isMetric(Object.values(l.columns)[0]))) { + return; + } + + const layer = layers.find(l => !!l.columns.some(isMetric)); + + if (!layer) { + return; + } + + const column = layer.columns.find(isMetric)!; + const columnId = 'metric'; + + return { + table: { + layerId: layer.id, + datasourceSuggestionId, + columns: [ + { + columnId, + operation: columnToOperation(column), + }, + ], + isMultiRow: false, + }, + state: { + currentIndexPatternId: layer.indexPatternId, + indexPatterns, + layers: { + [layer.id]: { + columnOrder: [columnId], + columns: { metric: column }, + indexPatternId: layer.indexPatternId, + }, + }, + }, + }; +} + +// Generate a histogram suggestion, if there isn't one already in the layers +function suggestHistogram({ + indexPatterns, + layers, + datasourceSuggestionId, +}: SuggestionContext): DatasourceSuggestion | undefined { + // If we already have something like a histogram, we don't need + // to suggest a new one. + if (layers.some(l => l.columns.length > 1 && l.columns.some(c => c.isBucketed))) { + return; + } + + const findDateField = (l: Layer) => + indexPatterns[l.indexPatternId].fields.find(f => f.aggregatable && f.type === 'date'); + + const layer = layers.find(l => l.columns.find(isMetric) && findDateField(l)); + + if (!layer) { + return; + } + + const column = layer.columns.find(isMetric)!; + const bucketableField = findDateField(layer); + + if (!column || !bucketableField) { + return; + } + + const bucketColumn = buildColumn({ + op: getOperationTypesForField(bucketableField)[0], + columns: layer.columnMap, + layerId: layer.id, + indexPattern: indexPatterns[layer.indexPatternId], + suggestedPriority: undefined, + field: bucketableField, + }); + + return { + table: { + datasourceSuggestionId, + columns: [ + { + columnId: 'histogram', + operation: columnToOperation(bucketColumn), + }, + { + columnId: 'metric', + operation: columnToOperation(column), + }, + ], + isMultiRow: true, + layerId: layer.id, + }, + state: { + indexPatterns, + currentIndexPatternId: layer.indexPatternId, + layers: { + [layer.id]: { + columnOrder: ['histogram', 'metric'], + columns: { + histogram: bucketColumn, + metric: column, + }, + indexPatternId: layer.indexPatternId, + }, + }, + }, + }; +} + function buildSuggestion({ state, updatedLayer, @@ -78,7 +215,7 @@ export function getDatasourceSuggestionsForField( } } -function getBucketOperation(field: IndexPatternField) { +function getBucketOperationTypes(field: IndexPatternField) { return getOperationTypesForField(field).find(op => op === 'date_histogram' || op === 'terms'); } @@ -90,7 +227,7 @@ function getExistingLayerSuggestionsForField( const layer = state.layers[layerId]; const indexPattern = state.indexPatterns[layer.indexPatternId]; const operations = getOperationTypesForField(field); - const usableAsBucketOperation = getBucketOperation(field); + const usableAsBucketOperation = getBucketOperationTypes(field); const fieldInUse = Object.values(layer.columns).some( column => hasField(column) && column.sourceField === field.name ); @@ -157,7 +294,7 @@ function addFieldAsBucketOperation( indexPattern: IndexPattern, field: IndexPatternField ) { - const applicableBucketOperation = getBucketOperation(field); + const applicableBucketOperation = getBucketOperationTypes(field); const newColumn = buildColumn({ op: applicableBucketOperation, columns: layer.columns, @@ -208,7 +345,7 @@ function getEmptyLayerSuggestionsForField( ) { const indexPattern = state.indexPatterns[indexPatternId]; let newLayer: IndexPatternLayer | undefined; - if (getBucketOperation(field)) { + if (getBucketOperationTypes(field)) { newLayer = createNewLayerWithBucketAggregation(layerId, indexPattern, field); } else if (indexPattern.timeFieldName && getOperationTypesForField(field).length > 0) { newLayer = createNewLayerWithMetricAggregation(layerId, indexPattern, field); @@ -240,7 +377,7 @@ function createNewLayerWithBucketAggregation( // let column know about count column const column = buildColumn({ layerId, - op: getBucketOperation(field), + op: getBucketOperationTypes(field), indexPattern, columns: { col2: countColumn, @@ -295,20 +432,46 @@ function createNewLayerWithMetricAggregation( }; } -export function getDatasourceSuggestionsFromCurrentState( - state: IndexPatternPrivateState -): Array> { - const layers = Object.entries(state.layers); - - return layers - .map(([layerId, layer], index) => { - if (layer.columnOrder.length === 0) { - return; - } - - return buildSuggestion({ state, layerId, isMultiRow: true, datasourceSuggestionId: index }); - }) - .reduce((prev, current) => (current ? prev.concat([current]) : prev), [] as Array< - DatasourceSuggestion - >); +export function getDatasourceSuggestionsFromCurrentState(state: IndexPatternPrivateState) { + const layers = Object.entries(state.layers).map(([id, layer]) => ({ + id, + indexPatternId: layer.indexPatternId, + columnMap: layer.columns, + columns: Object.values(layer.columns), + })); + const [firstLayer] = layers.filter(({ columns }) => columns.length > 0); + const suggestions: Array> = []; + + const histogramSuggestion = suggestHistogram({ + layers, + indexPatterns: state.indexPatterns, + datasourceSuggestionId: 1, + }); + + const metricSuggestion = suggestMetric({ + layers, + indexPatterns: state.indexPatterns, + datasourceSuggestionId: 2, + }); + + if (firstLayer) { + suggestions.push( + buildSuggestion({ + state, + layerId: firstLayer.id, + isMultiRow: true, + datasourceSuggestionId: 3, + }) + ); + } + + if (histogramSuggestion) { + suggestions.push(histogramSuggestion); + } + + if (metricSuggestion) { + suggestions.push(metricSuggestion); + } + + return suggestions; } diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx index a755cbc690e45..ff2e55ac83dcc 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; import { MetricConfigPanel } from './metric_config_panel'; -import { DatasourcePublicAPI, DatasourceDimensionPanelProps, Operation } from '../types'; +import { DatasourceDimensionPanelProps, Operation, DatasourcePublicAPI } from '../types'; import { State } from './types'; import { NativeRendererProps } from '../native_renderer'; -import { createMockDatasource } from '../editor_frame_plugin/mocks'; +import { createMockFramePublicAPI, createMockDatasource } from '../editor_frame_plugin/mocks'; describe('MetricConfigPanel', () => { const dragDropContext = { dragging: undefined, setDragging: jest.fn() }; @@ -23,6 +23,7 @@ describe('MetricConfigPanel', () => { function testState(): State { return { accessor: 'foo', + layerId: 'bar', }; } @@ -34,16 +35,15 @@ describe('MetricConfigPanel', () => { } test('the value dimension panel only accepts singular numeric operations', () => { - const datasource = { - ...mockDatasource(), - renderDimensionPanel: jest.fn(), - }; const state = testState(); const component = mount( ); @@ -53,7 +53,6 @@ describe('MetricConfigPanel', () => { const { columnId, filterOperations } = nativeProps; const exampleOperation: Operation = { dataType: 'number', - id: 'foo', isBucketed: false, label: 'bar', }; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx index aa60d79f4ebb1..335dbb27556d8 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx @@ -8,11 +8,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiForm, EuiFormRow } from '@elastic/eui'; import { State } from './types'; -import { VisualizationProps, Operation } from '../types'; +import { VisualizationProps } from '../types'; import { NativeRenderer } from '../native_renderer'; export function MetricConfigPanel(props: VisualizationProps) { - const { state, datasource } = props; + const { state, frame } = props; + const [datasource] = Object.values(frame.datasourceLayers); + const [layerId] = Object.keys(frame.datasourceLayers); return ( @@ -22,12 +24,13 @@ export function MetricConfigPanel(props: VisualizationProps) { })} > !op.isBucketed && op.dataType === 'number', + filterOperations: op => !op.isBucketed && op.dataType === 'number', }} /> diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index bd0f8ba80bb40..825b08d676bbe 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -5,21 +5,27 @@ */ import { metricChart, MetricChart } from './metric_expression'; -import { KibanaDatatable } from '../types'; +import { LensMultiTable } from '../types'; import React from 'react'; import { shallow } from 'enzyme'; import { MetricConfig } from './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 }], + const data: LensMultiTable = { + type: 'lens_multitable', + tables: { + l1: { + type: 'kibana_datatable', + columns: [{ id: 'a', name: 'a' }, { id: 'b', name: 'b' }, { id: 'c', name: 'c' }], + rows: [{ a: 10110, b: 2, c: 3 }], + }, + }, }; const args: MetricConfig = { - title: 'My fanci metric chart', accessor: 'a', + layerId: 'l1', + title: 'My fanci metric chart', }; return { data, args }; @@ -43,35 +49,35 @@ describe('metric_expression', () => { const { data, args } = sampleArgs(); expect(shallow()).toMatchInlineSnapshot(` - - - -
- 1 -
- - My fanci metric chart - -
-
-
-`); + + + +
+ 10110 +
+ + My fanci metric chart + +
+
+
+ `); }); }); }); diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index e7c33837d34a9..cc0f65efa9c0a 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -9,12 +9,12 @@ import ReactDOM from 'react-dom'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { MetricConfig } from './types'; -import { KibanaDatatable } from '../types'; +import { LensMultiTable } from '../types'; import { RenderFunction } from './plugin'; import { AutoScale } from './auto_scale'; export interface MetricChartProps { - data: KibanaDatatable; + data: LensMultiTable; args: MetricConfig; } @@ -26,7 +26,7 @@ export interface MetricRender { export const metricChart: ExpressionFunction< 'lens_metric_chart', - KibanaDatatable, + LensMultiTable, MetricConfig, MetricRender > = ({ @@ -44,9 +44,9 @@ export const metricChart: ExpressionFunction< }, }, context: { - types: ['kibana_datatable'], + types: ['lens_multitable'], }, - fn(data: KibanaDatatable, args: MetricChartProps) { + fn(data: LensMultiTable, args: MetricChartProps) { return { type: 'render', as: 'lens_metric_chart_renderer', @@ -59,16 +59,11 @@ export const metricChart: ExpressionFunction< // TODO the typings currently don't support custom type args. As soon as they do, this can be removed } as unknown) as ExpressionFunction< 'lens_metric_chart', - KibanaDatatable, + LensMultiTable, MetricConfig, MetricRender >; -export interface MetricChartProps { - data: KibanaDatatable; - args: MetricConfig; -} - export const metricChartRenderer: RenderFunction = { name: 'lens_metric_chart_renderer', displayName: 'Metric Chart', @@ -82,7 +77,7 @@ export const metricChartRenderer: RenderFunction = { export function MetricChart({ data, args }: MetricChartProps) { const { title, accessor } = args; - const row = data.rows[0]; + const [row] = Object.values(data.tables)[0].rows; // TODO: Use field formatters here... const value = Number(Number(row[accessor]).toFixed(3)).toString(); diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts index f964775c5ae5e..49b3e56c2d2ca 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts @@ -5,39 +5,36 @@ */ import { getSuggestions } from './metric_suggestions'; -import { TableColumn } from '../types'; +import { TableSuggestionColumn } from '..'; describe('metric_suggestions', () => { - function numCol(columnId: string): TableColumn { + function numCol(columnId: string): TableSuggestionColumn { return { columnId, operation: { dataType: 'number', - id: `avg_${columnId}`, label: `Avg ${columnId}`, isBucketed: false, }, }; } - function strCol(columnId: string): TableColumn { + function strCol(columnId: string): TableSuggestionColumn { return { columnId, operation: { dataType: 'string', - id: `terms_${columnId}`, label: `Top 5 ${columnId}`, isBucketed: true, }, }; } - function dateCol(columnId: string): TableColumn { + function dateCol(columnId: string): TableSuggestionColumn { return { columnId, operation: { dataType: 'date', - id: `date_histogram_${columnId}`, isBucketed: true, label: `${columnId} histogram`, }, @@ -54,11 +51,26 @@ describe('metric_suggestions', () => { expect( getSuggestions({ tables: [ - { datasourceSuggestionId: 0, isMultiRow: true, columns: [dateCol('a')] }, - { datasourceSuggestionId: 1, isMultiRow: true, columns: [strCol('foo'), strCol('bar')] }, - { datasourceSuggestionId: 2, isMultiRow: true, columns: [numCol('bar')] }, - { datasourceSuggestionId: 3, isMultiRow: true, columns: [unknownCol(), numCol('bar')] }, - { datasourceSuggestionId: 4, isMultiRow: false, columns: [numCol('bar'), numCol('baz')] }, + { columns: [dateCol('a')], datasourceSuggestionId: 0, isMultiRow: true, layerId: 'l1' }, + { + columns: [strCol('foo'), strCol('bar')], + datasourceSuggestionId: 1, + isMultiRow: true, + layerId: 'l1', + }, + { layerId: 'l1', datasourceSuggestionId: 2, isMultiRow: true, columns: [numCol('bar')] }, + { + columns: [unknownCol(), numCol('bar')], + datasourceSuggestionId: 3, + isMultiRow: true, + layerId: 'l1', + }, + { + columns: [numCol('bar'), numCol('baz')], + datasourceSuggestionId: 4, + isMultiRow: false, + layerId: 'l1', + }, ], }) ).toEqual([]); @@ -68,9 +80,10 @@ describe('metric_suggestions', () => { const [suggestion, ...rest] = getSuggestions({ tables: [ { + columns: [numCol('bytes')], datasourceSuggestionId: 0, isMultiRow: false, - columns: [numCol('bytes')], + layerId: 'l1', }, ], }); @@ -83,6 +96,7 @@ describe('metric_suggestions', () => { "score": 1, "state": Object { "accessor": "bytes", + "layerId": "l1", }, "title": "Avg bytes", } diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts index d99f0fbeaa114..136a1f07f3707 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts @@ -34,6 +34,7 @@ function getSuggestion(table: TableSuggestion): VisualizationSuggestion { datasourceSuggestionId: table.datasourceSuggestionId, previewIcon: 'visMetric', state: { + layerId: table.layerId, accessor: col.columnId, }, }; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts index 409eee06185bb..b6de912089c4b 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts @@ -6,37 +6,47 @@ import { metricVisualization } from './metric_visualization'; import { State } from './types'; -import { createMockDatasource } from '../editor_frame_plugin/mocks'; +import { createMockDatasource, createMockFramePublicAPI } from '../editor_frame_plugin/mocks'; import { generateId } from '../id_generator'; -import { DatasourcePublicAPI } from '../types'; +import { DatasourcePublicAPI, FramePublicAPI } from '../types'; jest.mock('../id_generator'); function exampleState(): State { return { accessor: 'a', + layerId: 'l1', + }; +} + +function mockFrame(): FramePublicAPI { + return { + ...createMockFramePublicAPI(), + addNewLayer: () => 'l42', + datasourceLayers: { + l1: createMockDatasource().publicAPIMock, + l42: createMockDatasource().publicAPIMock, + }, }; } describe('metric_visualization', () => { describe('#initialize', () => { it('loads default state', () => { - const mockDatasource = createMockDatasource(); (generateId as jest.Mock).mockReturnValueOnce('test-id1'); - const initialState = metricVisualization.initialize(mockDatasource.publicAPIMock); + const initialState = metricVisualization.initialize(mockFrame()); expect(initialState.accessor).toBeDefined(); expect(initialState).toMatchInlineSnapshot(` Object { "accessor": "test-id1", + "layerId": "l42", } `); }); it('loads from persisted state', () => { - expect( - metricVisualization.initialize(createMockDatasource().publicAPIMock, exampleState()) - ).toEqual(exampleState()); + expect(metricVisualization.initialize(mockFrame(), exampleState())).toEqual(exampleState()); }); }); @@ -60,7 +70,12 @@ describe('metric_visualization', () => { }, }; - expect(metricVisualization.toExpression(exampleState(), datasource)).toMatchInlineSnapshot(` + const frame = { + ...mockFrame(), + datasourceLayers: { l1: datasource }, + }; + + expect(metricVisualization.toExpression(exampleState(), frame)).toMatchInlineSnapshot(` Object { "chain": Array [ Object { diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx index d871d30080891..f178b2bd4fe5e 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { getSuggestions } from './metric_suggestions'; import { MetricConfigPanel } from './metric_config_panel'; import { Visualization } from '../types'; @@ -14,11 +15,33 @@ import { State, PersistableState } from './types'; import { generateId } from '../id_generator'; export const metricVisualization: Visualization = { + id: 'lnsMetric', + + visualizationTypes: [ + { + id: 'lnsMetric', + icon: 'visMetric', + label: i18n.translate('xpack.lens.metric.label', { + defaultMessage: 'Metric', + }), + }, + ], + + getDescription() { + return { + icon: 'visMetric', + label: i18n.translate('xpack.lens.metric.label', { + defaultMessage: 'Metric', + }), + }; + }, + getSuggestions, - initialize(_, state) { + initialize(frame, state) { return ( state || { + layerId: frame.addNewLayer(), accessor: generateId(), } ); @@ -34,8 +57,10 @@ export const metricVisualization: Visualization = { domElement ), - toExpression(state, datasource) { - const operation = datasource.getOperationForColumnId(state.accessor); + toExpression(state, frame) { + const [datasource] = Object.values(frame.datasourceLayers); + const operation = datasource && datasource.getOperationForColumnId(state.accessor); + return { type: 'expression', chain: [ diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts index cd02d36577262..89d41552639c4 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/types.ts @@ -4,9 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface MetricConfig { +export interface State { + layerId: string; accessor: string; } -export type State = MetricConfig; -export type PersistableState = MetricConfig; +export interface MetricConfig extends State { + title: string; +} + +export type PersistableState = State; From eda40df9f73500c21e7cd58e41cee8d8dc73cea9 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Fri, 9 Aug 2019 14:03:57 -0400 Subject: [PATCH 11/25] Back out suggestion changes, in lieu of Joe's work --- .../editor_frame/editor_frame.test.tsx | 7 +- .../editor_frame/suggestion_helpers.test.ts | 6 +- .../editor_frame/suggestion_helpers.ts | 9 +- ...xpattern.test.tsx => indexpattern.test.ts} | 2 +- .../indexpattern_suggestions.ts | 205 ++---------------- 5 files changed, 34 insertions(+), 195 deletions(-) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{indexpattern.test.tsx => indexpattern.test.ts} (99%) 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 01ba2f36cf1c7..7d9ff8918ab29 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 @@ -1120,7 +1120,12 @@ describe('editor_frame', () => { // TODO why is this necessary? instance.update(); const suggestions = instance.find('[data-test-subj="suggestion-title"]'); - expect(suggestions.map(el => el.text())).toEqual(['Suggestion1', 'Suggestion3']); + expect(suggestions.map(el => el.text())).toEqual([ + 'Suggestion1', + 'Suggestion2', + 'Suggestion3', + 'Suggestion4', + ]); }); it('should switch to suggested visualization', async () => { 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 9d5317f05a4f9..9d509404eef2c 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 @@ -85,8 +85,7 @@ describe('suggestion helpers', () => { 'vis1', {} ); - expect(suggestions).toHaveLength(2); - expect(suggestions.map(s => s.visualizationId)).toEqual(['vis2', 'vis1']); + expect(suggestions).toHaveLength(3); }); it('should rank the visualizations by score', () => { @@ -130,9 +129,10 @@ describe('suggestion helpers', () => { 'vis1', {} ); - expect(suggestions.length).toEqual(2); + expect(suggestions.length).toEqual(3); expect(suggestions[0].score).toBe(0.8); expect(suggestions[1].score).toBe(0.6); + expect(suggestions[2].score).toBe(0.2); }); it('should call all suggestion getters with all available data tables', () => { 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 d8e7a676058e7..c58bf4dc59e7e 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 @@ -51,12 +51,9 @@ export function getSuggestions( })); }); - return _(suggestions) - .flatten() - .sortBy(({ score }) => score) - .reverse() - .uniq(({ visualizationId }) => visualizationId) - .value() as Suggestion[]; + return _.flatten(suggestions).sort((a, b) => { + return b.score - a.score; + }); } export function toSwitchAction(suggestion: Suggestion): Action { 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.ts similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts index adda7217c97a2..b7d761b42030a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts @@ -960,7 +960,7 @@ describe('IndexPattern Data Source', () => { indexPatterns: expectedIndexPatterns, }, table: { - datasourceSuggestionId: 3, + datasourceSuggestionId: 0, isMultiRow: true, columns: [ { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts index 9030d66e51b18..01373967d072f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts @@ -13,147 +13,10 @@ import { IndexPatternLayer, IndexPatternPrivateState, IndexPattern, - IndexPatternColumn, } from './indexpattern'; import { buildColumn, getOperationTypesForField } from './operations'; import { hasField } from './utils'; -// A layer structure that makes suggestion generation a bit cleaner -interface Layer { - id: string; - columns: IndexPatternColumn[]; - indexPatternId: string; - columnMap: Record; -} - -// The options passed into the suggestion generation functions. -interface SuggestionContext { - indexPatterns: Record; - layers: Layer[]; - datasourceSuggestionId: number; -} - -function isMetric(column: IndexPatternColumn) { - return !column.isBucketed && column.dataType === 'number'; -} - -// Generate a single metric suggestion, if there isn't one already in the layers -function suggestMetric({ - indexPatterns, - layers, - datasourceSuggestionId, -}: SuggestionContext): DatasourceSuggestion | undefined { - // If we already have something like a metric, we don't need - // to suggest a new one. - if (layers.some(l => l.columns.length === 1 && isMetric(Object.values(l.columns)[0]))) { - return; - } - - const layer = layers.find(l => !!l.columns.some(isMetric)); - - if (!layer) { - return; - } - - const column = layer.columns.find(isMetric)!; - const columnId = 'metric'; - - return { - table: { - layerId: layer.id, - datasourceSuggestionId, - columns: [ - { - columnId, - operation: columnToOperation(column), - }, - ], - isMultiRow: false, - }, - state: { - currentIndexPatternId: layer.indexPatternId, - indexPatterns, - layers: { - [layer.id]: { - columnOrder: [columnId], - columns: { metric: column }, - indexPatternId: layer.indexPatternId, - }, - }, - }, - }; -} - -// Generate a histogram suggestion, if there isn't one already in the layers -function suggestHistogram({ - indexPatterns, - layers, - datasourceSuggestionId, -}: SuggestionContext): DatasourceSuggestion | undefined { - // If we already have something like a histogram, we don't need - // to suggest a new one. - if (layers.some(l => l.columns.length > 1 && l.columns.some(c => c.isBucketed))) { - return; - } - - const findDateField = (l: Layer) => - indexPatterns[l.indexPatternId].fields.find(f => f.aggregatable && f.type === 'date'); - - const layer = layers.find(l => l.columns.find(isMetric) && findDateField(l)); - - if (!layer) { - return; - } - - const column = layer.columns.find(isMetric)!; - const bucketableField = findDateField(layer); - - if (!column || !bucketableField) { - return; - } - - const bucketColumn = buildColumn({ - op: getOperationTypesForField(bucketableField)[0], - columns: layer.columnMap, - layerId: layer.id, - indexPattern: indexPatterns[layer.indexPatternId], - suggestedPriority: undefined, - field: bucketableField, - }); - - return { - table: { - datasourceSuggestionId, - columns: [ - { - columnId: 'histogram', - operation: columnToOperation(bucketColumn), - }, - { - columnId: 'metric', - operation: columnToOperation(column), - }, - ], - isMultiRow: true, - layerId: layer.id, - }, - state: { - indexPatterns, - currentIndexPatternId: layer.indexPatternId, - layers: { - [layer.id]: { - columnOrder: ['histogram', 'metric'], - columns: { - histogram: bucketColumn, - metric: column, - }, - indexPatternId: layer.indexPatternId, - }, - }, - }, - }; -} - function buildSuggestion({ state, updatedLayer, @@ -215,7 +78,7 @@ export function getDatasourceSuggestionsForField( } } -function getBucketOperationTypes(field: IndexPatternField) { +function getBucketOperation(field: IndexPatternField) { return getOperationTypesForField(field).find(op => op === 'date_histogram' || op === 'terms'); } @@ -227,7 +90,7 @@ function getExistingLayerSuggestionsForField( const layer = state.layers[layerId]; const indexPattern = state.indexPatterns[layer.indexPatternId]; const operations = getOperationTypesForField(field); - const usableAsBucketOperation = getBucketOperationTypes(field); + const usableAsBucketOperation = getBucketOperation(field); const fieldInUse = Object.values(layer.columns).some( column => hasField(column) && column.sourceField === field.name ); @@ -294,7 +157,7 @@ function addFieldAsBucketOperation( indexPattern: IndexPattern, field: IndexPatternField ) { - const applicableBucketOperation = getBucketOperationTypes(field); + const applicableBucketOperation = getBucketOperation(field); const newColumn = buildColumn({ op: applicableBucketOperation, columns: layer.columns, @@ -345,7 +208,7 @@ function getEmptyLayerSuggestionsForField( ) { const indexPattern = state.indexPatterns[indexPatternId]; let newLayer: IndexPatternLayer | undefined; - if (getBucketOperationTypes(field)) { + if (getBucketOperation(field)) { newLayer = createNewLayerWithBucketAggregation(layerId, indexPattern, field); } else if (indexPattern.timeFieldName && getOperationTypesForField(field).length > 0) { newLayer = createNewLayerWithMetricAggregation(layerId, indexPattern, field); @@ -377,7 +240,7 @@ function createNewLayerWithBucketAggregation( // let column know about count column const column = buildColumn({ layerId, - op: getBucketOperationTypes(field), + op: getBucketOperation(field), indexPattern, columns: { col2: countColumn, @@ -432,46 +295,20 @@ function createNewLayerWithMetricAggregation( }; } -export function getDatasourceSuggestionsFromCurrentState(state: IndexPatternPrivateState) { - const layers = Object.entries(state.layers).map(([id, layer]) => ({ - id, - indexPatternId: layer.indexPatternId, - columnMap: layer.columns, - columns: Object.values(layer.columns), - })); - const [firstLayer] = layers.filter(({ columns }) => columns.length > 0); - const suggestions: Array> = []; - - const histogramSuggestion = suggestHistogram({ - layers, - indexPatterns: state.indexPatterns, - datasourceSuggestionId: 1, - }); - - const metricSuggestion = suggestMetric({ - layers, - indexPatterns: state.indexPatterns, - datasourceSuggestionId: 2, - }); - - if (firstLayer) { - suggestions.push( - buildSuggestion({ - state, - layerId: firstLayer.id, - isMultiRow: true, - datasourceSuggestionId: 3, - }) - ); - } - - if (histogramSuggestion) { - suggestions.push(histogramSuggestion); - } - - if (metricSuggestion) { - suggestions.push(metricSuggestion); - } - - return suggestions; +export function getDatasourceSuggestionsFromCurrentState( + state: IndexPatternPrivateState +): Array> { + const layers = Object.entries(state.layers); + + return layers + .map(([layerId, layer], index) => { + if (layer.columnOrder.length === 0) { + return; + } + + return buildSuggestion({ state, layerId, isMultiRow: true, datasourceSuggestionId: index }); + }) + .reduce((prev, current) => (current ? prev.concat([current]) : prev), [] as Array< + DatasourceSuggestion + >); } From 085210c1741dd87d20d66bc09913c096a07a47a7 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Tue, 13 Aug 2019 14:44:43 -0400 Subject: [PATCH 12/25] Fix metric autoscale logic --- .../lens/public/metric_visualization_plugin/auto_scale.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx index fa7a04c2b3a51..4f813093db419 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx @@ -59,6 +59,7 @@ export class AutoScale extends React.Component { display: 'flex', justifyContent: 'center', position: 'relative', + maxWidth: '100%', }} >
Date: Tue, 13 Aug 2019 15:08:17 -0400 Subject: [PATCH 13/25] Register metric as an embeddable --- x-pack/legacy/plugins/lens/public/register_embeddable.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/legacy/plugins/lens/public/register_embeddable.ts b/x-pack/legacy/plugins/lens/public/register_embeddable.ts index 0364b1a4eb65b..e488f8e3d9aa3 100644 --- a/x-pack/legacy/plugins/lens/public/register_embeddable.ts +++ b/x-pack/legacy/plugins/lens/public/register_embeddable.ts @@ -8,10 +8,12 @@ import { indexPatternDatasourceSetup } from './indexpattern_plugin'; import { xyVisualizationSetup } from './xy_visualization_plugin'; import { editorFrameSetup } from './editor_frame_plugin'; import { datatableVisualizationSetup } from './datatable_visualization_plugin'; +import { metricVisualizationSetup } from './metric_visualization_plugin'; // bootstrap shimmed plugins to register everything necessary (expression functions and embeddables). // the new platform will take care of this once in place. indexPatternDatasourceSetup(); datatableVisualizationSetup(); xyVisualizationSetup(); +metricVisualizationSetup(); editorFrameSetup(); From 8436275546090c8e86ce3f19bf23757338d6e138 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Tue, 13 Aug 2019 15:09:02 -0400 Subject: [PATCH 14/25] Fix metric autoscale flicker --- .../auto_scale.test.tsx | 32 +++++++++---------- .../auto_scale.tsx | 23 +++++++------ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx index c373203056ae1..f5ad9249f600b 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx @@ -26,8 +26,8 @@ describe('AutoScale', () => { }); it('is the lesser of the x or y scale', () => { - expect(computeScale(mockElement(1000, 2000), mockElement(3000, 8000))).toBe(0.25); - expect(computeScale(mockElement(2000, 3000), mockElement(4000, 3200))).toBe(0.5); + expect(computeScale(mockElement(1000, 2000), mockElement(3000, 8000))).toBe(0.246); + expect(computeScale(mockElement(2000, 3000), mockElement(4000, 3200))).toBe(0.496); }); }); @@ -40,20 +40,20 @@ describe('AutoScale', () => { ) ).toMatchInlineSnapshot(` -
-
-

- Hoi! -

-
-
-`); +
+
+

+ Hoi! +

+
+
+ `); }); }); }); diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx index 4f813093db419..38b1f42dbd29f 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx @@ -21,22 +21,25 @@ export class AutoScale extends React.Component { constructor(props: Props) { super(props); - this.state = { scale: 1 }; + // An initial scale of 0 means we always redraw + // at least once, which is sub-optimal, but it + // prevents an annoying flicker. + this.state = { scale: 0 }; } - setParent(el: Element | null) { + setParent = (el: Element | null) => { if (this.parent !== el) { this.parent = el; setTimeout(() => this.scale()); } - } + }; - setChild(el: Element | null) { + setChild = (el: Element | null) => { if (this.child !== el) { this.child = el; setTimeout(() => this.scale()); } - } + }; scale() { const scale = computeScale(this.parent, this.child); @@ -54,7 +57,7 @@ export class AutoScale extends React.Component { return (
this.setParent(el)} + ref={this.setParent} style={{ display: 'flex', justifyContent: 'center', @@ -64,7 +67,7 @@ export class AutoScale extends React.Component { >
this.setChild(el)} + ref={this.setChild} style={{ transform: `scale(${scale})`, position: 'relative', @@ -94,8 +97,10 @@ export function computeScale( return 1; } - const scaleX = parent.clientWidth / child.clientWidth; - const scaleY = parent.clientHeight / child.clientHeight; + const marginSize = 16; + const labelSize = 16; + const scaleX = (parent.clientWidth - marginSize) / child.clientWidth; + const scaleY = (parent.clientHeight - marginSize - labelSize) / child.clientHeight; return Math.min(1, Math.min(scaleX, scaleY)); } From 4cbf3c84eb142acb2bcc3b30c89b0b29d395e1cd Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Tue, 13 Aug 2019 15:09:34 -0400 Subject: [PATCH 15/25] Render mini metric in suggestions panel --- .../metric_suggestions.test.ts | 17 +++++++++++++++++ .../metric_suggestions.ts | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts index 49b3e56c2d2ca..120d0369bc7ea 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts @@ -92,6 +92,23 @@ describe('metric_suggestions', () => { expect(suggestion).toMatchInlineSnapshot(` Object { "datasourceSuggestionId": 0, + "previewExpression": Object { + "chain": Array [ + Object { + "arguments": Object { + "accessor": Array [ + "bytes", + ], + "title": Array [ + "", + ], + }, + "function": "lens_metric_chart", + "type": "function", + }, + ], + "type": "expression", + }, "previewIcon": "visMetric", "score": 1, "state": Object { diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts index 136a1f07f3707..85981be00c3a0 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts @@ -33,6 +33,19 @@ function getSuggestion(table: TableSuggestion): VisualizationSuggestion { score: 1, datasourceSuggestionId: table.datasourceSuggestionId, previewIcon: 'visMetric', + previewExpression: { + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_metric_chart', + arguments: { + title: [''], + accessor: [col.columnId], + }, + }, + ], + }, state: { layerId: table.layerId, accessor: col.columnId, From 5065a146999948febf47aa551c6fa5fe4ffd40fd Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Tue, 13 Aug 2019 15:09:50 -0400 Subject: [PATCH 16/25] Cache the metric filterOperations function --- .../metric_visualization_plugin/metric_config_panel.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx index 335dbb27556d8..b80235e69ba05 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx @@ -8,9 +8,11 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiForm, EuiFormRow } from '@elastic/eui'; import { State } from './types'; -import { VisualizationProps } from '../types'; +import { VisualizationProps, OperationMetadata } from '../types'; import { NativeRenderer } from '../native_renderer'; +const isMetric = (op: OperationMetadata) => !op.isBucketed && op.dataType === 'number'; + export function MetricConfigPanel(props: VisualizationProps) { const { state, frame } = props; const [datasource] = Object.values(frame.datasourceLayers); @@ -30,7 +32,7 @@ export function MetricConfigPanel(props: VisualizationProps) { layerId, columnId: state.accessor, dragDropContext: props.dragDropContext, - filterOperations: op => !op.isBucketed && op.dataType === 'number', + filterOperations: isMetric, }} /> From b11021b2b4d4a8934de3330cc9f4351d4842010d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 14 Aug 2019 12:53:38 +0200 Subject: [PATCH 17/25] fix auto scaling edge cases --- .../public/metric_visualization_plugin/auto_scale.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx index 38b1f42dbd29f..a7267e6be1ede 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx @@ -61,8 +61,10 @@ export class AutoScale extends React.Component { style={{ display: 'flex', justifyContent: 'center', + alignItems: 'center', position: 'relative', maxWidth: '100%', + maxHeight: '100%', }} >
{ ref={this.setChild} style={{ transform: `scale(${scale})`, - position: 'relative', }} > {children} @@ -97,10 +98,8 @@ export function computeScale( return 1; } - const marginSize = 16; - const labelSize = 16; - const scaleX = (parent.clientWidth - marginSize) / child.clientWidth; - const scaleY = (parent.clientHeight - marginSize - labelSize) / child.clientHeight; + const scaleX = (parent.clientWidth) / child.clientWidth; + const scaleY = (parent.clientHeight) / child.clientHeight; return Math.min(1, Math.min(scaleX, scaleY)); } From e46f93591177e15b30a73d2ce09a51261f652d3b Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Wed, 14 Aug 2019 16:57:55 -0400 Subject: [PATCH 18/25] Modify auto-scale to handle resize events --- .../auto_scale.test.tsx | 10 +-- .../auto_scale.tsx | 82 +++++++++++-------- .../metric_expression.test.tsx | 54 ++++++------ .../metric_expression.tsx | 21 +++-- 4 files changed, 92 insertions(+), 75 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx index f5ad9249f600b..9be06ab6fd297 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx @@ -26,8 +26,8 @@ describe('AutoScale', () => { }); it('is the lesser of the x or y scale', () => { - expect(computeScale(mockElement(1000, 2000), mockElement(3000, 8000))).toBe(0.246); - expect(computeScale(mockElement(2000, 3000), mockElement(4000, 3200))).toBe(0.496); + expect(computeScale(mockElement(1000, 2000), mockElement(3000, 8000))).toBe(0.25); + expect(computeScale(mockElement(2000, 3000), mockElement(4000, 3200))).toBe(0.5); }); }); @@ -41,12 +41,10 @@ describe('AutoScale', () => { ) ).toMatchInlineSnapshot(`

Hoi! diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx index a7267e6be1ede..b19e6800aa414 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx @@ -5,8 +5,10 @@ */ import React from 'react'; +import _ from 'lodash'; +import { EuiResizeObserver } from '@elastic/eui'; -interface Props { +interface Props extends React.HTMLAttributes { children: React.ReactNode | React.ReactNode[]; } @@ -17,10 +19,20 @@ interface State { export class AutoScale extends React.Component { private child: Element | null = null; private parent: Element | null = null; + private scale: () => void; constructor(props: Props) { super(props); + this.scale = _.throttle(() => { + const scale = computeScale(this.parent, this.child); + + // Prevent an infinite render loop + if (this.state.scale !== scale) { + this.setState({ scale }); + } + }); + // An initial scale of 0 means we always redraw // at least once, which is sub-optimal, but it // prevents an annoying flicker. @@ -41,42 +53,44 @@ export class AutoScale extends React.Component { } }; - scale() { - const scale = computeScale(this.parent, this.child); - - // Prevent an infinite render loop - if (this.state.scale !== scale) { - this.setState({ scale }); - } - } - render() { const { children } = this.props; const { scale } = this.state; + const style = this.props.style || {}; return ( -
-
- {children} -
-
+ + {resizeRef => ( +
{ + this.setParent(el); + resizeRef(el); + }} + style={{ + ...style, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + maxWidth: '100%', + maxHeight: '100%', + }} + > +
+ {children} +
+
+ )} +
); } } @@ -98,8 +112,8 @@ export function computeScale( return 1; } - const scaleX = (parent.clientWidth) / child.clientWidth; - const scaleY = (parent.clientHeight) / child.clientHeight; + const scaleX = parent.clientWidth / child.clientWidth; + const scaleY = parent.clientHeight / child.clientHeight; return Math.min(1, Math.min(scaleX, scaleY)); } diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index 825b08d676bbe..81f8a0fbce210 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -49,34 +49,34 @@ describe('metric_expression', () => { const { data, args } = sampleArgs(); expect(shallow()).toMatchInlineSnapshot(` - - + - -
- 10110 -
- - My fanci metric chart - -
-
-
+ 10110 + + + My fanci metric chart + +

`); }); }); diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index cc0f65efa9c0a..e14c6749fff55 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -82,13 +82,18 @@ export function MetricChart({ data, args }: MetricChartProps) { const value = Number(Number(row[accessor]).toFixed(3)).toString(); return ( - - - -
{value}
- {title} -
-
-
+
+ {value} + {title} +
); } From 43ffe2386abf43717a8558a4e08d0ad3f0dfcde7 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 15 Aug 2019 14:46:14 +0200 Subject: [PATCH 19/25] use format hints in metric vis --- .../metric_expression.test.tsx | 3 +- .../metric_expression.tsx | 31 ++++++++++++++----- .../metric_visualization_plugin/plugin.tsx | 20 ++++++++++-- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index 81f8a0fbce210..b8c646be91e52 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -48,7 +48,8 @@ describe('metric_expression', () => { test('it renders the title and value', () => { const { data, args } = sampleArgs(); - expect(shallow()).toMatchInlineSnapshot(` + expect(shallow( x} />)) + .toMatchInlineSnapshot(`
; -export const metricChartRenderer: RenderFunction = { +export const getMetricChartRenderer = ( + formatFactory: FormatFactory +): RenderFunction => ({ name: 'lens_metric_chart_renderer', displayName: 'Metric Chart', help: 'Metric Chart Renderer', validate: () => {}, reuseDomNode: true, render: async (domNode: Element, config: MetricChartProps, _handlers: unknown) => { - ReactDOM.render(, domNode); + ReactDOM.render(, domNode); }, -}; +}); -export function MetricChart({ data, args }: MetricChartProps) { +export function MetricChart({ + data, + args, + formatFactory, +}: MetricChartProps & { formatFactory: FormatFactory }) { const { title, accessor } = args; - const [row] = Object.values(data.tables)[0].rows; - // TODO: Use field formatters here... - const value = Number(Number(row[accessor]).toFixed(3)).toString(); + let value = '-'; + const firstTable = Object.values(data.tables)[0]; + + if (firstTable) { + const column = firstTable.columns[0]; + const row = firstTable.rows[0]; + if (row[accessor]) { + value = + column && column.formatHint + ? formatFactory(column.formatHint).convert(row[accessor]) + : Number(Number(row[accessor]).toFixed(3)).toString(); + } + } return (
metricChart); - interpreter.renderersRegistry.register(() => metricChartRenderer as RenderFunction); + interpreter.renderersRegistry.register( + () => getMetricChartRenderer(fieldFormat.formatFactory) as RenderFunction + ); return metricVisualization; } @@ -65,6 +76,9 @@ export const metricVisualizationSetup = () => renderersRegistry, functionsRegistry, }, + fieldFormat: { + formatFactory: getFormat, + }, }); export const metricVisualizationStop = () => plugin.stop(); From 7e1c55066768a28151fcdf74089334ec8aa34f7a Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Fri, 16 Aug 2019 14:25:41 -0400 Subject: [PATCH 20/25] Tweak metric to only scale down so far, and scale both the number and the label. --- .../public/metric_visualization_plugin/auto_scale.tsx | 10 +++++----- .../metric_visualization_plugin/metric_expression.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx index b19e6800aa414..365e88d440f2a 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx @@ -74,16 +74,13 @@ export class AutoScale extends React.Component { alignItems: 'center', maxWidth: '100%', maxHeight: '100%', + overflow: 'hidden', }} >
{children} @@ -108,6 +105,9 @@ export function computeScale( parent: ClientDimensionable | null, child: ClientDimensionable | null ) { + const MAX_SCALE = 1; + const MIN_SCALE = 0.3; + if (!parent || !child) { return 1; } @@ -115,5 +115,5 @@ export function computeScale( const scaleX = parent.clientWidth / child.clientWidth; const scaleY = parent.clientHeight / child.clientHeight; - return Math.min(1, Math.min(scaleX, scaleY)); + return Math.max(Math.min(MAX_SCALE, Math.min(scaleX, scaleY)), MIN_SCALE); } diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index e14c6749fff55..6a677042172ef 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -90,10 +90,13 @@ export function MetricChart({ data, args }: MetricChartProps) { alignItems: 'center', maxWidth: '100%', maxHeight: '100%', + textAlign: 'center', }} > - {value} - {title} + +
{value}
+
{title}
+
); } From 859aa995669ca67da9a5b4634fa99a374fd357a0 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Fri, 16 Aug 2019 14:30:21 -0400 Subject: [PATCH 21/25] Fix lens metric tests --- .../auto_scale.test.tsx | 10 ++++-- .../metric_expression.test.tsx | 33 +++++++++++-------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx index 9be06ab6fd297..60008d1237d82 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.test.tsx @@ -25,8 +25,12 @@ describe('AutoScale', () => { expect(computeScale(mockElement(2000, 2000), mockElement(1000, 1000))).toBe(1); }); + it('is never under 0.3', () => { + expect(computeScale(mockElement(2000, 1000), mockElement(1000, 10000))).toBe(0.3); + }); + it('is the lesser of the x or y scale', () => { - expect(computeScale(mockElement(1000, 2000), mockElement(3000, 8000))).toBe(0.25); + expect(computeScale(mockElement(2000, 2000), mockElement(3000, 5000))).toBe(0.4); expect(computeScale(mockElement(2000, 3000), mockElement(4000, 3200))).toBe(0.5); }); }); @@ -41,10 +45,10 @@ describe('AutoScale', () => { ) ).toMatchInlineSnapshot(`

Hoi! diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index 81f8a0fbce210..f8ce2a61e9bdf 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -58,24 +58,31 @@ describe('metric_expression', () => { "justifyContent": "center", "maxHeight": "100%", "maxWidth": "100%", + "textAlign": "center", } } > - +
- 10110 + > + 10110 +
+
+ My fanci metric chart +
- - My fanci metric chart -

`); }); From 837c76ce7aeb2898227de8b2ca8af71ac1bace82 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 19 Aug 2019 05:47:29 +0200 Subject: [PATCH 22/25] remove unused imports --- x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx | 2 +- .../public/metric_visualization_plugin/metric_expression.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index d1fd22809fc87..07bd55cbd4e93 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; import { HashRouter, Switch, Route, RouteComponentProps } from 'react-router-dom'; -import chrome, { Chrome } from 'ui/chrome'; +import chrome from 'ui/chrome'; import { Storage } from 'ui/storage'; import { editorFrameSetup, editorFrameStop } from '../editor_frame_plugin'; import { indexPatternDatasourceSetup, indexPatternDatasourceStop } from '../indexpattern_plugin'; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index 238bcd86d87fc..daff873feb18c 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -7,7 +7,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { FormatFactory } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { MetricConfig } from './types'; import { LensMultiTable } from '../types'; From 96049f9a2aaea5f25b6e4581e61428922b1aa1d1 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 19 Aug 2019 11:05:20 -0400 Subject: [PATCH 23/25] Fix metric infinite loop due to null ref --- .../lens/public/metric_visualization_plugin/auto_scale.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx index 365e88d440f2a..9ca58c1944803 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/auto_scale.tsx @@ -40,14 +40,14 @@ export class AutoScale extends React.Component { } setParent = (el: Element | null) => { - if (this.parent !== el) { + if (el && this.parent !== el) { this.parent = el; setTimeout(() => this.scale()); } }; setChild = (el: Element | null) => { - if (this.child !== el) { + if (el && this.child !== el) { this.child = el; setTimeout(() => this.scale()); } From d8e68c5089877c91170af7bb16cb12fd289f7e71 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 19 Aug 2019 11:12:11 -0400 Subject: [PATCH 24/25] Allow changing of datasource in metric vis --- .../metric_config_panel.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx index b80235e69ba05..36025a5fa19a3 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiForm, EuiFormRow } from '@elastic/eui'; +import { EuiForm, EuiFormRow, EuiPanel, EuiSpacer } from '@elastic/eui'; import { State } from './types'; import { VisualizationProps, OperationMetadata } from '../types'; import { NativeRenderer } from '../native_renderer'; @@ -19,8 +19,13 @@ export function MetricConfigPanel(props: VisualizationProps) { const [layerId] = Object.keys(frame.datasourceLayers); return ( - + + + + + ) { }} /> - + ); } From b305b1df8b8e2588bdf69a9ff7d0d159fc80320b Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 19 Aug 2019 12:24:13 -0400 Subject: [PATCH 25/25] Add isMetric as a column descriptor so that we can switch from metric to table and back. --- .../plugins/lens/public/app_plugin/plugin.tsx | 2 +- .../visualization.test.tsx | 1 + .../editor_frame/chart_switch.test.tsx | 4 +++ .../indexpattern_plugin/datapanel.test.tsx | 4 +++ .../dimension_panel/dimension_panel.test.tsx | 10 +++++++ .../indexpattern_plugin/indexpattern.test.ts | 5 ++++ .../indexpattern_plugin/indexpattern.tsx | 3 +- .../indexpattern_suggestions.test.tsx | 13 +++++++++ .../indexpattern_suggestions.ts | 15 ++++++---- .../indexpattern_plugin/layerpanel.test.tsx | 2 ++ .../operation_definitions/count.tsx | 2 ++ .../date_histogram.test.tsx | 4 +++ .../operation_definitions/date_histogram.tsx | 2 ++ .../filter_ratio.test.tsx | 1 + .../operation_definitions/filter_ratio.tsx | 2 ++ .../operation_definitions/metrics.tsx | 2 ++ .../operation_definitions/terms.test.tsx | 11 +++++++ .../operation_definitions/terms.tsx | 2 ++ .../indexpattern_plugin/operations.test.ts | 4 +++ .../indexpattern_plugin/state_helpers.test.ts | 29 +++++++++++++++++++ .../metric_config_panel.test.tsx | 12 ++++++-- .../metric_config_panel.tsx | 4 +-- .../metric_expression.tsx | 1 - .../metric_suggestions.test.ts | 3 ++ .../metric_visualization.test.ts | 1 + .../multi_column_editor.test.tsx | 1 + x-pack/legacy/plugins/lens/public/types.ts | 1 + .../xy_config_panel.test.tsx | 2 ++ .../xy_config_panel.tsx | 2 +- .../xy_suggestions.test.ts | 4 +++ 30 files changed, 134 insertions(+), 15 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index d1fd22809fc87..07bd55cbd4e93 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; import { HashRouter, Switch, Route, RouteComponentProps } from 'react-router-dom'; -import chrome, { Chrome } from 'ui/chrome'; +import chrome from 'ui/chrome'; import { Storage } from 'ui/storage'; import { editorFrameSetup, editorFrameStop } from '../editor_frame_plugin'; import { indexPatternDatasourceSetup, indexPatternDatasourceStop } from '../indexpattern_plugin'; diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx index 177dfc9577028..39fcce7a2a90c 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx @@ -97,6 +97,7 @@ describe('Datatable Visualization', () => { const baseOperation: Operation = { dataType: 'string', isBucketed: true, + isMetric: false, label: '', }; expect(filterOperations({ ...baseOperation })).toEqual(true); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.test.tsx index a3d9f02c9def3..4413d3b0c474b 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.test.tsx @@ -205,6 +205,7 @@ describe('chart_switch', () => { label: '', dataType: 'string', isBucketed: true, + isMetric: false, }, }, { @@ -213,6 +214,7 @@ describe('chart_switch', () => { label: '', dataType: 'number', isBucketed: false, + isMetric: true, }, }, ], @@ -433,6 +435,7 @@ describe('chart_switch', () => { label: '', dataType: 'string', isBucketed: true, + isMetric: false, }, }, { @@ -441,6 +444,7 @@ describe('chart_switch', () => { label: '', dataType: 'number', isBucketed: false, + isMetric: true, }, }, ], diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx index dfd4adde48560..7f74fd8c1f0b8 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx @@ -27,6 +27,7 @@ const initialState: IndexPatternPrivateState = { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, operationType: 'terms', sourceField: 'source', params: { @@ -41,6 +42,7 @@ const initialState: IndexPatternPrivateState = { label: 'My Op', dataType: 'number', isBucketed: false, + isMetric: true, operationType: 'avg', sourceField: 'memory', }, @@ -54,6 +56,7 @@ const initialState: IndexPatternPrivateState = { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, operationType: 'terms', sourceField: 'source', params: { @@ -68,6 +71,7 @@ const initialState: IndexPatternPrivateState = { label: 'My Op', dataType: 'number', isBucketed: false, + isMetric: true, operationType: 'avg', sourceField: 'bytes', }, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx index 2ddfce6b7e0a5..f45b674e0c19b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx @@ -89,6 +89,7 @@ describe('IndexPatternDimensionPanel', () => { label: 'Date Histogram of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -202,6 +203,7 @@ describe('IndexPatternDimensionPanel', () => { label: 'Max of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'max', @@ -243,6 +245,7 @@ describe('IndexPatternDimensionPanel', () => { label: 'Max of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'max', @@ -284,6 +287,7 @@ describe('IndexPatternDimensionPanel', () => { label: 'Max of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'max', @@ -368,6 +372,7 @@ describe('IndexPatternDimensionPanel', () => { label: 'Max of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'max', @@ -541,6 +546,7 @@ describe('IndexPatternDimensionPanel', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, label: '', operationType: 'avg', sourceField: 'bytes', @@ -578,6 +584,7 @@ describe('IndexPatternDimensionPanel', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, label: '', operationType: 'count', }, @@ -772,6 +779,7 @@ describe('IndexPatternDimensionPanel', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, label: '', operationType: 'count', }, @@ -854,6 +862,7 @@ describe('IndexPatternDimensionPanel', () => { label: 'Max of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'max', @@ -956,6 +965,7 @@ describe('IndexPatternDimensionPanel', () => { label: 'Date Histogram of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts index 336deef6147a3..8307b8e0ab828 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts @@ -153,6 +153,7 @@ describe('IndexPattern Data Source', () => { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -214,6 +215,7 @@ describe('IndexPattern Data Source', () => { label: 'Count of Documents', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'count', @@ -222,6 +224,7 @@ describe('IndexPattern Data Source', () => { label: 'Date', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -391,6 +394,7 @@ describe('IndexPattern Data Source', () => { const sampleColumn: IndexPatternColumn = { dataType: 'number', isBucketed: false, + isMetric: true, label: 'foo', operationType: 'max', sourceField: 'baz', @@ -441,6 +445,7 @@ describe('IndexPattern Data Source', () => { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, } as Operation); }); 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 16202ec50f28d..cdea9fdf9b932 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, scale } = column; + const { dataType, label, isBucketed, isMetric, scale } = column; return { label, dataType, isBucketed, scale, + isMetric, }; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx index d86d88ed6ff02..37fee8f279d7a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx @@ -153,6 +153,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -529,6 +530,7 @@ describe('IndexPattern Data Source suggestions', () => { col1: { dataType: 'string', isBucketed: true, + isMetric: false, sourceField: 'source', label: 'values of source', operationType: 'terms', @@ -541,6 +543,7 @@ describe('IndexPattern Data Source suggestions', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, sourceField: 'bytes', label: 'Min of bytes', operationType: 'min', @@ -564,6 +567,7 @@ describe('IndexPattern Data Source suggestions', () => { col1: { dataType: 'date', isBucketed: true, + isMetric: false, sourceField: 'timestamp', label: 'date histogram of timestamp', operationType: 'date_histogram', @@ -574,6 +578,7 @@ describe('IndexPattern Data Source suggestions', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, sourceField: 'bytes', label: 'Min of bytes', operationType: 'min', @@ -884,6 +889,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op 2', dataType: 'number', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -910,6 +916,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, }, }, ], @@ -927,6 +934,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op 2', dataType: 'number', isBucketed: true, + isMetric: false, }, }, ], @@ -947,6 +955,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, operationType: 'terms', sourceField: 'field1', @@ -960,6 +969,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, operationType: 'terms', sourceField: 'field2', @@ -973,6 +983,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, operationType: 'terms', sourceField: 'field3', @@ -986,6 +997,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op', dataType: 'number', isBucketed: false, + isMetric: true, operationType: 'avg', sourceField: 'field4', @@ -994,6 +1006,7 @@ describe('IndexPattern Data Source suggestions', () => { label: 'My Op', dataType: 'number', isBucketed: false, + isMetric: true, operationType: 'min', sourceField: 'field5', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts index 1b5007cbe5558..a3a04802b0a31 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts @@ -31,7 +31,12 @@ function buildSuggestion({ datasourceSuggestionId?: number; }) { const columnOrder = (updatedLayer || state.layers[layerId]).columnOrder; - const columns = (updatedLayer || state.layers[layerId]).columns; + const columnMap = (updatedLayer || state.layers[layerId]).columns; + const columns = columnOrder.map(columnId => ({ + columnId, + operation: columnToOperation(columnMap[columnId]), + })); + return { state: updatedLayer ? { @@ -44,11 +49,8 @@ function buildSuggestion({ : state, table: { - columns: columnOrder.map(columnId => ({ - columnId, - operation: columnToOperation(columns[columnId]), - })), - isMultiRow: isMultiRow || true, + columns, + isMultiRow: isMultiRow || columns.some(col => !columnMap[col.columnId].isMetric), datasourceSuggestionId: datasourceSuggestionId || 0, layerId, }, @@ -100,6 +102,7 @@ function getExistingLayerSuggestionsForField( } else if (!usableAsBucketOperation && operations.length > 0) { updatedLayer = addFieldAsMetricOperation(layer, layerId, indexPattern, field); } + return updatedLayer ? [ buildSuggestion({ diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/layerpanel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/layerpanel.test.tsx index 46e381d69741b..0faa6b4725896 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/layerpanel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/layerpanel.test.tsx @@ -27,6 +27,7 @@ const initialState: IndexPatternPrivateState = { label: 'My Op', dataType: 'string', isBucketed: true, + isMetric: false, operationType: 'terms', sourceField: 'source', params: { @@ -41,6 +42,7 @@ const initialState: IndexPatternPrivateState = { label: 'My Op', dataType: 'number', isBucketed: false, + isMetric: true, operationType: 'avg', sourceField: 'memory', }, 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 0cb4838faa12c..304999ca8f5bc 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, + isMetric: true, scale: 'ratio', }, ]; @@ -32,6 +33,7 @@ export const countOperation: OperationDefinition = { operationType: 'count', suggestedPriority, isBucketed: false, + isMetric: true, scale: 'ratio', }; }, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx index 8e94087f4a5fb..299d5ca8250c7 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx @@ -58,6 +58,7 @@ describe('date_histogram', () => { label: 'Value of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -76,6 +77,7 @@ describe('date_histogram', () => { label: 'Value of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -155,6 +157,7 @@ describe('date_histogram', () => { { dataType: 'date', isBucketed: true, + isMetric: false, label: '', operationType: 'date_histogram', sourceField: 'dateField', @@ -197,6 +200,7 @@ describe('date_histogram', () => { { dataType: 'date', isBucketed: true, + isMetric: false, label: '', operationType: 'date_histogram', sourceField: 'dateField', 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 e454c700bb8db..8f0b0ff0393d9 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 { label: 'Filter Ratio', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'filter_ratio', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx index 3f4e7c3dc407d..7caada3c9f7a5 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx @@ -27,6 +27,7 @@ export const filterRatioOperation: OperationDefinition( { dataType: 'number', isBucketed: false, + isMetric: true, scale: 'ratio', }, ]; @@ -60,6 +61,7 @@ function buildMetricOperation( suggestedPriority, sourceField: field ? field.name : '', isBucketed: false, + isMetric: true, scale: 'ratio', } as T; }, 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 1f639907b79d0..def66db01a7fe 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 @@ -31,6 +31,7 @@ describe('terms', () => { label: 'Top value of category', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -45,6 +46,7 @@ describe('terms', () => { label: 'Count', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'count', @@ -91,6 +93,7 @@ describe('terms', () => { { dataType: 'string', isBucketed: true, + isMetric: false, scale: 'ordinal', }, ]); @@ -106,6 +109,7 @@ describe('terms', () => { { dataType: 'boolean', isBucketed: true, + isMetric: false, scale: 'ordinal', }, ]); @@ -158,6 +162,7 @@ describe('terms', () => { label: 'Count', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'count', @@ -184,6 +189,7 @@ describe('terms', () => { label: 'Top value of category', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -199,6 +205,7 @@ describe('terms', () => { label: 'Count', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'count', @@ -213,6 +220,7 @@ describe('terms', () => { label: 'Top value of category', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -238,6 +246,7 @@ describe('terms', () => { label: 'Top value of category', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -253,6 +262,7 @@ describe('terms', () => { label: 'Value of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -310,6 +320,7 @@ describe('terms', () => { label: 'Count', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'filter_ratio', 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 ca0455c9372da..a11b49f6ab784 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, + isMetric: false, scale: 'ordinal', }, ]; @@ -85,6 +86,7 @@ export const termsOperation: OperationDefinition = { suggestedPriority, sourceField: field.name, isBucketed: true, + isMetric: false, params: { size: DEFAULT_SIZE, orderBy: existingMetricColumn 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 d4353f52c4a67..49719c35a09c2 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 @@ -163,6 +163,7 @@ describe('getOperationTypesForField', () => { label: 'Date Histogram of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -234,6 +235,7 @@ describe('getOperationTypesForField', () => { "operationMetaData": Object { "dataType": "string", "isBucketed": true, + "isMetric": false, "scale": "ordinal", }, "operations": Array [ @@ -248,6 +250,7 @@ describe('getOperationTypesForField', () => { "operationMetaData": Object { "dataType": "date", "isBucketed": true, + "isMetric": false, "scale": "interval", }, "operations": Array [ @@ -262,6 +265,7 @@ describe('getOperationTypesForField', () => { "operationMetaData": Object { "dataType": "number", "isBucketed": false, + "isMetric": true, "scale": "ratio", }, "operations": Array [ diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts index e44dea4340777..897af8bbc28ff 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts @@ -31,6 +31,7 @@ describe('state_helpers', () => { label: 'Top values of source', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -55,6 +56,7 @@ describe('state_helpers', () => { label: 'Count', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'count', @@ -76,6 +78,7 @@ describe('state_helpers', () => { label: 'Top values of source', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -100,6 +103,7 @@ describe('state_helpers', () => { label: 'Count', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'count', @@ -127,6 +131,7 @@ describe('state_helpers', () => { label: 'Value of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -173,6 +178,7 @@ describe('state_helpers', () => { label: 'Average of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'avg', @@ -182,6 +188,7 @@ describe('state_helpers', () => { label: 'Max of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'max', @@ -200,6 +207,7 @@ describe('state_helpers', () => { label: 'Date histogram of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -232,6 +240,7 @@ describe('state_helpers', () => { label: 'Date histogram of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -253,6 +262,7 @@ describe('state_helpers', () => { label: 'Date histogram of order_date', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -274,6 +284,7 @@ describe('state_helpers', () => { label: 'Top values of source', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -289,6 +300,7 @@ describe('state_helpers', () => { label: 'Average of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'avg', @@ -308,6 +320,7 @@ describe('state_helpers', () => { label: 'Count', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'count', @@ -343,6 +356,7 @@ describe('state_helpers', () => { label: 'Value of timestamp', dataType: 'string', isBucketed: false, + isMetric: false, // Private operationType: 'date_histogram', @@ -362,6 +376,7 @@ describe('state_helpers', () => { label: 'Top Values of category', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -378,6 +393,7 @@ describe('state_helpers', () => { label: 'Average of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'avg', @@ -387,6 +403,7 @@ describe('state_helpers', () => { label: 'Date Histogram of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -406,6 +423,7 @@ describe('state_helpers', () => { label: 'Top Values of category', dataType: 'string', isBucketed: true, + isMetric: false, // Private operationType: 'terms', @@ -423,6 +441,7 @@ describe('state_helpers', () => { label: 'Average of bytes', dataType: 'number', isBucketed: false, + isMetric: true, // Private operationType: 'avg', @@ -433,6 +452,7 @@ describe('state_helpers', () => { label: 'Date Histogram of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -512,6 +532,7 @@ describe('state_helpers', () => { col1: { dataType: 'string', isBucketed: true, + isMetric: false, label: '', operationType: 'terms', sourceField: 'fieldA', @@ -524,6 +545,7 @@ describe('state_helpers', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, label: '', operationType: 'avg', sourceField: 'xxx', @@ -545,6 +567,7 @@ describe('state_helpers', () => { col1: { dataType: 'string', isBucketed: true, + isMetric: false, label: '', operationType: 'date_histogram', sourceField: 'fieldC', @@ -555,6 +578,7 @@ describe('state_helpers', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, label: '', operationType: 'avg', sourceField: 'fieldB', @@ -576,6 +600,7 @@ describe('state_helpers', () => { col1: { dataType: 'date', isBucketed: true, + isMetric: false, label: '', operationType: 'date_histogram', sourceField: 'fieldD', @@ -606,6 +631,7 @@ describe('state_helpers', () => { col1: { dataType: 'string', isBucketed: true, + isMetric: false, label: '', operationType: 'terms', sourceField: 'fieldA', @@ -618,6 +644,7 @@ describe('state_helpers', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, label: '', operationType: 'avg', sourceField: 'fieldD', @@ -639,6 +666,7 @@ describe('state_helpers', () => { col1: { dataType: 'string', isBucketed: true, + isMetric: false, label: '', operationType: 'terms', sourceField: 'fieldA', @@ -651,6 +679,7 @@ describe('state_helpers', () => { col2: { dataType: 'number', isBucketed: false, + isMetric: true, label: '', operationType: 'min', sourceField: 'fieldC', diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx index ff2e55ac83dcc..e2c184a7a4803 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.test.tsx @@ -54,15 +54,21 @@ describe('MetricConfigPanel', () => { const exampleOperation: Operation = { dataType: 'number', isBucketed: false, + isMetric: false, label: 'bar', }; const ops: Operation[] = [ - { ...exampleOperation, dataType: 'number' }, + { ...exampleOperation, isMetric: true, dataType: 'number' }, + { ...exampleOperation, isMetric: false, dataType: 'number' }, { ...exampleOperation, dataType: 'string' }, - { ...exampleOperation, dataType: 'boolean' }, + { ...exampleOperation, isMetric: true, dataType: 'boolean' }, + { ...exampleOperation, isMetric: false, dataType: 'boolean' }, { ...exampleOperation, dataType: 'date' }, ]; expect(columnId).toEqual('shazm'); - expect(ops.filter(filterOperations)).toEqual([{ ...exampleOperation, dataType: 'number' }]); + expect(ops.filter(filterOperations)).toEqual([ + { ...exampleOperation, isMetric: true, dataType: 'number' }, + { ...exampleOperation, isMetric: true, dataType: 'boolean' }, + ]); }); }); diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx index 36025a5fa19a3..e5d1d7dc731fc 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_config_panel.tsx @@ -6,12 +6,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiForm, EuiFormRow, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { EuiFormRow, EuiPanel, EuiSpacer } from '@elastic/eui'; import { State } from './types'; import { VisualizationProps, OperationMetadata } from '../types'; import { NativeRenderer } from '../native_renderer'; -const isMetric = (op: OperationMetadata) => !op.isBucketed && op.dataType === 'number'; +const isMetric = (op: OperationMetadata) => op.isMetric; export function MetricConfigPanel(props: VisualizationProps) { const { state, frame } = props; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index 6a677042172ef..b5eafd3595a42 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -7,7 +7,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { MetricConfig } from './types'; import { LensMultiTable } from '../types'; import { RenderFunction } from './plugin'; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts index 120d0369bc7ea..bf9d5ad4340f2 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts @@ -15,6 +15,7 @@ describe('metric_suggestions', () => { dataType: 'number', label: `Avg ${columnId}`, isBucketed: false, + isMetric: true, }, }; } @@ -26,6 +27,7 @@ describe('metric_suggestions', () => { dataType: 'string', label: `Top 5 ${columnId}`, isBucketed: true, + isMetric: false, }, }; } @@ -36,6 +38,7 @@ describe('metric_suggestions', () => { operation: { dataType: 'date', isBucketed: true, + isMetric: false, label: `${columnId} histogram`, }, }; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts index b6de912089c4b..fa68aa2c7122a 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_visualization.test.ts @@ -65,6 +65,7 @@ describe('metric_visualization', () => { id: 'a', dataType: 'number', isBucketed: false, + isMetric: true, label: 'shazm', }; }, diff --git a/x-pack/legacy/plugins/lens/public/multi_column_editor/multi_column_editor.test.tsx b/x-pack/legacy/plugins/lens/public/multi_column_editor/multi_column_editor.test.tsx index 012c27d3ce3ff..08a94c2180ab9 100644 --- a/x-pack/legacy/plugins/lens/public/multi_column_editor/multi_column_editor.test.tsx +++ b/x-pack/legacy/plugins/lens/public/multi_column_editor/multi_column_editor.test.tsx @@ -49,6 +49,7 @@ describe('MultiColumnEditor', () => { dataType: 'number', id, isBucketed: true, + isMetric: false, label: 'BaaaZZZ!', }; }, diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts index 3f448f00a8d75..e74310ad36bf7 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; + isMetric: boolean; scale?: 'ordinal' | 'interval' | 'ratio'; // Extra meta-information like cardinality, color } diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.test.tsx index 64ceddac2021d..cc4a9c80853bf 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.test.tsx @@ -148,6 +148,7 @@ describe('XYConfigPanel', () => { const exampleOperation: Operation = { dataType: 'number', isBucketed: false, + isMetric: true, label: 'bar', }; const bucketedOps: Operation[] = [ @@ -186,6 +187,7 @@ describe('XYConfigPanel', () => { const exampleOperation: Operation = { dataType: 'number', isBucketed: false, + isMetric: true, label: 'bar', }; const ops: Operation[] = [ diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx index 249ea6b5b72ea..835b2d9c0bccb 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx @@ -28,7 +28,7 @@ import { NativeRenderer } from '../native_renderer'; import { MultiColumnEditor } from '../multi_column_editor'; import { generateId } from '../id_generator'; -const isNumericMetric = (op: OperationMetadata) => !op.isBucketed && op.dataType === 'number'; +const isNumericMetric = (op: OperationMetadata) => op.isMetric && op.dataType === 'number'; const isBucketed = (op: OperationMetadata) => op.isBucketed; type UnwrapArray = T extends Array ? P : T; 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 4005a51280595..d3422138fec65 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 @@ -20,6 +20,7 @@ describe('xy_suggestions', () => { dataType: 'number', label: `Avg ${columnId}`, isBucketed: false, + isMetric: true, }, }; } @@ -31,6 +32,7 @@ describe('xy_suggestions', () => { dataType: 'string', label: `Top 5 ${columnId}`, isBucketed: true, + isMetric: false, }, }; } @@ -41,6 +43,7 @@ describe('xy_suggestions', () => { operation: { dataType: 'date', isBucketed: true, + isMetric: false, label: `${columnId} histogram`, }, }; @@ -258,6 +261,7 @@ describe('xy_suggestions', () => { columnId: 'mybool', operation: { dataType: 'boolean', + isMetric: false, isBucketed: false, label: 'Yes / No', },