kibana/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx
Wylie Conlon b50e7ba7da
[Lens] Implement types for reference-based operations (#83603)
* [Lens] Implement types for reference-based operations

* Update from review feedback
2020-11-20 13:42:12 -05:00

434 lines
13 KiB
TypeScript

/*
* 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();
});
});
});