2019-09-17 20:57:53 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2020-08-14 16:54:43 +02:00
|
|
|
import { Ast } from '@kbn/interpreter/common';
|
2019-09-17 20:57:53 +02:00
|
|
|
import { i18n } from '@kbn/i18n';
|
2020-11-04 18:28:00 +01:00
|
|
|
import {
|
|
|
|
SuggestionRequest,
|
|
|
|
Visualization,
|
|
|
|
VisualizationSuggestion,
|
|
|
|
Operation,
|
|
|
|
DatasourcePublicAPI,
|
|
|
|
} from '../types';
|
2020-09-15 11:24:02 +02:00
|
|
|
import { LensIconChartDatatable } from '../assets/chart_datatable';
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
export interface LayerState {
|
|
|
|
layerId: string;
|
|
|
|
columns: string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface DatatableVisualizationState {
|
|
|
|
layers: LayerState[];
|
2020-12-15 12:37:59 +01:00
|
|
|
sorting?: {
|
|
|
|
columnId: string | undefined;
|
|
|
|
direction: 'asc' | 'desc' | 'none';
|
|
|
|
};
|
2019-09-17 20:57:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function newLayerState(layerId: string): LayerState {
|
|
|
|
return {
|
|
|
|
layerId,
|
2020-03-17 14:57:52 +01:00
|
|
|
columns: [],
|
2019-09-17 20:57:53 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-08-21 18:08:25 +02:00
|
|
|
export const datatableVisualization: Visualization<DatatableVisualizationState> = {
|
2019-09-17 20:57:53 +02:00
|
|
|
id: 'lnsDatatable',
|
|
|
|
|
|
|
|
visualizationTypes: [
|
|
|
|
{
|
|
|
|
id: 'lnsDatatable',
|
2020-09-15 11:24:02 +02:00
|
|
|
icon: LensIconChartDatatable,
|
2019-09-17 20:57:53 +02:00
|
|
|
label: i18n.translate('xpack.lens.datatable.label', {
|
2019-10-03 19:30:33 +02:00
|
|
|
defaultMessage: 'Data table',
|
2019-09-17 20:57:53 +02:00
|
|
|
}),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
|
2020-04-30 01:14:47 +02:00
|
|
|
getVisualizationTypeId() {
|
|
|
|
return 'lnsDatatable';
|
|
|
|
},
|
|
|
|
|
2020-01-14 18:06:51 +01:00
|
|
|
getLayerIds(state) {
|
2020-05-22 09:08:58 +02:00
|
|
|
return state.layers.map((l) => l.layerId);
|
2020-01-14 18:06:51 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
clearLayer(state) {
|
|
|
|
return {
|
2020-05-22 09:08:58 +02:00
|
|
|
layers: state.layers.map((l) => newLayerState(l.layerId)),
|
2020-01-14 18:06:51 +01:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
getDescription() {
|
2019-09-17 20:57:53 +02:00
|
|
|
return {
|
2020-09-15 11:24:02 +02:00
|
|
|
icon: LensIconChartDatatable,
|
2019-09-17 20:57:53 +02:00
|
|
|
label: i18n.translate('xpack.lens.datatable.label', {
|
2019-10-03 19:30:33 +02:00
|
|
|
defaultMessage: 'Data table',
|
2019-09-17 20:57:53 +02:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
switchVisualizationType: (_, state) => state,
|
|
|
|
|
|
|
|
initialize(frame, state) {
|
|
|
|
return (
|
|
|
|
state || {
|
2019-09-25 16:58:46 +02:00
|
|
|
layers: [newLayerState(frame.addNewLayer())],
|
2019-09-17 20:57:53 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
getSuggestions({
|
|
|
|
table,
|
|
|
|
state,
|
2019-10-30 00:05:03 +01:00
|
|
|
keptLayerIds,
|
2019-09-17 20:57:53 +02:00
|
|
|
}: SuggestionRequest<DatatableVisualizationState>): Array<
|
|
|
|
VisualizationSuggestion<DatatableVisualizationState>
|
|
|
|
> {
|
2019-10-30 00:05:03 +01:00
|
|
|
if (
|
|
|
|
keptLayerIds.length > 1 ||
|
|
|
|
(keptLayerIds.length && table.layerId !== keptLayerIds[0]) ||
|
|
|
|
(state && table.changeType === 'unchanged')
|
|
|
|
) {
|
2019-09-17 20:57:53 +02:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
const title =
|
|
|
|
table.changeType === 'unchanged'
|
|
|
|
? i18n.translate('xpack.lens.datatable.suggestionLabel', {
|
|
|
|
defaultMessage: 'As table',
|
|
|
|
})
|
|
|
|
: i18n.translate('xpack.lens.datatable.visualizationOf', {
|
|
|
|
defaultMessage: 'Table {operations}',
|
|
|
|
values: {
|
|
|
|
operations:
|
|
|
|
table.label ||
|
|
|
|
table.columns
|
2020-05-22 09:08:58 +02:00
|
|
|
.map((col) => col.operation.label)
|
2019-09-17 20:57:53 +02:00
|
|
|
.join(
|
|
|
|
i18n.translate('xpack.lens.datatable.conjunctionSign', {
|
|
|
|
defaultMessage: ' & ',
|
|
|
|
description:
|
|
|
|
'A character that can be used for conjunction of multiple enumarated items. Make sure to include spaces around it if needed.',
|
|
|
|
})
|
|
|
|
),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
title,
|
2020-05-05 21:59:32 +02:00
|
|
|
// table with >= 10 columns will have a score of 0.4, fewer columns reduce score
|
|
|
|
score: (Math.min(table.columns.length, 10) / 10) * 0.4,
|
2019-09-17 20:57:53 +02:00
|
|
|
state: {
|
|
|
|
layers: [
|
|
|
|
{
|
|
|
|
layerId: table.layerId,
|
2020-05-22 09:08:58 +02:00
|
|
|
columns: table.columns.map((col) => col.columnId),
|
2019-09-17 20:57:53 +02:00
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
2020-09-15 11:24:02 +02:00
|
|
|
previewIcon: LensIconChartDatatable,
|
2020-04-30 16:52:10 +02:00
|
|
|
// tables are hidden from suggestion bar, but used for drag & drop and chart switching
|
|
|
|
hide: true,
|
2019-09-17 20:57:53 +02:00
|
|
|
},
|
|
|
|
];
|
|
|
|
},
|
|
|
|
|
2020-03-17 14:57:52 +01:00
|
|
|
getConfiguration({ state, frame, layerId }) {
|
2020-11-04 18:28:00 +01:00
|
|
|
const { sortedColumns, datasource } =
|
|
|
|
getDataSourceAndSortedColumns(state, frame.datasourceLayers, layerId) || {};
|
|
|
|
|
|
|
|
if (!sortedColumns) {
|
2020-03-17 14:57:52 +01:00
|
|
|
return { groups: [] };
|
2020-01-14 18:06:51 +01:00
|
|
|
}
|
2020-03-17 14:57:52 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
groups: [
|
|
|
|
{
|
|
|
|
groupId: 'columns',
|
2020-09-04 16:11:41 +02:00
|
|
|
groupLabel: i18n.translate('xpack.lens.datatable.breakdown', {
|
|
|
|
defaultMessage: 'Break down by',
|
2020-03-17 14:57:52 +01:00
|
|
|
}),
|
|
|
|
layerId: state.layers[0].layerId,
|
2020-11-17 10:19:13 +01:00
|
|
|
accessors: sortedColumns
|
|
|
|
.filter((c) => datasource!.getOperationForColumnId(c)?.isBucketed)
|
|
|
|
.map((accessor) => ({ columnId: accessor })),
|
2020-03-17 14:57:52 +01:00
|
|
|
supportsMoreColumns: true,
|
2020-09-04 16:11:41 +02:00
|
|
|
filterOperations: (op) => op.isBucketed,
|
2020-03-20 19:59:51 +01:00
|
|
|
dataTestSubj: 'lnsDatatable_column',
|
2020-03-17 14:57:52 +01:00
|
|
|
},
|
2020-09-04 16:11:41 +02:00
|
|
|
{
|
|
|
|
groupId: 'metrics',
|
|
|
|
groupLabel: i18n.translate('xpack.lens.datatable.metrics', {
|
|
|
|
defaultMessage: 'Metrics',
|
|
|
|
}),
|
|
|
|
layerId: state.layers[0].layerId,
|
2020-11-17 10:19:13 +01:00
|
|
|
accessors: sortedColumns
|
|
|
|
.filter((c) => !datasource!.getOperationForColumnId(c)?.isBucketed)
|
|
|
|
.map((accessor) => ({ columnId: accessor })),
|
2020-09-04 16:11:41 +02:00
|
|
|
supportsMoreColumns: true,
|
|
|
|
filterOperations: (op) => !op.isBucketed,
|
|
|
|
required: true,
|
|
|
|
dataTestSubj: 'lnsDatatable_metrics',
|
|
|
|
},
|
2020-03-17 14:57:52 +01:00
|
|
|
],
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
setDimension({ prevState, layerId, columnId }) {
|
|
|
|
return {
|
|
|
|
...prevState,
|
2020-05-22 09:08:58 +02:00
|
|
|
layers: prevState.layers.map((l) => {
|
2020-03-17 14:57:52 +01:00
|
|
|
if (l.layerId !== layerId || l.columns.includes(columnId)) {
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
return { ...l, columns: [...l.columns, columnId] };
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
removeDimension({ prevState, layerId, columnId }) {
|
|
|
|
return {
|
|
|
|
...prevState,
|
2020-05-22 09:08:58 +02:00
|
|
|
layers: prevState.layers.map((l) =>
|
2020-03-17 14:57:52 +01:00
|
|
|
l.layerId === layerId
|
|
|
|
? {
|
|
|
|
...l,
|
2020-05-22 09:08:58 +02:00
|
|
|
columns: l.columns.filter((c) => c !== columnId),
|
2020-03-17 14:57:52 +01:00
|
|
|
}
|
|
|
|
: l
|
|
|
|
),
|
2020-12-15 12:37:59 +01:00
|
|
|
sorting: prevState.sorting?.columnId === columnId ? undefined : prevState.sorting,
|
2020-03-17 14:57:52 +01:00
|
|
|
};
|
2020-01-14 18:06:51 +01:00
|
|
|
},
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-11-04 18:28:00 +01:00
|
|
|
toExpression(state, datasourceLayers, { title, description } = {}): Ast | null {
|
|
|
|
const { sortedColumns, datasource } =
|
|
|
|
getDataSourceAndSortedColumns(state, datasourceLayers, state.layers[0].layerId) || {};
|
|
|
|
|
|
|
|
if (
|
|
|
|
sortedColumns?.length &&
|
|
|
|
sortedColumns.filter((c) => !datasource!.getOperationForColumnId(c)?.isBucketed).length === 0
|
|
|
|
) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const operations = sortedColumns!
|
|
|
|
.map((columnId) => ({ columnId, operation: datasource!.getOperationForColumnId(columnId) }))
|
2019-09-17 20:57:53 +02:00
|
|
|
.filter((o): o is { columnId: string; operation: Operation } => !!o.operation);
|
|
|
|
|
|
|
|
return {
|
|
|
|
type: 'expression',
|
|
|
|
chain: [
|
|
|
|
{
|
|
|
|
type: 'function',
|
|
|
|
function: 'lens_datatable',
|
|
|
|
arguments: {
|
2020-10-01 18:02:37 +02:00
|
|
|
title: [title || ''],
|
|
|
|
description: [description || ''],
|
2019-09-17 20:57:53 +02:00
|
|
|
columns: [
|
|
|
|
{
|
|
|
|
type: 'expression',
|
|
|
|
chain: [
|
|
|
|
{
|
|
|
|
type: 'function',
|
|
|
|
function: 'lens_datatable_columns',
|
|
|
|
arguments: {
|
2020-05-22 09:08:58 +02:00
|
|
|
columnIds: operations.map((o) => o.columnId),
|
2020-12-15 12:37:59 +01:00
|
|
|
sortBy: [state.sorting?.columnId || ''],
|
|
|
|
sortDirection: [state.sorting?.direction || 'none'],
|
2019-09-17 20:57:53 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
},
|
2020-11-04 18:28:00 +01:00
|
|
|
|
|
|
|
getErrorMessages(state, frame) {
|
|
|
|
return undefined;
|
|
|
|
},
|
2020-12-15 12:37:59 +01:00
|
|
|
|
|
|
|
onEditAction(state, event) {
|
|
|
|
if (event.data.action !== 'sort') {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
sorting: {
|
|
|
|
columnId: event.data.columnId,
|
|
|
|
direction: event.data.direction,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
2019-09-17 20:57:53 +02:00
|
|
|
};
|
2020-11-04 18:28:00 +01:00
|
|
|
|
|
|
|
function getDataSourceAndSortedColumns(
|
|
|
|
state: DatatableVisualizationState,
|
|
|
|
datasourceLayers: Record<string, DatasourcePublicAPI>,
|
|
|
|
layerId: string
|
|
|
|
) {
|
|
|
|
const layer = state.layers.find((l: LayerState) => l.layerId === layerId);
|
|
|
|
if (!layer) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const datasource = datasourceLayers[layer.layerId];
|
|
|
|
const originalOrder = datasource.getTableSpec().map(({ columnId }) => columnId);
|
|
|
|
// When we add a column it could be empty, and therefore have no order
|
|
|
|
const sortedColumns = Array.from(new Set(originalOrder.concat(layer.columns)));
|
|
|
|
return { datasource, sortedColumns };
|
|
|
|
}
|