/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ import { Ast } from '@kbn/interpreter/common'; import { buildExpression } from '../../../../../src/plugins/expressions/public'; import { createMockDatasource, createMockFramePublicAPI } from '../editor_frame_service/mocks'; import { DatatableVisualizationState, datatableVisualization } from './visualization'; import { Operation, DataType, FramePublicAPI, TableSuggestionColumn } from '../types'; function mockFrame(): FramePublicAPI { return { ...createMockFramePublicAPI(), addNewLayer: () => 'aaa', removeLayers: () => {}, datasourceLayers: {}, query: { query: '', language: 'lucene' }, dateRange: { fromDate: 'now-7d', toDate: 'now', }, filters: [], }; } describe('Datatable Visualization', () => { describe('#initialize', () => { it('should initialize from the empty state', () => { expect(datatableVisualization.initialize(mockFrame(), undefined)).toEqual({ layers: [ { layerId: 'aaa', columns: [], }, ], }); }); it('should initialize from a persisted state', () => { const expectedState: DatatableVisualizationState = { layers: [ { layerId: 'foo', columns: ['saved'], }, ], }; expect(datatableVisualization.initialize(mockFrame(), expectedState)).toEqual(expectedState); }); }); describe('#getLayerIds', () => { it('return the layer ids', () => { const state: DatatableVisualizationState = { layers: [ { layerId: 'baz', columns: ['a', 'b', 'c'], }, ], }; expect(datatableVisualization.getLayerIds(state)).toEqual(['baz']); }); }); describe('#clearLayer', () => { it('should reset the layer', () => { const state: DatatableVisualizationState = { layers: [ { layerId: 'baz', columns: ['a', 'b', 'c'], }, ], }; expect(datatableVisualization.clearLayer(state, 'baz')).toMatchObject({ layers: [ { layerId: 'baz', columns: [], }, ], }); }); }); describe('#getSuggestions', () => { function numCol(columnId: string): TableSuggestionColumn { return { columnId, operation: { dataType: 'number', label: `Avg ${columnId}`, isBucketed: false, }, }; } function strCol(columnId: string): TableSuggestionColumn { return { columnId, operation: { dataType: 'string', label: `Top 5 ${columnId}`, isBucketed: true, }, }; } it('should accept a single-layer suggestion', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layers: [{ layerId: 'first', columns: ['col1'] }], }, table: { isMultiRow: true, layerId: 'first', changeType: 'initial', columns: [numCol('col1'), strCol('col2')], }, keptLayerIds: [], }); expect(suggestions.length).toBeGreaterThan(0); }); it('should not make suggestions when the table is unchanged', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layers: [{ layerId: 'first', columns: ['col1'] }], }, table: { isMultiRow: true, layerId: 'first', changeType: 'unchanged', columns: [numCol('col1')], }, keptLayerIds: ['first'], }); expect(suggestions).toEqual([]); }); it('should not make suggestions when multiple layers are involved', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layers: [{ layerId: 'first', columns: ['col1'] }], }, table: { isMultiRow: true, layerId: 'first', changeType: 'unchanged', columns: [numCol('col1')], }, keptLayerIds: ['first', 'second'], }); expect(suggestions).toEqual([]); }); it('should not make suggestions when the suggestion keeps a different layer', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layers: [{ layerId: 'older', columns: ['col1'] }], }, table: { isMultiRow: true, layerId: 'newer', changeType: 'initial', columns: [numCol('col1'), strCol('col2')], }, keptLayerIds: ['older'], }); expect(suggestions).toEqual([]); }); it('should suggest unchanged tables when the state is not passed in', () => { const suggestions = datatableVisualization.getSuggestions({ table: { isMultiRow: true, layerId: 'first', changeType: 'unchanged', columns: [numCol('col1')], }, keptLayerIds: ['first'], }); expect(suggestions.length).toBeGreaterThan(0); }); }); describe('#getConfiguration', () => { it('returns a single layer option', () => { const datasource = createMockDatasource('test'); const frame = mockFrame(); frame.datasourceLayers = { first: datasource.publicAPIMock }; expect( datatableVisualization.getConfiguration({ layerId: 'first', state: { layers: [{ layerId: 'first', columns: [] }], }, frame, }).groups ).toHaveLength(2); }); it('allows only bucket operations one category', () => { const datasource = createMockDatasource('test'); const frame = mockFrame(); frame.datasourceLayers = { first: datasource.publicAPIMock }; const filterOperations = datatableVisualization.getConfiguration({ layerId: 'first', state: { layers: [{ layerId: 'first', columns: [] }], }, frame, }).groups[0].filterOperations; const baseOperation: Operation = { dataType: 'string', isBucketed: true, label: '', }; expect(filterOperations({ ...baseOperation })).toEqual(true); expect(filterOperations({ ...baseOperation, dataType: 'number' })).toEqual(true); expect(filterOperations({ ...baseOperation, dataType: 'date' })).toEqual(true); expect(filterOperations({ ...baseOperation, dataType: 'boolean' })).toEqual(true); expect(filterOperations({ ...baseOperation, dataType: 'other' as DataType })).toEqual(true); expect(filterOperations({ ...baseOperation, dataType: 'date', isBucketed: false })).toEqual( false ); expect(filterOperations({ ...baseOperation, dataType: 'number', isBucketed: false })).toEqual( false ); }); it('allows only metric operations in one category', () => { const datasource = createMockDatasource('test'); const frame = mockFrame(); frame.datasourceLayers = { first: datasource.publicAPIMock }; const filterOperations = datatableVisualization.getConfiguration({ layerId: 'first', state: { layers: [{ layerId: 'first', columns: [] }], }, frame, }).groups[1].filterOperations; const baseOperation: Operation = { dataType: 'string', isBucketed: true, label: '', }; expect(filterOperations({ ...baseOperation })).toEqual(false); expect(filterOperations({ ...baseOperation, dataType: 'number' })).toEqual(false); expect(filterOperations({ ...baseOperation, dataType: 'date' })).toEqual(false); expect(filterOperations({ ...baseOperation, dataType: 'boolean' })).toEqual(false); expect(filterOperations({ ...baseOperation, dataType: 'other' as DataType })).toEqual(false); expect(filterOperations({ ...baseOperation, dataType: 'date', isBucketed: false })).toEqual( true ); expect(filterOperations({ ...baseOperation, dataType: 'number', isBucketed: false })).toEqual( true ); }); it('reorders the rendered colums based on the order from the datasource', () => { const datasource = createMockDatasource('test'); const layer = { layerId: 'a', columns: ['b', 'c'] }; const frame = mockFrame(); frame.datasourceLayers = { a: datasource.publicAPIMock }; datasource.publicAPIMock.getTableSpec.mockReturnValue([{ columnId: 'c' }, { columnId: 'b' }]); expect( datatableVisualization.getConfiguration({ layerId: 'a', state: { layers: [layer] }, frame, }).groups[1].accessors ).toEqual([{ columnId: 'c' }, { columnId: 'b' }]); }); }); describe('#removeDimension', () => { it('allows columns to be removed', () => { const layer = { layerId: 'layer1', columns: ['b', 'c'] }; expect( datatableVisualization.removeDimension({ prevState: { layers: [layer] }, layerId: 'layer1', columnId: 'b', }) ).toEqual({ layers: [ { layerId: 'layer1', columns: ['c'], }, ], }); }); }); describe('#setDimension', () => { it('allows columns to be added', () => { const layer = { layerId: 'layer1', columns: ['b', 'c'] }; expect( datatableVisualization.setDimension({ prevState: { layers: [layer] }, layerId: 'layer1', columnId: 'd', groupId: '', }) ).toEqual({ layers: [ { layerId: 'layer1', columns: ['b', 'c', 'd'], }, ], }); }); it('does not set a duplicate dimension', () => { const layer = { layerId: 'layer1', columns: ['b', 'c'] }; expect( datatableVisualization.setDimension({ prevState: { layers: [layer] }, layerId: 'layer1', columnId: 'b', groupId: '', }) ).toEqual({ layers: [ { layerId: 'layer1', columns: ['b', 'c'], }, ], }); }); }); describe('#toExpression', () => { it('reorders the rendered colums based on the order from the datasource', () => { const datasource = createMockDatasource('test'); const layer = { layerId: 'a', columns: ['b', 'c'] }; const frame = mockFrame(); frame.datasourceLayers = { a: datasource.publicAPIMock }; datasource.publicAPIMock.getTableSpec.mockReturnValue([{ columnId: 'c' }, { columnId: 'b' }]); datasource.publicAPIMock.getOperationForColumnId.mockReturnValue({ dataType: 'string', isBucketed: false, // <= make them metrics label: 'label', }); const expression = datatableVisualization.toExpression( { layers: [layer] }, frame.datasourceLayers ) as Ast; const tableArgs = buildExpression(expression).findFunction('lens_datatable_columns'); expect(tableArgs).toHaveLength(1); expect(tableArgs[0].arguments).toEqual({ columnIds: ['c', 'b'], }); }); it('returns no expression if the metric dimension is not defined', () => { const datasource = createMockDatasource('test'); const layer = { layerId: 'a', columns: ['b', 'c'] }; const frame = mockFrame(); frame.datasourceLayers = { a: datasource.publicAPIMock }; datasource.publicAPIMock.getTableSpec.mockReturnValue([{ columnId: 'c' }, { columnId: 'b' }]); datasource.publicAPIMock.getOperationForColumnId.mockReturnValue({ dataType: 'string', isBucketed: true, // move it from the metric to the break down by side label: 'label', }); const expression = datatableVisualization.toExpression( { layers: [layer] }, frame.datasourceLayers ); expect(expression).toEqual(null); }); }); describe('#getErrorMessages', () => { it('returns undefined if the datasource is missing a metric dimension', () => { const datasource = createMockDatasource('test'); const layer = { layerId: 'a', columns: ['b', 'c'] }; const frame = mockFrame(); frame.datasourceLayers = { a: datasource.publicAPIMock }; datasource.publicAPIMock.getTableSpec.mockReturnValue([{ columnId: 'c' }, { columnId: 'b' }]); datasource.publicAPIMock.getOperationForColumnId.mockReturnValue({ dataType: 'string', isBucketed: true, // move it from the metric to the break down by side label: 'label', }); const error = datatableVisualization.getErrorMessages({ layers: [layer] }, frame); expect(error).toBeUndefined(); }); it('returns undefined if the metric dimension is defined', () => { const datasource = createMockDatasource('test'); const layer = { layerId: 'a', columns: ['b', 'c'] }; const frame = mockFrame(); frame.datasourceLayers = { a: datasource.publicAPIMock }; datasource.publicAPIMock.getTableSpec.mockReturnValue([{ columnId: 'c' }, { columnId: 'b' }]); datasource.publicAPIMock.getOperationForColumnId.mockReturnValue({ dataType: 'string', isBucketed: false, // keep it a metric label: 'label', }); const error = datatableVisualization.getErrorMessages({ layers: [layer] }, frame); expect(error).toBeUndefined(); }); }); });