[Lens] Move Lens functions to common (#105455)

* 🚚 First move batch to common

* 🚚 Second batch of move

* 🏷️ Import types only

* 🚚 Third batch

* 🚚 Fourth batch move

* 🚚 Another module moved

* 🚚 More function moved

* 🚚 Last bit of move

*  Reduce page load bundle size

* 🐛 Fix import issue

* 🐛 More import fix

*  Registered functions on the server

* 🐛 Expose datatable_column as well

*  Add server side expression test

* 🚚 Moved back render functions to public

*  Add a timezone arg to time_scale

* 🔥 Remove timezone arg

* 🔥 Remove server side code for now

* 👌 Integrated feedback

* 🚚 Move back datatable render function

* 🏷️ Fix imports

* 🏷️ Fix missing export

* 🚚 Move render functions back!

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Marco Liberati 2021-07-26 15:26:29 +02:00 committed by GitHub
parent 8924ff3219
commit 3c6b85469b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
136 changed files with 2124 additions and 1793 deletions

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { counterRate, CounterRateArgs } from '../counter_rate';
import { counterRate, CounterRateArgs } from './index';
import { Datatable } from 'src/plugins/expressions/public';
import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils';

View file

@ -6,11 +6,14 @@
*/
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public';
import {
getBucketIdentifier,
buildResultColumns,
} from '../../../../../../src/plugins/expressions/common';
import type {
ExpressionFunctionDefinition,
Datatable,
} from '../../../../../../src/plugins/expressions/common';
export interface CounterRateArgs {
by?: string[];

View file

@ -0,0 +1,158 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { cloneDeep } from 'lodash';
import type {
DatatableColumnMeta,
ExpressionFunctionDefinition,
} from '../../../../../../src/plugins/expressions/common';
import type { FormatFactory, LensMultiTable } from '../../types';
import type { ColumnConfigArg } from './datatable_column';
import { getSortingCriteria } from './sorting';
import { computeSummaryRowForColumn } from './summary';
import { transposeTable } from './transpose_helpers';
export interface SortingState {
columnId: string | undefined;
direction: 'asc' | 'desc' | 'none';
}
export interface DatatableProps {
data: LensMultiTable;
untransposedData?: LensMultiTable;
args: DatatableArgs;
}
export interface DatatableRender {
type: 'render';
as: 'lens_datatable_renderer';
value: DatatableProps;
}
export interface DatatableArgs {
title: string;
description?: string;
columns: ColumnConfigArg[];
sortingColumnId: SortingState['columnId'];
sortingDirection: SortingState['direction'];
}
function isRange(meta: { params?: { id?: string } } | undefined) {
return meta?.params?.id === 'range';
}
export const getDatatable = ({
formatFactory,
}: {
formatFactory: FormatFactory;
}): ExpressionFunctionDefinition<
'lens_datatable',
LensMultiTable,
DatatableArgs,
DatatableRender
> => ({
name: 'lens_datatable',
type: 'render',
inputTypes: ['lens_multitable'],
help: i18n.translate('xpack.lens.datatable.expressionHelpLabel', {
defaultMessage: 'Datatable renderer',
}),
args: {
title: {
types: ['string'],
help: i18n.translate('xpack.lens.datatable.titleLabel', {
defaultMessage: 'Title',
}),
},
description: {
types: ['string'],
help: '',
},
columns: {
types: ['lens_datatable_column'],
help: '',
multi: true,
},
sortingColumnId: {
types: ['string'],
help: '',
},
sortingDirection: {
types: ['string'],
help: '',
},
},
fn(data, args, context) {
let untransposedData: LensMultiTable | undefined;
// do the sorting at this level to propagate it also at CSV download
const [firstTable] = Object.values(data.tables);
const [layerId] = Object.keys(context.inspectorAdapters.tables || {});
const formatters: Record<string, ReturnType<FormatFactory>> = {};
firstTable.columns.forEach((column) => {
formatters[column.id] = formatFactory(column.meta?.params);
});
const hasTransposedColumns = args.columns.some((c) => c.isTransposed);
if (hasTransposedColumns) {
// store original shape of data separately
untransposedData = cloneDeep(data);
// transposes table and args inplace
transposeTable(args, firstTable, formatters);
}
const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args;
const columnsReverseLookup = firstTable.columns.reduce<
Record<string, { name: string; index: number; meta?: DatatableColumnMeta }>
>((memo, { id, name, meta }, i) => {
memo[id] = { name, index: i, meta };
return memo;
}, {});
const columnsWithSummary = args.columns.filter((c) => c.summaryRow);
for (const column of columnsWithSummary) {
column.summaryRowValue = computeSummaryRowForColumn(
column,
firstTable,
formatters,
formatFactory({ id: 'number' })
);
}
if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') {
// Sort on raw values for these types, while use the formatted value for the rest
const sortingCriteria = getSortingCriteria(
isRange(columnsReverseLookup[sortBy]?.meta)
? 'range'
: columnsReverseLookup[sortBy]?.meta?.type,
sortBy,
formatters[sortBy],
sortDirection
);
// replace the table here
context.inspectorAdapters.tables[layerId].rows = (firstTable.rows || [])
.slice()
.sort(sortingCriteria);
// replace also the local copy
firstTable.rows = context.inspectorAdapters.tables[layerId].rows;
} else {
args.sortingColumnId = undefined;
args.sortingDirection = 'none';
}
return {
type: 'render',
as: 'lens_datatable_renderer',
value: {
data,
untransposedData,
args,
},
};
},
});

View file

@ -0,0 +1,85 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { Direction } from '@elastic/eui';
import type {
CustomPaletteState,
PaletteOutput,
} from '../../../../../../src/plugins/charts/common';
import type {
ExpressionFunctionDefinition,
DatatableColumn,
} from '../../../../../../src/plugins/expressions/common';
import type { CustomPaletteParams } from '../../types';
export type LensGridDirection = 'none' | Direction;
export interface ColumnConfig {
columns: ColumnConfigArg[];
sortingColumnId: string | undefined;
sortingDirection: LensGridDirection;
}
export type ColumnConfigArg = Omit<ColumnState, 'palette'> & {
type: 'lens_datatable_column';
palette?: PaletteOutput<CustomPaletteState>;
summaryRowValue?: unknown;
};
export interface ColumnState {
columnId: string;
width?: number;
hidden?: boolean;
isTransposed?: boolean;
// These flags are necessary to transpose columns and map them back later
// They are set automatically and are not user-editable
transposable?: boolean;
originalColumnId?: string;
originalName?: string;
bucketValues?: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>;
alignment?: 'left' | 'right' | 'center';
palette?: PaletteOutput<CustomPaletteParams>;
colorMode?: 'none' | 'cell' | 'text';
summaryRow?: 'none' | 'sum' | 'avg' | 'count' | 'min' | 'max';
summaryLabel?: string;
}
export type DatatableColumnResult = ColumnState & { type: 'lens_datatable_column' };
export const datatableColumn: ExpressionFunctionDefinition<
'lens_datatable_column',
null,
ColumnState,
DatatableColumnResult
> = {
name: 'lens_datatable_column',
aliases: [],
type: 'lens_datatable_column',
help: '',
inputTypes: ['null'],
args: {
columnId: { types: ['string'], help: '' },
alignment: { types: ['string'], help: '' },
hidden: { types: ['boolean'], help: '' },
width: { types: ['number'], help: '' },
isTransposed: { types: ['boolean'], help: '' },
transposable: { types: ['boolean'], help: '' },
colorMode: { types: ['string'], help: '' },
palette: {
types: ['palette'],
help: '',
},
summaryRow: { types: ['string'], help: '' },
summaryLabel: { types: ['string'], help: '' },
},
fn: function fn(input: unknown, args: ColumnState) {
return {
type: 'lens_datatable_column',
...args,
};
},
};

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './datatable_column';
export * from './datatable';
export * from './summary';
export * from './transpose_helpers';
export * from './utils';

View file

@ -7,7 +7,7 @@
import ipaddr from 'ipaddr.js';
import type { IPv4, IPv6 } from 'ipaddr.js';
import { FieldFormat } from 'src/plugins/data/public';
import type { FieldFormat } from '../../../../../../src/plugins/data/common';
function isIPv6Address(ip: IPv4 | IPv6): ip is IPv6 {
return ip.kind() === 'ipv6';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { IFieldFormat } from 'src/plugins/data/public';
import { IFieldFormat } from 'src/plugins/data/common';
import { Datatable } from 'src/plugins/expressions';
import { computeSummaryRowForColumn, getFinalSummaryConfiguration } from './summary';

View file

@ -6,11 +6,11 @@
*/
import { i18n } from '@kbn/i18n';
import { FieldFormat } from 'src/plugins/data/public';
import { Datatable } from 'src/plugins/expressions/public';
import { ColumnConfigArg } from './datatable_visualization';
import type { FieldFormat } from '../../../../../../src/plugins/data/common';
import type { Datatable } from '../../../../../../src/plugins/expressions/common';
import { ColumnConfigArg } from './datatable_column';
import { getOriginalId } from './transpose_helpers';
import { isNumericField } from './utils';
import { isNumericFieldForDatatable } from './utils';
type SummaryRowType = Extract<ColumnConfigArg['summaryRow'], string>;
@ -19,7 +19,7 @@ export function getFinalSummaryConfiguration(
columnArgs: Pick<ColumnConfigArg, 'summaryRow' | 'summaryLabel'> | undefined,
table: Datatable | undefined
) {
const isNumeric = isNumericField(table, columnId);
const isNumeric = isNumericFieldForDatatable(table, columnId);
const summaryRow = isNumeric ? columnArgs?.summaryRow || 'none' : 'none';
const summaryLabel = columnArgs?.summaryLabel ?? getDefaultSummaryLabel(summaryRow);

View file

@ -7,8 +7,8 @@
import type { FieldFormat } from 'src/plugins/data/public';
import type { Datatable } from 'src/plugins/expressions';
import { DatatableArgs } from './datatable';
import { Args } from './expression';
import { transposeTable } from './transpose_helpers';
describe('transpose_helpes', () => {
@ -59,7 +59,7 @@ describe('transpose_helpes', () => {
};
}
function buildArgs(): Args {
function buildArgs(): DatatableArgs {
return {
title: 'Table',
sortingColumnId: undefined,

View file

@ -5,11 +5,14 @@
* 2.0.
*/
import type { FieldFormat } from 'src/plugins/data/public';
import type { Datatable, DatatableColumn, DatatableRow } from 'src/plugins/expressions';
import { ColumnConfig } from './components/table_basic';
import { Args, ColumnConfigArg } from './expression';
import type {
Datatable,
DatatableColumn,
DatatableRow,
} from '../../../../../../src/plugins/expressions';
import type { FieldFormat } from '../../../../../../src/plugins/data/common';
import type { DatatableArgs } from './datatable';
import type { ColumnConfig, ColumnConfigArg } from './datatable_column';
const TRANSPOSE_SEPARATOR = '---';
@ -42,7 +45,7 @@ export function getOriginalId(id: string) {
* @param formatters Formatters for all columns to transpose columns by actual display values
*/
export function transposeTable(
args: Args,
args: DatatableArgs,
firstTable: Datatable,
formatters: Record<string, FieldFormat>
) {
@ -112,7 +115,7 @@ function transposeRows(
* grouped by unique value
*/
function updateColumnArgs(
args: Args,
args: DatatableArgs,
bucketsColumnArgs: ColumnConfig['columns'],
transposedColumnGroups: Array<ColumnConfig['columns']>
) {
@ -150,7 +153,7 @@ function getUniqueValues(table: Datatable, formatter: FieldFormat, columnId: str
* @param uniqueValues
*/
function transposeColumns(
args: Args,
args: DatatableArgs,
bucketsColumnArgs: ColumnConfig['columns'],
metricColumns: ColumnConfig['columns'],
firstTable: Datatable,

View file

@ -5,14 +5,14 @@
* 2.0.
*/
import { Datatable } from 'src/plugins/expressions/public';
import type { Datatable } from '../../../../../../src/plugins/expressions/common';
import { getOriginalId } from './transpose_helpers';
function isValidNumber(value: unknown): boolean {
return typeof value === 'number' || value == null;
}
export function isNumericField(currentData: Datatable | undefined, accessor: string) {
export function isNumericFieldForDatatable(currentData: Datatable | undefined, accessor: string) {
const isNumeric =
currentData?.columns.find((col) => col.id === accessor || getOriginalId(col.id) === accessor)
?.meta.type === 'number';

View file

@ -7,7 +7,7 @@
import { Datatable, DatatableColumn } from 'src/plugins/expressions/public';
import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils';
import { FormatColumnArgs, formatColumn } from './format_column';
import { FormatColumnArgs, formatColumn } from './index';
describe('format_column', () => {
const fn: (input: Datatable, args: FormatColumnArgs) => Datatable = functionWrapper(formatColumn);

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import {
import type {
ExpressionFunctionDefinition,
Datatable,
DatatableColumn,
} from 'src/plugins/expressions/public';
} from '../../../../../../src/plugins/expressions/common';
export interface FormatColumnArgs {
format: string;

View file

@ -0,0 +1,122 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import type { PaletteOutput } from '../../../../../../src/plugins/charts/common';
import type { LensMultiTable, CustomPaletteParams } from '../../types';
import { HeatmapGridConfigResult, HEATMAP_GRID_FUNCTION } from './heatmap_grid';
import { HeatmapLegendConfigResult, HEATMAP_LEGEND_FUNCTION } from './heatmap_legend';
export const HEATMAP_FUNCTION = 'lens_heatmap';
export const HEATMAP_FUNCTION_RENDERER = 'lens_heatmap_renderer';
export type ChartShapes = 'heatmap';
export interface SharedHeatmapLayerState {
shape: ChartShapes;
xAccessor?: string;
yAccessor?: string;
valueAccessor?: string;
legend: HeatmapLegendConfigResult;
gridConfig: HeatmapGridConfigResult;
}
export type HeatmapLayerState = SharedHeatmapLayerState & {
layerId: string;
};
export type HeatmapVisualizationState = HeatmapLayerState & {
// need to store the current accessor to reset the color stops at accessor change
palette?: PaletteOutput<CustomPaletteParams> & { accessor: string };
};
export type HeatmapExpressionArgs = SharedHeatmapLayerState & {
title?: string;
description?: string;
palette: PaletteOutput;
};
export interface HeatmapRender {
type: 'render';
as: typeof HEATMAP_FUNCTION_RENDERER;
value: HeatmapExpressionProps;
}
export interface HeatmapExpressionProps {
data: LensMultiTable;
args: HeatmapExpressionArgs;
}
export const heatmap: ExpressionFunctionDefinition<
typeof HEATMAP_FUNCTION,
LensMultiTable,
HeatmapExpressionArgs,
HeatmapRender
> = {
name: HEATMAP_FUNCTION,
type: 'render',
help: i18n.translate('xpack.lens.heatmap.expressionHelpLabel', {
defaultMessage: 'Heatmap renderer',
}),
args: {
title: {
types: ['string'],
help: i18n.translate('xpack.lens.heatmap.titleLabel', {
defaultMessage: 'Title',
}),
},
description: {
types: ['string'],
help: '',
},
xAccessor: {
types: ['string'],
help: '',
},
yAccessor: {
types: ['string'],
help: '',
},
valueAccessor: {
types: ['string'],
help: '',
},
shape: {
types: ['string'],
help: '',
},
palette: {
default: `{theme "palette" default={system_palette name="default"} }`,
help: '',
types: ['palette'],
},
legend: {
types: [HEATMAP_LEGEND_FUNCTION],
help: i18n.translate('xpack.lens.heatmapChart.legend.help', {
defaultMessage: 'Configure the chart legend.',
}),
},
gridConfig: {
types: [HEATMAP_GRID_FUNCTION],
help: i18n.translate('xpack.lens.heatmapChart.gridConfig.help', {
defaultMessage: 'Configure the heatmap layout.',
}),
},
},
inputTypes: ['lens_multitable'],
fn(data: LensMultiTable, args: HeatmapExpressionArgs) {
return {
type: 'render',
as: HEATMAP_FUNCTION_RENDERER,
value: {
data,
args,
},
};
},
};

View file

@ -0,0 +1,114 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
export const HEATMAP_GRID_FUNCTION = 'lens_heatmap_grid';
export interface HeatmapGridConfig {
// grid
strokeWidth?: number;
strokeColor?: string;
cellHeight?: number;
cellWidth?: number;
// cells
isCellLabelVisible: boolean;
// Y-axis
isYAxisLabelVisible: boolean;
yAxisLabelWidth?: number;
yAxisLabelColor?: string;
// X-axis
isXAxisLabelVisible: boolean;
}
export type HeatmapGridConfigResult = HeatmapGridConfig & { type: typeof HEATMAP_GRID_FUNCTION };
export const heatmapGridConfig: ExpressionFunctionDefinition<
typeof HEATMAP_GRID_FUNCTION,
null,
HeatmapGridConfig,
HeatmapGridConfigResult
> = {
name: HEATMAP_GRID_FUNCTION,
aliases: [],
type: HEATMAP_GRID_FUNCTION,
help: `Configure the heatmap layout `,
inputTypes: ['null'],
args: {
// grid
strokeWidth: {
types: ['number'],
help: i18n.translate('xpack.lens.heatmapChart.config.strokeWidth.help', {
defaultMessage: 'Specifies the grid stroke width',
}),
required: false,
},
strokeColor: {
types: ['string'],
help: i18n.translate('xpack.lens.heatmapChart.config.strokeColor.help', {
defaultMessage: 'Specifies the grid stroke color',
}),
required: false,
},
cellHeight: {
types: ['number'],
help: i18n.translate('xpack.lens.heatmapChart.config.cellHeight.help', {
defaultMessage: 'Specifies the grid cell height',
}),
required: false,
},
cellWidth: {
types: ['number'],
help: i18n.translate('xpack.lens.heatmapChart.config.cellWidth.help', {
defaultMessage: 'Specifies the grid cell width',
}),
required: false,
},
// cells
isCellLabelVisible: {
types: ['boolean'],
help: i18n.translate('xpack.lens.heatmapChart.config.isCellLabelVisible.help', {
defaultMessage: 'Specifies whether or not the cell label is visible.',
}),
},
// Y-axis
isYAxisLabelVisible: {
types: ['boolean'],
help: i18n.translate('xpack.lens.heatmapChart.config.isYAxisLabelVisible.help', {
defaultMessage: 'Specifies whether or not the Y-axis labels are visible.',
}),
},
yAxisLabelWidth: {
types: ['number'],
help: i18n.translate('xpack.lens.heatmapChart.config.yAxisLabelWidth.help', {
defaultMessage: 'Specifies the width of the Y-axis labels.',
}),
required: false,
},
yAxisLabelColor: {
types: ['string'],
help: i18n.translate('xpack.lens.heatmapChart.config.yAxisLabelColor.help', {
defaultMessage: 'Specifies the color of the Y-axis labels.',
}),
required: false,
},
// X-axis
isXAxisLabelVisible: {
types: ['boolean'],
help: i18n.translate('xpack.lens.heatmapChart.config.isXAxisLabelVisible.help', {
defaultMessage: 'Specifies whether or not the X-axis labels are visible.',
}),
},
},
fn(input, args) {
return {
type: HEATMAP_GRID_FUNCTION,
...args,
};
},
};

View file

@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Position } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
export const HEATMAP_LEGEND_FUNCTION = 'lens_heatmap_legendConfig';
export interface HeatmapLegendConfig {
/**
* Flag whether the legend should be shown. If there is just a single series, it will be hidden
*/
isVisible: boolean;
/**
* Position of the legend relative to the chart
*/
position: Position;
}
export type HeatmapLegendConfigResult = HeatmapLegendConfig & {
type: typeof HEATMAP_LEGEND_FUNCTION;
};
/**
* TODO check if it's possible to make a shared function
* based on the XY chart
*/
export const heatmapLegendConfig: ExpressionFunctionDefinition<
typeof HEATMAP_LEGEND_FUNCTION,
null,
HeatmapLegendConfig,
HeatmapLegendConfigResult
> = {
name: HEATMAP_LEGEND_FUNCTION,
aliases: [],
type: HEATMAP_LEGEND_FUNCTION,
help: `Configure the heatmap chart's legend`,
inputTypes: ['null'],
args: {
isVisible: {
types: ['boolean'],
help: i18n.translate('xpack.lens.heatmapChart.legend.isVisible.help', {
defaultMessage: 'Specifies whether or not the legend is visible.',
}),
},
position: {
types: ['string'],
options: [Position.Top, Position.Right, Position.Bottom, Position.Left],
help: i18n.translate('xpack.lens.heatmapChart.legend.position.help', {
defaultMessage: 'Specifies the legend position.',
}),
},
},
fn(input, args) {
return {
type: HEATMAP_LEGEND_FUNCTION,
...args,
};
},
};

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './heatmap_grid';
export * from './heatmap_legend';
export * from './heatmap_chart';

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './counter_rate';
export * from './format_column';
export * from './rename_columns';
export * from './merge_tables';
export * from './time_scale';
export * from './datatable';
export * from './heatmap_chart';
export * from './metric_chart';
export * from './pie_chart';
export * from './xy_chart';

View file

@ -6,16 +6,16 @@
*/
import { i18n } from '@kbn/i18n';
import {
ExecutionContext,
Datatable,
import type {
ExpressionFunctionDefinition,
} from 'src/plugins/expressions/public';
import { ExpressionValueSearchContext, search } from '../../../../../src/plugins/data/public';
const { toAbsoluteDates } = search.aggs;
Datatable,
ExecutionContext,
} from '../../../../../../src/plugins/expressions/common';
import { toAbsoluteDates } from '../../../../../../src/plugins/data/common';
import type { ExpressionValueSearchContext } from '../../../../../../src/plugins/data/common';
import { LensMultiTable } from '../types';
import { Adapters } from '../../../../../src/plugins/inspector/common';
import type { LensMultiTable } from '../../types';
import { Adapters } from '../../../../../../src/plugins/inspector/common';
interface MergeTables {
layerIds: string[];

View file

@ -6,7 +6,7 @@
*/
import moment from 'moment';
import { mergeTables } from './merge_tables';
import { mergeTables } from './index';
import { ExpressionValueSearchContext } from 'src/plugins/data/public';
import {
Datatable,

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './types';
export * from './metric_chart';

View file

@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import type { LensMultiTable } from '../../types';
import type { MetricConfig } from './types';
export interface MetricChartProps {
data: LensMultiTable;
args: MetricConfig;
}
export interface MetricRender {
type: 'render';
as: 'lens_metric_chart_renderer';
value: MetricChartProps;
}
export const metricChart: ExpressionFunctionDefinition<
'lens_metric_chart',
LensMultiTable,
Omit<MetricConfig, 'layerId'>,
MetricRender
> = {
name: 'lens_metric_chart',
type: 'render',
help: 'A metric chart',
args: {
title: {
types: ['string'],
help: 'The chart title.',
},
description: {
types: ['string'],
help: '',
},
metricTitle: {
types: ['string'],
help: 'The title of the metric shown.',
},
accessor: {
types: ['string'],
help: 'The column whose value is being displayed',
},
mode: {
types: ['string'],
options: ['reduced', 'full'],
default: 'full',
help:
'The display mode of the chart - reduced will only show the metric itself without min size',
},
},
inputTypes: ['lens_multitable'],
fn(data, args) {
return {
type: 'render',
as: 'lens_metric_chart_renderer',
value: {
data,
args,
},
} as MetricRender;
},
};

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './types';
export * from './pie_chart';

View file

@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Position } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import type { LensMultiTable } from '../../types';
import type { PieExpressionProps, PieExpressionArgs } from './types';
export interface PieRender {
type: 'render';
as: 'lens_pie_renderer';
value: PieExpressionProps;
}
export const pie: ExpressionFunctionDefinition<
'lens_pie',
LensMultiTable,
PieExpressionArgs,
PieRender
> = {
name: 'lens_pie',
type: 'render',
help: i18n.translate('xpack.lens.pie.expressionHelpLabel', {
defaultMessage: 'Pie renderer',
}),
args: {
title: {
types: ['string'],
help: 'The chart title.',
},
description: {
types: ['string'],
help: '',
},
groups: {
types: ['string'],
multi: true,
help: '',
},
metric: {
types: ['string'],
help: '',
},
shape: {
types: ['string'],
options: ['pie', 'donut', 'treemap'],
help: '',
},
hideLabels: {
types: ['boolean'],
help: '',
},
numberDisplay: {
types: ['string'],
options: ['hidden', 'percent', 'value'],
help: '',
},
categoryDisplay: {
types: ['string'],
options: ['default', 'inside', 'hide'],
help: '',
},
legendDisplay: {
types: ['string'],
options: ['default', 'show', 'hide'],
help: '',
},
nestedLegend: {
types: ['boolean'],
help: '',
},
legendPosition: {
types: ['string'],
options: [Position.Top, Position.Right, Position.Bottom, Position.Left],
help: '',
},
percentDecimals: {
types: ['number'],
help: '',
},
palette: {
default: `{theme "palette" default={system_palette name="default"} }`,
help: '',
types: ['palette'],
},
},
inputTypes: ['lens_multitable'],
fn(data: LensMultiTable, args: PieExpressionArgs) {
return {
type: 'render',
as: 'lens_pie_renderer',
value: {
data,
args,
},
};
},
};

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { PaletteOutput } from 'src/plugins/charts/public';
import { LensMultiTable } from '../types';
import type { PaletteOutput } from '../../../../../../src/plugins/charts/common';
import type { LensMultiTable } from '../../types';
export interface SharedPieLayerState {
groups: string[];

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './rename_columns';

View file

@ -6,8 +6,8 @@
*/
import { renameColumns } from './rename_columns';
import { Datatable } from '../../../../../src/plugins/expressions/public';
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
import { Datatable } from '../../../../../../src/plugins/expressions/common';
import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks';
describe('rename_columns', () => {
it('should rename columns of a given datatable', () => {

View file

@ -6,14 +6,20 @@
*/
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, Datatable, DatatableColumn } from 'src/plugins/expressions';
import { IndexPatternColumn } from './operations';
import {
ExpressionFunctionDefinition,
Datatable,
DatatableColumn,
} from '../../../../../../src/plugins/expressions/common';
interface RemapArgs {
idMap: string;
}
export type OriginalColumn = { id: string } & IndexPatternColumn;
type OriginalColumn = { id: string; label: string } & (
| { operationType: 'date_histogram'; sourceField: string }
| { operationType: string; sourceField: never }
);
export const renameColumns: ExpressionFunctionDefinition<
'lens_rename_columns',
@ -75,7 +81,7 @@ export const renameColumns: ExpressionFunctionDefinition<
};
function getColumnName(originalColumn: OriginalColumn, newColumn: DatatableColumn) {
if (originalColumn && originalColumn.operationType === 'date_histogram') {
if (originalColumn?.operationType === 'date_histogram') {
const fieldName = originalColumn.sourceField;
// HACK: This is a hack, and introduces some fragility into

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './time_scale';
export * from './types';

View file

@ -7,14 +7,25 @@
import moment from 'moment';
import { Datatable } from 'src/plugins/expressions/public';
import { DataPublicPluginStart, TimeRange } from 'src/plugins/data/public';
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
import { TimeRange } from 'src/plugins/data/public';
import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils';
import { getTimeScaleFunction, TimeScaleArgs } from './time_scale';
// mock the specific inner variable:
// there are intra dependencies in the data plugin we might break trying to mock the whole thing
jest.mock('../../../../../../src/plugins/data/common/query/timefilter/get_time', () => {
const localMoment = jest.requireActual('moment');
return {
calculateBounds: jest.fn(({ from, to }) => ({
min: localMoment(from),
max: localMoment(to),
})),
};
});
import { timeScale, TimeScaleArgs } from './time_scale';
describe('time_scale', () => {
let timeScale: (input: Datatable, args: TimeScaleArgs) => Promise<Datatable>;
let dataMock: jest.Mocked<DataPublicPluginStart>;
let timeScaleWrapped: (input: Datatable, args: TimeScaleArgs) => Promise<Datatable>;
const emptyTable: Datatable = {
type: 'datatable',
@ -61,7 +72,6 @@ describe('time_scale', () => {
}
beforeEach(() => {
dataMock = dataPluginMock.createStartContract();
setDateHistogramMeta({
timeZone: 'UTC',
timeRange: {
@ -70,17 +80,11 @@ describe('time_scale', () => {
},
interval: '1d',
});
(dataMock.query.timefilter.timefilter.calculateBounds as jest.Mock).mockImplementation(
({ from, to }) => ({
min: moment(from),
max: moment(to),
})
);
timeScale = functionWrapper(getTimeScaleFunction(dataMock));
timeScaleWrapped = functionWrapper(timeScale);
});
it('should apply time scale factor to each row', async () => {
const result = await timeScale(
const result = await timeScaleWrapped(
{
...emptyTable,
rows: [
@ -115,7 +119,7 @@ describe('time_scale', () => {
});
it('should skip gaps in the data', async () => {
const result = await timeScale(
const result = await timeScaleWrapped(
{
...emptyTable,
rows: [
@ -163,7 +167,7 @@ describe('time_scale', () => {
},
],
};
const result = await timeScale(mismatchedTable, {
const result = await timeScaleWrapped(mismatchedTable, {
...defaultArgs,
inputColumnId: 'nonexistent',
});
@ -180,7 +184,7 @@ describe('time_scale', () => {
},
interval: '1h',
});
const result = await timeScale(
const result = await timeScaleWrapped(
{
...emptyTable,
rows: [
@ -220,7 +224,7 @@ describe('time_scale', () => {
},
interval: '3h',
});
const result = await timeScale(
const result = await timeScaleWrapped(
{
...emptyTable,
rows: [
@ -262,7 +266,7 @@ describe('time_scale', () => {
},
interval: '1d',
});
const result = await timeScale(
const result = await timeScaleWrapped(
{
...emptyTable,
rows: [
@ -307,7 +311,7 @@ describe('time_scale', () => {
},
interval: '1d',
});
const result = await timeScale(
const result = await timeScaleWrapped(
{
...emptyTable,
rows: [
@ -347,7 +351,7 @@ describe('time_scale', () => {
},
interval: '1y',
});
const result = await timeScale(
const result = await timeScaleWrapped(
{
...emptyTable,
rows: [

View file

@ -0,0 +1,151 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment-timezone';
import { i18n } from '@kbn/i18n';
import type {
ExpressionFunctionDefinition,
Datatable,
} from '../../../../../../src/plugins/expressions/common';
import {
getDateHistogramMetaDataByDatatableColumn,
parseInterval,
calculateBounds,
} from '../../../../../../src/plugins/data/common';
import { buildResultColumns } from '../../../../../../src/plugins/expressions/common';
import type { TimeScaleUnit } from './types';
export interface TimeScaleArgs {
dateColumnId: string;
inputColumnId: string;
outputColumnId: string;
targetUnit: TimeScaleUnit;
outputColumnName?: string;
}
const unitInMs: Record<TimeScaleUnit, number> = {
s: 1000,
m: 1000 * 60,
h: 1000 * 60 * 60,
d: 1000 * 60 * 60 * 24,
};
export const timeScale: ExpressionFunctionDefinition<
'lens_time_scale',
Datatable,
TimeScaleArgs,
Promise<Datatable>
> = {
name: 'lens_time_scale',
type: 'datatable',
help: '',
args: {
dateColumnId: {
types: ['string'],
help: '',
required: true,
},
inputColumnId: {
types: ['string'],
help: '',
required: true,
},
outputColumnId: {
types: ['string'],
help: '',
required: true,
},
outputColumnName: {
types: ['string'],
help: '',
},
targetUnit: {
types: ['string'],
options: ['s', 'm', 'h', 'd'],
help: '',
required: true,
},
},
inputTypes: ['datatable'],
async fn(
input,
{ dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs
) {
const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId);
if (!dateColumnDefinition) {
throw new Error(
i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', {
defaultMessage: 'Specified dateColumnId {columnId} does not exist.',
values: {
columnId: dateColumnId,
},
})
);
}
const resultColumns = buildResultColumns(
input,
outputColumnId,
inputColumnId,
outputColumnName,
{ allowColumnOverwrite: true }
);
if (!resultColumns) {
return input;
}
const targetUnitInMs = unitInMs[targetUnit];
const timeInfo = getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition);
const intervalDuration = timeInfo?.interval && parseInterval(timeInfo.interval);
if (!timeInfo || !intervalDuration) {
throw new Error(
i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', {
defaultMessage: 'Could not fetch date histogram information',
})
);
}
// the datemath plugin always parses dates by using the current default moment time zone.
// to use the configured time zone, we are switching just for the bounds calculation.
const defaultTimezone = moment().zoneName();
moment.tz.setDefault(timeInfo.timeZone);
const timeBounds = timeInfo.timeRange && calculateBounds(timeInfo.timeRange);
const result = {
...input,
columns: resultColumns,
rows: input.rows.map((row) => {
const newRow = { ...row };
let startOfBucket = moment(row[dateColumnId]);
let endOfBucket = startOfBucket.clone().add(intervalDuration);
if (timeBounds && timeBounds.min) {
startOfBucket = moment.max(startOfBucket, timeBounds.min);
}
if (timeBounds && timeBounds.max) {
endOfBucket = moment.min(endOfBucket, timeBounds.max);
}
const bucketSize = endOfBucket.diff(startOfBucket);
const factor = bucketSize / targetUnitInMs;
const currentValue = newRow[inputColumnId];
if (currentValue != null) {
newRow[outputColumnId] = Number(currentValue) / factor;
}
return newRow;
}),
};
// reset default moment timezone
moment.tz.setDefault(defaultTimezone);
return result;
},
};

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export type TimeScaleUnit = 's' | 'm' | 'h' | 'd';

View file

@ -0,0 +1,171 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type {
ArgumentType,
ExpressionFunctionDefinition,
} from '../../../../../../src/plugins/expressions/common';
export interface AxesSettingsConfig {
x: boolean;
yLeft: boolean;
yRight: boolean;
}
export interface AxisExtentConfig {
mode: 'full' | 'dataBounds' | 'custom';
lowerBound?: number;
upperBound?: number;
}
interface AxisConfig {
title: string;
hide?: boolean;
}
export type YAxisMode = 'auto' | 'left' | 'right';
export interface YConfig {
forAccessor: string;
axisMode?: YAxisMode;
color?: string;
}
export type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & {
type: 'lens_xy_axisTitlesVisibilityConfig';
};
export const axisTitlesVisibilityConfig: ExpressionFunctionDefinition<
'lens_xy_axisTitlesVisibilityConfig',
null,
AxesSettingsConfig,
AxisTitlesVisibilityConfigResult
> = {
name: 'lens_xy_axisTitlesVisibilityConfig',
aliases: [],
type: 'lens_xy_axisTitlesVisibilityConfig',
help: `Configure the xy chart's axis titles appearance`,
inputTypes: ['null'],
args: {
x: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.xAxisTitle.help', {
defaultMessage: 'Specifies whether or not the title of the x-axis are visible.',
}),
},
yLeft: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.yLeftAxisTitle.help', {
defaultMessage: 'Specifies whether or not the title of the left y-axis are visible.',
}),
},
yRight: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.yRightAxisTitle.help', {
defaultMessage: 'Specifies whether or not the title of the right y-axis are visible.',
}),
},
},
fn: function fn(input: unknown, args: AxesSettingsConfig) {
return {
type: 'lens_xy_axisTitlesVisibilityConfig',
...args,
};
},
};
export type AxisExtentConfigResult = AxisExtentConfig & { type: 'lens_xy_axisExtentConfig' };
export const axisExtentConfig: ExpressionFunctionDefinition<
'lens_xy_axisExtentConfig',
null,
AxisExtentConfig,
AxisExtentConfigResult
> = {
name: 'lens_xy_axisExtentConfig',
aliases: [],
type: 'lens_xy_axisExtentConfig',
help: `Configure the xy chart's axis extents`,
inputTypes: ['null'],
args: {
mode: {
types: ['string'],
options: ['full', 'dataBounds', 'custom'],
help: i18n.translate('xpack.lens.xyChart.extentMode.help', {
defaultMessage: 'The extent mode',
}),
},
lowerBound: {
types: ['number'],
help: i18n.translate('xpack.lens.xyChart.extentMode.help', {
defaultMessage: 'The extent mode',
}),
},
upperBound: {
types: ['number'],
help: i18n.translate('xpack.lens.xyChart.extentMode.help', {
defaultMessage: 'The extent mode',
}),
},
},
fn: function fn(input: unknown, args: AxisExtentConfig) {
return {
type: 'lens_xy_axisExtentConfig',
...args,
};
},
};
export const axisConfig: { [key in keyof AxisConfig]: ArgumentType<AxisConfig[key]> } = {
title: {
types: ['string'],
help: i18n.translate('xpack.lens.xyChart.title.help', {
defaultMessage: 'The axis title',
}),
},
hide: {
types: ['boolean'],
default: false,
help: 'Show / hide axis',
},
};
export type YConfigResult = YConfig & { type: 'lens_xy_yConfig' };
export const yAxisConfig: ExpressionFunctionDefinition<
'lens_xy_yConfig',
null,
YConfig,
YConfigResult
> = {
name: 'lens_xy_yConfig',
aliases: [],
type: 'lens_xy_yConfig',
help: `Configure the behavior of a xy chart's y axis metric`,
inputTypes: ['null'],
args: {
forAccessor: {
types: ['string'],
help: 'The accessor this configuration is for',
},
axisMode: {
types: ['string'],
options: ['auto', 'left', 'right'],
help: 'The axis mode of the metric',
},
color: {
types: ['string'],
help: 'The color of the series',
},
},
fn: function fn(input: unknown, args: YConfig) {
return {
type: 'lens_xy_yConfig',
...args,
};
},
};

View file

@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export type FittingFunction = typeof fittingFunctionDefinitions[number]['id'];
export const fittingFunctionDefinitions = [
{
id: 'None',
title: i18n.translate('xpack.lens.fittingFunctionsTitle.none', {
defaultMessage: 'Hide',
}),
description: i18n.translate('xpack.lens.fittingFunctionsDescription.none', {
defaultMessage: 'Do not fill gaps',
}),
},
{
id: 'Zero',
title: i18n.translate('xpack.lens.fittingFunctionsTitle.zero', {
defaultMessage: 'Zero',
}),
description: i18n.translate('xpack.lens.fittingFunctionsDescription.zero', {
defaultMessage: 'Fill gaps with zeros',
}),
},
{
id: 'Linear',
title: i18n.translate('xpack.lens.fittingFunctionsTitle.linear', {
defaultMessage: 'Linear',
}),
description: i18n.translate('xpack.lens.fittingFunctionsDescription.linear', {
defaultMessage: 'Fill gaps with a line',
}),
},
{
id: 'Carry',
title: i18n.translate('xpack.lens.fittingFunctionsTitle.carry', {
defaultMessage: 'Last',
}),
description: i18n.translate('xpack.lens.fittingFunctionsDescription.carry', {
defaultMessage: 'Fill gaps with the last value',
}),
},
{
id: 'Lookahead',
title: i18n.translate('xpack.lens.fittingFunctionsTitle.lookahead', {
defaultMessage: 'Next',
}),
description: i18n.translate('xpack.lens.fittingFunctionsDescription.lookahead', {
defaultMessage: 'Fill gaps with the next value',
}),
},
] as const;

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import type { AxesSettingsConfig } from './axis_config';
export type GridlinesConfigResult = AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' };
export const gridlinesConfig: ExpressionFunctionDefinition<
'lens_xy_gridlinesConfig',
null,
AxesSettingsConfig,
GridlinesConfigResult
> = {
name: 'lens_xy_gridlinesConfig',
aliases: [],
type: 'lens_xy_gridlinesConfig',
help: `Configure the xy chart's gridlines appearance`,
inputTypes: ['null'],
args: {
x: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.xAxisGridlines.help', {
defaultMessage: 'Specifies whether or not the gridlines of the x-axis are visible.',
}),
},
yLeft: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.yLeftAxisgridlines.help', {
defaultMessage: 'Specifies whether or not the gridlines of the left y-axis are visible.',
}),
},
yRight: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.yRightAxisgridlines.help', {
defaultMessage: 'Specifies whether or not the gridlines of the right y-axis are visible.',
}),
},
},
fn: function fn(input: unknown, args: AxesSettingsConfig) {
return {
type: 'lens_xy_gridlinesConfig',
...args,
};
},
};

View file

@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './axis_config';
export * from './fitting_function';
export * from './grid_lines_config';
export * from './layer_config';
export * from './legend_config';
export * from './series_type';
export * from './tick_labels_config';
export * from './xy_args';
export * from './xy_chart';

View file

@ -0,0 +1,120 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { PaletteOutput } from '../../../../../../src/plugins/charts/common';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import { axisConfig, YConfig } from './axis_config';
import type { SeriesType } from './series_type';
export interface XYLayerConfig {
hide?: boolean;
layerId: string;
xAccessor?: string;
accessors: string[];
yConfig?: YConfig[];
seriesType: SeriesType;
splitAccessor?: string;
palette?: PaletteOutput;
}
export interface ValidLayer extends XYLayerConfig {
xAccessor: NonNullable<XYLayerConfig['xAccessor']>;
}
export type LayerArgs = XYLayerConfig & {
columnToLabel?: string; // Actually a JSON key-value pair
yScaleType: 'time' | 'linear' | 'log' | 'sqrt';
xScaleType: 'time' | 'linear' | 'ordinal';
isHistogram: boolean;
// palette will always be set on the expression
palette: PaletteOutput;
};
export type LayerConfigResult = LayerArgs & { type: 'lens_xy_layer' };
export const layerConfig: ExpressionFunctionDefinition<
'lens_xy_layer',
null,
LayerArgs,
LayerConfigResult
> = {
name: 'lens_xy_layer',
aliases: [],
type: 'lens_xy_layer',
help: `Configure a layer in the xy chart`,
inputTypes: ['null'],
args: {
...axisConfig,
layerId: {
types: ['string'],
help: '',
},
xAccessor: {
types: ['string'],
help: '',
},
seriesType: {
types: ['string'],
options: [
'bar',
'line',
'area',
'bar_stacked',
'area_stacked',
'bar_percentage_stacked',
'area_percentage_stacked',
],
help: 'The type of chart to display.',
},
xScaleType: {
options: ['ordinal', 'linear', 'time'],
help: 'The scale type of the x axis',
default: 'ordinal',
},
isHistogram: {
types: ['boolean'],
default: false,
help: 'Whether to layout the chart as a histogram',
},
yScaleType: {
options: ['log', 'sqrt', 'linear', 'time'],
help: 'The scale type of the y axes',
default: 'linear',
},
splitAccessor: {
types: ['string'],
help: 'The column to split by',
multi: false,
},
accessors: {
types: ['string'],
help: 'The columns to display on the y axis.',
multi: true,
},
yConfig: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
types: ['lens_xy_yConfig' as any],
help: 'Additional configuration for y axes',
multi: true,
},
columnToLabel: {
types: ['string'],
help: 'JSON key-value pairs of column ID to label',
},
palette: {
default: `{theme "palette" default={system_palette name="default"} }`,
help: '',
types: ['palette'],
},
},
fn: function fn(input: unknown, args: LayerArgs) {
return {
type: 'lens_xy_layer',
...args,
};
},
};

View file

@ -0,0 +1,110 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
export interface LegendConfig {
/**
* Flag whether the legend should be shown. If there is just a single series, it will be hidden
*/
isVisible: boolean;
/**
* Position of the legend relative to the chart
*/
position: Position;
/**
* Flag whether the legend should be shown even with just a single series
*/
showSingleSeries?: boolean;
/**
* Flag whether the legend is inside the chart
*/
isInside?: boolean;
/**
* Horizontal Alignment of the legend when it is set inside chart
*/
horizontalAlignment?: HorizontalAlignment;
/**
* Vertical Alignment of the legend when it is set inside chart
*/
verticalAlignment?: VerticalAlignment;
/**
* Number of columns when legend is set inside chart
*/
floatingColumns?: number;
}
export type LegendConfigResult = LegendConfig & { type: 'lens_xy_legendConfig' };
export const legendConfig: ExpressionFunctionDefinition<
'lens_xy_legendConfig',
null,
LegendConfig,
LegendConfigResult
> = {
name: 'lens_xy_legendConfig',
aliases: [],
type: 'lens_xy_legendConfig',
help: `Configure the xy chart's legend`,
inputTypes: ['null'],
args: {
isVisible: {
types: ['boolean'],
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: i18n.translate('xpack.lens.xyChart.position.help', {
defaultMessage: 'Specifies the legend position.',
}),
},
showSingleSeries: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.showSingleSeries.help', {
defaultMessage: 'Specifies whether a legend with just a single entry should be shown',
}),
},
isInside: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.isInside.help', {
defaultMessage: 'Specifies whether a legend is inside the chart',
}),
},
horizontalAlignment: {
types: ['string'],
options: [HorizontalAlignment.Right, HorizontalAlignment.Left],
help: i18n.translate('xpack.lens.xyChart.horizontalAlignment.help', {
defaultMessage:
'Specifies the horizontal alignment of the legend when it is displayed inside chart.',
}),
},
verticalAlignment: {
types: ['string'],
options: [VerticalAlignment.Top, VerticalAlignment.Bottom],
help: i18n.translate('xpack.lens.xyChart.verticalAlignment.help', {
defaultMessage:
'Specifies the vertical alignment of the legend when it is displayed inside chart.',
}),
},
floatingColumns: {
types: ['number'],
help: i18n.translate('xpack.lens.xyChart.floatingColumns.help', {
defaultMessage: 'Specifies the number of columns when legend is displayed inside chart.',
}),
},
},
fn: function fn(input: unknown, args: LegendConfig) {
return {
type: 'lens_xy_legendConfig',
...args,
};
},
};

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export type SeriesType =
| 'bar'
| 'bar_horizontal'
| 'line'
| 'area'
| 'bar_stacked'
| 'bar_percentage_stacked'
| 'bar_horizontal_stacked'
| 'bar_horizontal_percentage_stacked'
| 'area_stacked'
| 'area_percentage_stacked';

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import type { AxesSettingsConfig } from './axis_config';
export type TickLabelsConfigResult = AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' };
export const tickLabelsConfig: ExpressionFunctionDefinition<
'lens_xy_tickLabelsConfig',
null,
AxesSettingsConfig,
TickLabelsConfigResult
> = {
name: 'lens_xy_tickLabelsConfig',
aliases: [],
type: 'lens_xy_tickLabelsConfig',
help: `Configure the xy chart's tick labels appearance`,
inputTypes: ['null'],
args: {
x: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.xAxisTickLabels.help', {
defaultMessage: 'Specifies whether or not the tick labels of the x-axis are visible.',
}),
},
yLeft: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.yLeftAxisTickLabels.help', {
defaultMessage: 'Specifies whether or not the tick labels of the left y-axis are visible.',
}),
},
yRight: {
types: ['boolean'],
help: i18n.translate('xpack.lens.xyChart.yRightAxisTickLabels.help', {
defaultMessage: 'Specifies whether or not the tick labels of the right y-axis are visible.',
}),
},
},
fn: function fn(input: unknown, args: AxesSettingsConfig) {
return {
type: 'lens_xy_tickLabelsConfig',
...args,
};
},
};

View file

@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { AxisExtentConfigResult, AxisTitlesVisibilityConfigResult } from './axis_config';
import type { FittingFunction } from './fitting_function';
import type { GridlinesConfigResult } from './grid_lines_config';
import type { LayerArgs } from './layer_config';
import type { LegendConfigResult } from './legend_config';
import type { TickLabelsConfigResult } from './tick_labels_config';
export type ValueLabelConfig = 'hide' | 'inside' | 'outside';
export type XYCurveType = 'LINEAR' | 'CURVE_MONOTONE_X';
// Arguments to XY chart expression, with computed properties
export interface XYArgs {
title?: string;
description?: string;
xTitle: string;
yTitle: string;
yRightTitle: string;
yLeftExtent: AxisExtentConfigResult;
yRightExtent: AxisExtentConfigResult;
legend: LegendConfigResult;
valueLabels: ValueLabelConfig;
layers: LayerArgs[];
fittingFunction?: FittingFunction;
axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult;
tickLabelsVisibilitySettings?: TickLabelsConfigResult;
gridlinesVisibilitySettings?: GridlinesConfigResult;
curveType?: XYCurveType;
fillOpacity?: number;
hideEndzones?: boolean;
valuesInLegend?: boolean;
}

View file

@ -0,0 +1,156 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import type { ExpressionValueSearchContext } from '../../../../../../src/plugins/data/common';
import type { LensMultiTable } from '../../types';
import type { XYArgs } from './xy_args';
import { fittingFunctionDefinitions } from './fitting_function';
export interface XYChartProps {
data: LensMultiTable;
args: XYArgs;
}
export interface XYRender {
type: 'render';
as: 'lens_xy_chart_renderer';
value: XYChartProps;
}
export const xyChart: ExpressionFunctionDefinition<
'lens_xy_chart',
LensMultiTable | ExpressionValueSearchContext | null,
XYArgs,
XYRender
> = {
name: 'lens_xy_chart',
type: 'render',
inputTypes: ['lens_multitable', 'kibana_context', 'null'],
help: i18n.translate('xpack.lens.xyChart.help', {
defaultMessage: 'An X/Y chart',
}),
args: {
title: {
types: ['string'],
help: 'The chart title.',
},
description: {
types: ['string'],
help: '',
},
xTitle: {
types: ['string'],
help: i18n.translate('xpack.lens.xyChart.xTitle.help', {
defaultMessage: 'X axis title',
}),
},
yTitle: {
types: ['string'],
help: i18n.translate('xpack.lens.xyChart.yLeftTitle.help', {
defaultMessage: 'Y left axis title',
}),
},
yRightTitle: {
types: ['string'],
help: i18n.translate('xpack.lens.xyChart.yRightTitle.help', {
defaultMessage: 'Y right axis title',
}),
},
yLeftExtent: {
types: ['lens_xy_axisExtentConfig'],
help: i18n.translate('xpack.lens.xyChart.yLeftExtent.help', {
defaultMessage: 'Y left axis extents',
}),
},
yRightExtent: {
types: ['lens_xy_axisExtentConfig'],
help: i18n.translate('xpack.lens.xyChart.yRightExtent.help', {
defaultMessage: 'Y right axis extents',
}),
},
legend: {
types: ['lens_xy_legendConfig'],
help: i18n.translate('xpack.lens.xyChart.legend.help', {
defaultMessage: 'Configure the chart legend.',
}),
},
fittingFunction: {
types: ['string'],
options: [...fittingFunctionDefinitions.map(({ id }) => id)],
help: i18n.translate('xpack.lens.xyChart.fittingFunction.help', {
defaultMessage: 'Define how missing values are treated',
}),
},
valueLabels: {
types: ['string'],
options: ['hide', 'inside'],
help: '',
},
tickLabelsVisibilitySettings: {
types: ['lens_xy_tickLabelsConfig'],
help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', {
defaultMessage: 'Show x and y axes tick labels',
}),
},
gridlinesVisibilitySettings: {
types: ['lens_xy_gridlinesConfig'],
help: i18n.translate('xpack.lens.xyChart.gridlinesSettings.help', {
defaultMessage: 'Show x and y axes gridlines',
}),
},
axisTitlesVisibilitySettings: {
types: ['lens_xy_axisTitlesVisibilityConfig'],
help: i18n.translate('xpack.lens.xyChart.axisTitlesSettings.help', {
defaultMessage: 'Show x and y axes titles',
}),
},
layers: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
types: ['lens_xy_layer'] as any,
help: 'Layers of visual series',
multi: true,
},
curveType: {
types: ['string'],
options: ['LINEAR', 'CURVE_MONOTONE_X'],
help: i18n.translate('xpack.lens.xyChart.curveType.help', {
defaultMessage: 'Define how curve type is rendered for a line chart',
}),
},
fillOpacity: {
types: ['number'],
help: i18n.translate('xpack.lens.xyChart.fillOpacity.help', {
defaultMessage: 'Define the area chart fill opacity',
}),
},
hideEndzones: {
types: ['boolean'],
default: false,
help: i18n.translate('xpack.lens.xyChart.hideEndzones.help', {
defaultMessage: 'Hide endzone markers for partial data',
}),
},
valuesInLegend: {
types: ['boolean'],
default: false,
help: i18n.translate('xpack.lens.xyChart.valuesInLegend.help', {
defaultMessage: 'Show values in legend',
}),
},
},
fn(data: LensMultiTable, args: XYArgs) {
return {
type: 'render',
as: 'lens_xy_chart_renderer',
value: {
data,
args,
},
};
},
};

View file

@ -8,3 +8,6 @@
export * from './api';
export * from './constants';
export * from './types';
// Note: do not import the expression folder here or the page bundle will be bloated with all
// the package

View file

@ -10,9 +10,9 @@ import {
FieldFormat,
FieldFormatInstanceType,
KBN_FIELD_TYPES,
} from '../../../../../src/plugins/data/public';
import { FormatFactory } from '../types';
import { TimeScaleUnit } from './time_scale';
} from '../../../../../src/plugins/data/common';
import type { FormatFactory } from '../types';
import type { TimeScaleUnit } from '../expressions/time_scale';
const unitSuffixes: Record<TimeScaleUnit, string> = {
s: i18n.translate('xpack.lens.fieldFormats.suffix.s', { defaultMessage: '/s' }),

View file

@ -6,7 +6,7 @@
*/
import { FormatFactory } from '../types';
import { getSuffixFormatter } from './suffix_formatter';
import { getSuffixFormatter } from './index';
describe('suffix formatter', () => {
it('should call nested formatter and apply suffix', () => {

View file

@ -5,7 +5,10 @@
* 2.0.
*/
import { FilterMeta, Filter } from 'src/plugins/data/common';
import type { FilterMeta, Filter, IFieldFormat } from '../../../../src/plugins/data/common';
import type { Datatable, SerializedFieldFormat } from '../../../../src/plugins/expressions/common';
export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
export interface ExistingFields {
indexPatternTitle: string;
@ -24,3 +27,32 @@ export interface PersistableFilterMeta extends FilterMeta {
export interface PersistableFilter extends Filter {
meta: PersistableFilterMeta;
}
export interface LensMultiTable {
type: 'lens_multitable';
tables: Record<string, Datatable>;
dateRange?: {
fromDate: Date;
toDate: Date;
};
}
export interface ColorStop {
color: string;
stop: number;
}
export interface CustomPaletteParams {
name?: string;
reverse?: boolean;
rangeType?: 'number' | 'percent';
continuity?: 'above' | 'below' | 'all' | 'none';
progression?: 'fixed';
rangeMin?: number;
rangeMax?: number;
stops?: ColorStop[];
colorStops?: ColorStop[];
steps?: number;
}
export type RequiredPaletteParamTypes = Required<CustomPaletteParams>;

View file

@ -14,7 +14,7 @@ import { Datatable } from 'src/plugins/expressions/public';
import { IUiSettingsClient } from 'kibana/public';
import { act } from 'react-dom/test-utils';
import { ReactWrapper } from 'enzyme';
import { Args, ColumnConfigArg } from '../expression';
import { DatatableArgs, ColumnConfigArg } from '../../../common/expressions';
import { DataContextType } from './types';
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
@ -91,7 +91,7 @@ describe('datatable cell renderer', () => {
const paletteRegistry = chartPluginMock.createPaletteRegistry();
const customPalette = paletteRegistry.get('custom');
function getCellRenderer(columnConfig: Args) {
function getCellRenderer(columnConfig: DatatableArgs) {
return createGridCell(
{
a: { convert: (x) => `formatted ${x}` } as FieldFormat,
@ -101,7 +101,7 @@ describe('datatable cell renderer', () => {
({ get: jest.fn() } as unknown) as IUiSettingsClient
);
}
function getColumnConfiguration(): Args {
function getColumnConfiguration(): DatatableArgs {
return {
title: 'myData',
columns: [
@ -136,7 +136,10 @@ describe('datatable cell renderer', () => {
});
}
async function renderCellComponent(columnConfig: Args, context: Partial<DataContextType> = {}) {
async function renderCellComponent(
columnConfig: DatatableArgs,
context: Partial<DataContextType> = {}
) {
const CellRendererWithPalette = getCellRenderer(columnConfig);
const setCellProps = jest.fn();

View file

@ -8,11 +8,11 @@
import React, { useContext, useEffect } from 'react';
import { EuiDataGridCellValueElementProps } from '@elastic/eui';
import { IUiSettingsClient } from 'kibana/public';
import type { FormatFactory } from '../../types';
import type { FormatFactory } from '../../../common';
import { getOriginalId } from '../../../common/expressions';
import type { ColumnConfig } from '../../../common/expressions';
import type { DataContextType } from './types';
import { ColumnConfig } from './table_basic';
import { getContrastColor, getNumericValue } from '../../shared_components/coloring/utils';
import { getOriginalId } from '../transpose_helpers';
export const createGridCell = (
formatters: Record<string, ReturnType<FormatFactory>>,

View file

@ -13,8 +13,8 @@ import {
EuiListGroupItemProps,
} from '@elastic/eui';
import type { Datatable, DatatableColumn, DatatableColumnMeta } from 'src/plugins/expressions';
import type { FormatFactory } from '../../types';
import { ColumnConfig } from './table_basic';
import type { FormatFactory } from '../../../common';
import type { ColumnConfig } from '../../../common/expressions';
export const createGridColumns = (
bucketColumns: string[],

View file

@ -21,8 +21,7 @@ import {
} from '@elastic/eui';
import { PaletteRegistry } from 'src/plugins/charts/public';
import { VisualizationDimensionEditorProps } from '../../types';
import { ColumnState, DatatableVisualizationState } from '../visualization';
import { getOriginalId } from '../transpose_helpers';
import { DatatableVisualizationState } from '../visualization';
import {
CustomizablePalette,
applyPaletteParams,
@ -33,13 +32,16 @@ import {
PalettePanelContainer,
findMinMaxByColumnId,
} from '../../shared_components/';
import './dimension_editor.scss';
import type { ColumnState } from '../../../common/expressions';
import {
isNumericFieldForDatatable,
getDefaultSummaryLabel,
getFinalSummaryConfiguration,
getSummaryRowOptions,
} from '../summary';
import { isNumericField } from '../utils';
getOriginalId,
} from '../../../common/expressions';
import './dimension_editor.scss';
const idPrefix = htmlIdGenerator()();
@ -93,7 +95,7 @@ export function TableDimensionEditor(
const currentData = frame.activeData?.[state.layerId];
// either read config state or use same logic as chart itself
const isNumeric = isNumericField(currentData, accessor);
const isNumeric = isNumericFieldForDatatable(currentData, accessor);
const currentAlignment = column?.alignment || (isNumeric ? 'right' : 'left');
const currentColorMode = column?.colorMode || 'none';
const hasDynamicColoring = currentColorMode !== 'none';

View file

@ -17,9 +17,8 @@ import {
createGridHideHandler,
createTransposeColumnFilterHandler,
} from './table_actions';
import { LensGridDirection } from './types';
import { ColumnConfig } from './table_basic';
import { LensMultiTable } from '../../types';
import { LensMultiTable } from '../../../common';
import { LensGridDirection, ColumnConfig } from '../../../common/expressions';
function getDefaultConfig(): ColumnConfig {
return {

View file

@ -7,15 +7,11 @@
import type { EuiDataGridSorting } from '@elastic/eui';
import type { Datatable, DatatableColumn } from 'src/plugins/expressions';
import type { LensFilterEvent, LensMultiTable } from '../../types';
import type {
LensGridDirection,
LensResizeAction,
LensSortAction,
LensToggleAction,
} from './types';
import { ColumnConfig } from './table_basic';
import { getOriginalId } from '../transpose_helpers';
import type { LensFilterEvent } from '../../types';
import type { LensMultiTable } from '../../../common';
import type { LensResizeAction, LensSortAction, LensToggleAction } from './types';
import type { ColumnConfig, LensGridDirection } from '../../../common/expressions';
import { getOriginalId } from '../../../common/expressions';
export const createGridResizeHandler = (
columnConfig: ColumnConfig,

View file

@ -15,8 +15,8 @@ import { VisualizationContainer } from '../../visualization_container';
import { EmptyPlaceholder } from '../../shared_components';
import { LensIconChartDatatable } from '../../assets/chart_datatable';
import { DataContext, DatatableComponent } from './table_basic';
import { LensMultiTable } from '../../types';
import { DatatableProps } from '../expression';
import { LensMultiTable } from '../../../common';
import { DatatableProps } from '../../../common/expressions';
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
import { IUiSettingsClient } from 'kibana/public';

View file

@ -18,18 +18,17 @@ import {
EuiDataGridSorting,
EuiDataGridStyle,
} from '@elastic/eui';
import { CustomPaletteState, PaletteOutput } from 'src/plugins/charts/common';
import { FormatFactory, LensFilterEvent, LensTableRowContextMenuEvent } from '../../types';
import type { LensFilterEvent, LensTableRowContextMenuEvent } from '../../types';
import type { FormatFactory } from '../../../common';
import { LensGridDirection } from '../../../common/expressions';
import { VisualizationContainer } from '../../visualization_container';
import { EmptyPlaceholder, findMinMaxByColumnId } from '../../shared_components';
import { LensIconChartDatatable } from '../../assets/chart_datatable';
import { ColumnState } from '../visualization';
import {
import type {
DataContextType,
DatatableRenderProps,
LensSortAction,
LensResizeAction,
LensGridDirection,
LensToggleAction,
} from './types';
import { createGridColumns } from './columns';
@ -42,8 +41,7 @@ import {
createTransposeColumnFilterHandler,
} from './table_actions';
import { CUSTOM_PALETTE } from '../../shared_components/coloring/constants';
import { getFinalSummaryConfiguration } from '../summary';
import { getOriginalId } from '../transpose_helpers';
import { getOriginalId, getFinalSummaryConfiguration } from '../../../common/expressions';
export const DataContext = React.createContext<DataContextType>({});
@ -52,17 +50,6 @@ const gridStyle: EuiDataGridStyle = {
header: 'underline',
};
export interface ColumnConfig {
columns: Array<
Omit<ColumnState, 'palette'> & {
type: 'lens_datatable_column';
palette?: PaletteOutput<CustomPaletteState>;
}
>;
sortingColumnId: string | undefined;
sortingDirection: LensGridDirection;
}
export const DatatableComponent = (props: DatatableRenderProps) => {
const [firstTable] = Object.values(props.data.tables);

View file

@ -5,16 +5,14 @@
* 2.0.
*/
import type { Direction } from '@elastic/eui';
import { IUiSettingsClient } from 'kibana/public';
import { CustomPaletteState, PaletteRegistry } from 'src/plugins/charts/public';
import type { IAggType } from 'src/plugins/data/public';
import type { Datatable, RenderMode } from 'src/plugins/expressions';
import type { FormatFactory, ILensInterpreterRenderHandlers, LensEditEvent } from '../../types';
import type { DatatableProps } from '../expression';
import type { ILensInterpreterRenderHandlers, LensEditEvent } from '../../types';
import { LENS_EDIT_SORT_ACTION, LENS_EDIT_RESIZE_ACTION, LENS_TOGGLE_ACTION } from './constants';
export type LensGridDirection = 'none' | Direction;
import type { FormatFactory } from '../../../common';
import type { DatatableProps, LensGridDirection } from '../../../common/expressions';
export interface LensSortActionData {
columnId: string | undefined;
@ -49,12 +47,6 @@ export type DatatableRenderProps = DatatableProps & {
rowHasRowClickTriggerActions?: boolean[];
};
export interface DatatableRender {
type: 'render';
as: 'lens_datatable_renderer';
value: DatatableProps;
}
export interface DataContextType {
table?: Datatable;
rowHasRowClickTriggerActions?: boolean[];

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import { DatatableProps, getDatatable } from './expression';
import { LensMultiTable } from '../types';
import { DatatableProps } from '../../common/expressions';
import type { LensMultiTable } from '../../common';
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
import { IFieldFormat } from '../../../../../src/plugins/data/public';
import { getDatatable } from './expression';
function sampleArgs() {
const indexPatternId = 'indexPatternId';

View file

@ -7,194 +7,20 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { cloneDeep } from 'lodash';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import type { IAggType } from 'src/plugins/data/public';
import {
DatatableColumnMeta,
ExpressionFunctionDefinition,
ExpressionRenderDefinition,
} from 'src/plugins/expressions';
import { CustomPaletteState, PaletteOutput } from 'src/plugins/charts/common';
import { PaletteRegistry } from 'src/plugins/charts/public';
import { IUiSettingsClient } from 'kibana/public';
import { getSortingCriteria } from './sorting';
import { ExpressionRenderDefinition } from 'src/plugins/expressions';
import { DatatableComponent } from './components/table_basic';
import { ColumnState } from './visualization';
import type { FormatFactory, ILensInterpreterRenderHandlers, LensMultiTable } from '../types';
import type { DatatableRender } from './components/types';
import { transposeTable } from './transpose_helpers';
import { computeSummaryRowForColumn } from './summary';
import type { ILensInterpreterRenderHandlers } from '../types';
import type { FormatFactory } from '../../common';
import { DatatableProps } from '../../common/expressions';
export type ColumnConfigArg = Omit<ColumnState, 'palette'> & {
type: 'lens_datatable_column';
palette?: PaletteOutput<CustomPaletteState>;
summaryRowValue?: unknown;
};
export interface Args {
title: string;
description?: string;
columns: ColumnConfigArg[];
sortingColumnId: string | undefined;
sortingDirection: 'asc' | 'desc' | 'none';
}
export interface DatatableProps {
data: LensMultiTable;
untransposedData?: LensMultiTable;
args: Args;
}
function isRange(meta: { params?: { id?: string } } | undefined) {
return meta?.params?.id === 'range';
}
export const getDatatable = ({
formatFactory,
}: {
formatFactory: FormatFactory;
}): ExpressionFunctionDefinition<'lens_datatable', LensMultiTable, Args, DatatableRender> => ({
name: 'lens_datatable',
type: 'render',
inputTypes: ['lens_multitable'],
help: i18n.translate('xpack.lens.datatable.expressionHelpLabel', {
defaultMessage: 'Datatable renderer',
}),
args: {
title: {
types: ['string'],
help: i18n.translate('xpack.lens.datatable.titleLabel', {
defaultMessage: 'Title',
}),
},
description: {
types: ['string'],
help: '',
},
columns: {
types: ['lens_datatable_column'],
help: '',
multi: true,
},
sortingColumnId: {
types: ['string'],
help: '',
},
sortingDirection: {
types: ['string'],
help: '',
},
},
fn(data, args, context) {
let untransposedData: LensMultiTable | undefined;
// do the sorting at this level to propagate it also at CSV download
const [firstTable] = Object.values(data.tables);
const [layerId] = Object.keys(context.inspectorAdapters.tables || {});
const formatters: Record<string, ReturnType<FormatFactory>> = {};
firstTable.columns.forEach((column) => {
formatters[column.id] = formatFactory(column.meta?.params);
});
const hasTransposedColumns = args.columns.some((c) => c.isTransposed);
if (hasTransposedColumns) {
// store original shape of data separately
untransposedData = cloneDeep(data);
// transposes table and args inplace
transposeTable(args, firstTable, formatters);
}
const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args;
const columnsReverseLookup = firstTable.columns.reduce<
Record<string, { name: string; index: number; meta?: DatatableColumnMeta }>
>((memo, { id, name, meta }, i) => {
memo[id] = { name, index: i, meta };
return memo;
}, {});
const columnsWithSummary = args.columns.filter((c) => c.summaryRow);
for (const column of columnsWithSummary) {
column.summaryRowValue = computeSummaryRowForColumn(
column,
firstTable,
formatters,
formatFactory({ id: 'number' })
);
}
if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') {
// Sort on raw values for these types, while use the formatted value for the rest
const sortingCriteria = getSortingCriteria(
isRange(columnsReverseLookup[sortBy]?.meta)
? 'range'
: columnsReverseLookup[sortBy]?.meta?.type,
sortBy,
formatters[sortBy],
sortDirection
);
// replace the table here
context.inspectorAdapters.tables[layerId].rows = (firstTable.rows || [])
.slice()
.sort(sortingCriteria);
// replace also the local copy
firstTable.rows = context.inspectorAdapters.tables[layerId].rows;
} else {
args.sortingColumnId = undefined;
args.sortingDirection = 'none';
}
return {
type: 'render',
as: 'lens_datatable_renderer',
value: {
data,
untransposedData,
args,
},
};
},
});
type DatatableColumnResult = ColumnState & { type: 'lens_datatable_column' };
export const datatableColumn: ExpressionFunctionDefinition<
'lens_datatable_column',
null,
ColumnState,
DatatableColumnResult
> = {
name: 'lens_datatable_column',
aliases: [],
type: 'lens_datatable_column',
help: '',
inputTypes: ['null'],
args: {
columnId: { types: ['string'], help: '' },
alignment: { types: ['string'], help: '' },
hidden: { types: ['boolean'], help: '' },
width: { types: ['number'], help: '' },
isTransposed: { types: ['boolean'], help: '' },
transposable: { types: ['boolean'], help: '' },
colorMode: { types: ['string'], help: '' },
palette: {
types: ['palette'],
help: '',
},
summaryRow: { types: ['string'], help: '' },
summaryLabel: { types: ['string'], help: '' },
},
fn: function fn(input: unknown, args: ColumnState) {
return {
type: 'lens_datatable_column',
...args,
};
},
};
export { datatableColumn, getDatatable } from '../../common/expressions';
export const getDatatableRenderer = (dependencies: {
formatFactory: FormatFactory;

View file

@ -5,11 +5,12 @@
* 2.0.
*/
import { CoreSetup } from 'kibana/public';
import { ChartsPluginSetup } from 'src/plugins/charts/public';
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import { EditorFrameSetup, FormatFactory } from '../types';
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
import type { CoreSetup } from 'kibana/public';
import type { ChartsPluginSetup } from 'src/plugins/charts/public';
import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import type { EditorFrameSetup } from '../types';
import type { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
import type { FormatFactory } from '../../common';
interface DatatableVisualizationPluginStartPlugins {
data: DataPublicPluginStart;

View file

@ -10,9 +10,8 @@ import { render } from 'react-dom';
import { Ast } from '@kbn/interpreter/common';
import { I18nProvider } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { DatatableColumn } from 'src/plugins/expressions/public';
import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
import {
import type { PaletteRegistry } from 'src/plugins/charts/public';
import type {
SuggestionRequest,
Visualization,
VisualizationSuggestion,
@ -21,32 +20,9 @@ import {
import { LensIconChartDatatable } from '../assets/chart_datatable';
import { TableDimensionEditor } from './components/dimension_editor';
import { CUSTOM_PALETTE } from '../shared_components/coloring/constants';
import { CustomPaletteParams } from '../shared_components/coloring/types';
import { getStopsForFixedMode } from '../shared_components';
import { getDefaultSummaryLabel } from './summary';
export interface ColumnState {
columnId: string;
width?: number;
hidden?: boolean;
isTransposed?: boolean;
// These flags are necessary to transpose columns and map them back later
// They are set automatically and are not user-editable
transposable?: boolean;
originalColumnId?: string;
originalName?: string;
bucketValues?: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>;
alignment?: 'left' | 'right' | 'center';
palette?: PaletteOutput<CustomPaletteParams>;
colorMode?: 'none' | 'cell' | 'text';
summaryRow?: 'none' | 'sum' | 'avg' | 'count' | 'min' | 'max';
summaryLabel?: string;
}
export interface SortingState {
columnId: string | undefined;
direction: 'asc' | 'desc' | 'none';
}
import { getDefaultSummaryLabel } from '../../common/expressions';
import type { ColumnState, SortingState } from '../../common/expressions';
export interface DatatableVisualizationState {
columns: ColumnState[];

View file

@ -22,7 +22,7 @@ import {
EditorFrameStart,
} from '../types';
import { Document } from '../persistence/saved_object_store';
import { mergeTables } from './merge_tables';
import { mergeTables } from '../../common/expressions';
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import { DashboardStart } from '../../../../../src/plugins/dashboard/public';

View file

@ -23,9 +23,8 @@ import type { LensByReferenceInput, LensByValueInput } from './embeddable';
import type { Document } from '../persistence';
import type { IndexPatternPersistedState } from '../indexpattern_datasource/types';
import type { XYState } from '../xy_visualization/types';
import type { PieVisualizationState } from '../pie_visualization/types';
import type { PieVisualizationState, MetricState } from '../../common/expressions';
import type { DatatableVisualizationState } from '../datatable_visualization/visualization';
import type { MetricState } from '../metric_visualization/types';
type LensAttributes<TVisType, TVisState> = Omit<
Document,

View file

@ -16,11 +16,11 @@ import {
ScaleType,
Settings,
} from '@elastic/charts';
import { CustomPaletteState } from 'src/plugins/charts/public';
import type { CustomPaletteState } from 'src/plugins/charts/public';
import { VisualizationContainer } from '../visualization_container';
import { HeatmapRenderProps } from './types';
import type { HeatmapRenderProps } from './types';
import './index.scss';
import { LensBrushEvent, LensFilterEvent } from '../types';
import type { LensBrushEvent, LensFilterEvent } from '../types';
import {
applyPaletteParams,
defaultPaletteParams,

View file

@ -14,8 +14,8 @@ import {
EuiFlexGroup,
EuiButtonEmpty,
} from '@elastic/eui';
import { PaletteRegistry } from 'src/plugins/charts/public';
import { VisualizationDimensionEditorProps } from '../types';
import type { PaletteRegistry } from 'src/plugins/charts/public';
import type { VisualizationDimensionEditorProps } from '../types';
import {
CustomizablePalette,
FIXED_PROGRESSION,
@ -23,7 +23,7 @@ import {
PalettePanelContainer,
} from '../shared_components/';
import './dimension_editor.scss';
import { HeatmapVisualizationState } from './types';
import type { HeatmapVisualizationState } from './types';
import { getSafePaletteParams } from './utils';
export function HeatmapDimensionEditor(

View file

@ -9,221 +9,15 @@ import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import ReactDOM from 'react-dom';
import React from 'react';
import { Position } from '@elastic/charts';
import {
ExpressionFunctionDefinition,
IInterpreterRenderHandlers,
} from '../../../../../src/plugins/expressions';
import { FormatFactory, LensBrushEvent, LensFilterEvent, LensMultiTable } from '../types';
import {
FUNCTION_NAME,
HEATMAP_GRID_FUNCTION,
LEGEND_FUNCTION,
LENS_HEATMAP_RENDERER,
} from './constants';
import type {
HeatmapExpressionArgs,
HeatmapExpressionProps,
HeatmapGridConfig,
HeatmapGridConfigResult,
HeatmapRender,
LegendConfigResult,
} from './types';
import { HeatmapLegendConfig } from './types';
import { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public';
import type { IInterpreterRenderHandlers } from '../../../../../src/plugins/expressions';
import type { LensBrushEvent, LensFilterEvent } from '../types';
import type { FormatFactory } from '../../common';
import { LENS_HEATMAP_RENDERER } from './constants';
import type { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public';
import { HeatmapChartReportable } from './chart_component';
import type { HeatmapExpressionProps } from './types';
export const heatmapGridConfig: ExpressionFunctionDefinition<
typeof HEATMAP_GRID_FUNCTION,
null,
HeatmapGridConfig,
HeatmapGridConfigResult
> = {
name: HEATMAP_GRID_FUNCTION,
aliases: [],
type: HEATMAP_GRID_FUNCTION,
help: `Configure the heatmap layout `,
inputTypes: ['null'],
args: {
// grid
strokeWidth: {
types: ['number'],
help: i18n.translate('xpack.lens.heatmapChart.config.strokeWidth.help', {
defaultMessage: 'Specifies the grid stroke width',
}),
required: false,
},
strokeColor: {
types: ['string'],
help: i18n.translate('xpack.lens.heatmapChart.config.strokeColor.help', {
defaultMessage: 'Specifies the grid stroke color',
}),
required: false,
},
cellHeight: {
types: ['number'],
help: i18n.translate('xpack.lens.heatmapChart.config.cellHeight.help', {
defaultMessage: 'Specifies the grid cell height',
}),
required: false,
},
cellWidth: {
types: ['number'],
help: i18n.translate('xpack.lens.heatmapChart.config.cellWidth.help', {
defaultMessage: 'Specifies the grid cell width',
}),
required: false,
},
// cells
isCellLabelVisible: {
types: ['boolean'],
help: i18n.translate('xpack.lens.heatmapChart.config.isCellLabelVisible.help', {
defaultMessage: 'Specifies whether or not the cell label is visible.',
}),
},
// Y-axis
isYAxisLabelVisible: {
types: ['boolean'],
help: i18n.translate('xpack.lens.heatmapChart.config.isYAxisLabelVisible.help', {
defaultMessage: 'Specifies whether or not the Y-axis labels are visible.',
}),
},
yAxisLabelWidth: {
types: ['number'],
help: i18n.translate('xpack.lens.heatmapChart.config.yAxisLabelWidth.help', {
defaultMessage: 'Specifies the width of the Y-axis labels.',
}),
required: false,
},
yAxisLabelColor: {
types: ['string'],
help: i18n.translate('xpack.lens.heatmapChart.config.yAxisLabelColor.help', {
defaultMessage: 'Specifies the color of the Y-axis labels.',
}),
required: false,
},
// X-axis
isXAxisLabelVisible: {
types: ['boolean'],
help: i18n.translate('xpack.lens.heatmapChart.config.isXAxisLabelVisible.help', {
defaultMessage: 'Specifies whether or not the X-axis labels are visible.',
}),
},
},
fn(input, args) {
return {
type: HEATMAP_GRID_FUNCTION,
...args,
};
},
};
/**
* TODO check if it's possible to make a shared function
* based on the XY chart
*/
export const heatmapLegendConfig: ExpressionFunctionDefinition<
typeof LEGEND_FUNCTION,
null,
HeatmapLegendConfig,
LegendConfigResult
> = {
name: LEGEND_FUNCTION,
aliases: [],
type: LEGEND_FUNCTION,
help: `Configure the heatmap chart's legend`,
inputTypes: ['null'],
args: {
isVisible: {
types: ['boolean'],
help: i18n.translate('xpack.lens.heatmapChart.legend.isVisible.help', {
defaultMessage: 'Specifies whether or not the legend is visible.',
}),
},
position: {
types: ['string'],
options: [Position.Top, Position.Right, Position.Bottom, Position.Left],
help: i18n.translate('xpack.lens.heatmapChart.legend.position.help', {
defaultMessage: 'Specifies the legend position.',
}),
},
},
fn(input, args) {
return {
type: LEGEND_FUNCTION,
...args,
};
},
};
export const heatmap: ExpressionFunctionDefinition<
typeof FUNCTION_NAME,
LensMultiTable,
HeatmapExpressionArgs,
HeatmapRender
> = {
name: FUNCTION_NAME,
type: 'render',
help: i18n.translate('xpack.lens.heatmap.expressionHelpLabel', {
defaultMessage: 'Heatmap renderer',
}),
args: {
title: {
types: ['string'],
help: i18n.translate('xpack.lens.heatmap.titleLabel', {
defaultMessage: 'Title',
}),
},
description: {
types: ['string'],
help: '',
},
xAccessor: {
types: ['string'],
help: '',
},
yAccessor: {
types: ['string'],
help: '',
},
valueAccessor: {
types: ['string'],
help: '',
},
shape: {
types: ['string'],
help: '',
},
palette: {
default: `{theme "palette" default={system_palette name="default"} }`,
help: '',
types: ['palette'],
},
legend: {
types: [LEGEND_FUNCTION],
help: i18n.translate('xpack.lens.heatmapChart.legend.help', {
defaultMessage: 'Configure the chart legend.',
}),
},
gridConfig: {
types: [HEATMAP_GRID_FUNCTION],
help: i18n.translate('xpack.lens.heatmapChart.gridConfig.help', {
defaultMessage: 'Configure the heatmap layout.',
}),
},
},
inputTypes: ['lens_multitable'],
fn(data: LensMultiTable, args: HeatmapExpressionArgs) {
return {
type: 'render',
as: LENS_HEATMAP_RENDERER,
value: {
data,
args,
},
};
},
};
export { heatmapGridConfig, heatmapLegendConfig, heatmap } from '../../common/expressions';
export const getHeatmapRenderer = (dependencies: {
formatFactory: Promise<FormatFactory>;

View file

@ -5,11 +5,12 @@
* 2.0.
*/
import { CoreSetup } from 'kibana/public';
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import { EditorFrameSetup, FormatFactory } from '../types';
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import type { CoreSetup } from 'kibana/public';
import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import type { EditorFrameSetup } from '../types';
import type { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import { getTimeZone } from '../utils';
import type { FormatFactory } from '../../common';
export interface HeatmapVisualizationPluginSetupPlugins {
expressions: ExpressionsSetup;

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import { getSuggestions } from './suggestions';
import { HeatmapVisualizationState } from './types';
import { HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION } from './constants';
import { Position } from '@elastic/charts';
import { getSuggestions } from './suggestions';
import type { HeatmapVisualizationState } from './types';
import { HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION } from './constants';
describe('heatmap suggestions', () => {
describe('rejects suggestions', () => {

View file

@ -8,8 +8,8 @@
import { partition } from 'lodash';
import { Position } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { Visualization } from '../types';
import { HeatmapVisualizationState } from './types';
import type { Visualization } from '../types';
import type { HeatmapVisualizationState } from './types';
import { CHART_SHAPES, HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION } from './constants';
export const getSuggestions: Visualization<HeatmapVisualizationState>['getSuggestions'] = ({

View file

@ -9,9 +9,9 @@ import React, { memo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Position } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { VisualizationToolbarProps } from '../types';
import type { VisualizationToolbarProps } from '../types';
import { LegendSettingsPopover } from '../shared_components';
import { HeatmapVisualizationState } from './types';
import type { HeatmapVisualizationState } from './types';
const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [
{

View file

@ -5,17 +5,12 @@
* 2.0.
*/
import { Position } from '@elastic/charts';
import { PaletteOutput } from '../../../../../src/plugins/charts/common';
import { FormatFactory, LensBrushEvent, LensFilterEvent, LensMultiTable } from '../types';
import {
CHART_SHAPES,
HEATMAP_GRID_FUNCTION,
LEGEND_FUNCTION,
LENS_HEATMAP_RENDERER,
} from './constants';
import { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public';
import { CustomPaletteParams } from '../shared_components';
import type { PaletteOutput } from '../../../../../src/plugins/charts/common';
import type { LensBrushEvent, LensFilterEvent } from '../types';
import type { LensMultiTable, FormatFactory, CustomPaletteParams } from '../../common';
import type { HeatmapGridConfigResult, HeatmapLegendConfigResult } from '../../common/expressions';
import { CHART_SHAPES, LENS_HEATMAP_RENDERER } from './constants';
import type { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public';
export type ChartShapes = typeof CHART_SHAPES[keyof typeof CHART_SHAPES];
@ -24,7 +19,7 @@ export interface SharedHeatmapLayerState {
xAccessor?: string;
yAccessor?: string;
valueAccessor?: string;
legend: LegendConfigResult;
legend: HeatmapLegendConfigResult;
gridConfig: HeatmapGridConfigResult;
}
@ -62,34 +57,3 @@ export type HeatmapRenderProps = HeatmapExpressionProps & {
onSelectRange: (data: LensBrushEvent['data']) => void;
paletteService: PaletteRegistry;
};
export interface HeatmapLegendConfig {
/**
* Flag whether the legend should be shown. If there is just a single series, it will be hidden
*/
isVisible: boolean;
/**
* Position of the legend relative to the chart
*/
position: Position;
}
export type LegendConfigResult = HeatmapLegendConfig & { type: typeof LEGEND_FUNCTION };
export interface HeatmapGridConfig {
// grid
strokeWidth?: number;
strokeColor?: string;
cellHeight?: number;
cellWidth?: number;
// cells
isCellLabelVisible: boolean;
// Y-axis
isYAxisLabelVisible: boolean;
yAxisLabelWidth?: number;
yAxisLabelColor?: string;
// X-axis
isXAxisLabelVisible: boolean;
}
export type HeatmapGridConfigResult = HeatmapGridConfig & { type: typeof HEATMAP_GRID_FUNCTION };

View file

@ -19,8 +19,8 @@ import {
LEGEND_FUNCTION,
} from './constants';
import { Position } from '@elastic/charts';
import { HeatmapVisualizationState } from './types';
import { DatasourcePublicAPI, Operation } from '../types';
import type { HeatmapVisualizationState } from './types';
import type { DatasourcePublicAPI, Operation } from '../types';
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
function exampleState(): HeatmapVisualizationState {

View file

@ -12,8 +12,8 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
import { Ast } from '@kbn/interpreter/common';
import { Position } from '@elastic/charts';
import { PaletteRegistry } from '../../../../../src/plugins/charts/public';
import { OperationMetadata, Visualization } from '../types';
import { HeatmapVisualizationState } from './types';
import type { OperationMetadata, Visualization } from '../types';
import type { HeatmapVisualizationState } from './types';
import { getSuggestions } from './suggestions';
import {
CHART_NAMES,
@ -27,9 +27,10 @@ import {
} from './constants';
import { HeatmapToolbar } from './toolbar_component';
import { LensIconChartHeatmap } from '../assets/chart_heatmap';
import { CustomPaletteParams, CUSTOM_PALETTE, getStopsForFixedMode } from '../shared_components';
import { CUSTOM_PALETTE, getStopsForFixedMode } from '../shared_components';
import { HeatmapDimensionEditor } from './dimension_editor';
import { getSafePaletteParams } from './utils';
import type { CustomPaletteParams } from '../../common';
const groupLabelForBar = i18n.translate('xpack.lens.heatmapVisualization.heatmapGroupLabel', {
defaultMessage: 'Heatmap',

View file

@ -11,8 +11,13 @@ export type {
EmbeddableComponentProps,
TypedLensByValueInput,
} from './embeddable/embeddable_component';
export type { XYState } from './xy_visualization/types';
export type { DataType, OperationMetadata } from './types';
export type {
XYState,
PieVisualizationState,
PieLayerState,
SharedPieLayerState,
MetricState,
AxesSettingsConfig,
XYLayerConfig,
LegendConfig,
@ -21,15 +26,8 @@ export type {
YAxisMode,
XYCurveType,
YConfig,
} from './xy_visualization/types';
export type { DataType, OperationMetadata } from './types';
export type {
PieVisualizationState,
PieLayerState,
SharedPieLayerState,
} from './pie_visualization/types';
} from '../common/expressions';
export type { DatatableVisualizationState } from './datatable_visualization/visualization';
export type { MetricState } from './metric_visualization/types';
export type {
IndexPatternPersistedState,
PersistedIndexPatternLayer,

View file

@ -16,7 +16,7 @@ import { IndexPatternColumn } from '../indexpattern';
import { isColumnInvalid } from '../utils';
import { IndexPatternPrivateState } from '../types';
import { DimensionEditor } from './dimension_editor';
import { DateRange } from '../../../common';
import type { DateRange } from '../../../common';
import { getOperationSupportMatrix } from './operation_support';
export type IndexPatternDimensionTriggerProps = DatasourceDimensionTriggerProps<IndexPatternPrivateState> & {

View file

@ -15,8 +15,8 @@ import {
IndexPatternColumn,
operationDefinitionMap,
} from '../operations';
import { unitSuffixesLong } from '../suffix_formatter';
import { TimeScaleUnit } from '../time_scale';
import type { TimeScaleUnit } from '../../../common/expressions';
import { unitSuffixesLong } from '../../../common/suffix_formatter';
import { IndexPatternLayer } from '../types';
export function setTimeScaling(

View file

@ -43,14 +43,14 @@ export class IndexPatternDatasource {
renameColumns,
formatColumn,
counterRate,
getTimeScaleFunction,
timeScale,
getSuffixFormatter,
} = await import('../async_services');
return core
.getStartServices()
.then(([coreStart, { data, indexPatternFieldEditor, uiActions }]) => {
data.fieldFormats.register([getSuffixFormatter(data.fieldFormats.deserialize)]);
expressions.registerFunction(getTimeScaleFunction(data));
expressions.registerFunction(timeScale);
expressions.registerFunction(counterRate);
expressions.registerFunction(renameColumns);
expressions.registerFunction(formatColumn);

View file

@ -69,11 +69,15 @@ export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: stri
};
}
export * from './rename_columns';
export * from './format_column';
export * from './time_scale';
export * from './counter_rate';
export * from './suffix_formatter';
export {
CounterRateArgs,
ExpressionFunctionCounterRate,
counterRate,
} from '../../common/expressions';
export { FormatColumnArgs, supportedFormats, formatColumn } from '../../common/expressions';
export { getSuffixFormatter, unitSuffixesLong } from '../../common/suffix_formatter';
export { timeScale, TimeScaleArgs } from '../../common/expressions';
export { renameColumns } from '../../common/expressions';
export function getIndexPatternDatasource({
core,

View file

@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
import type { ExpressionFunctionAST } from '@kbn/interpreter/common';
import memoizeOne from 'memoize-one';
import type { TimeScaleUnit } from '../../../time_scale';
import type { TimeScaleUnit } from '../../../../../common/expressions';
import type { IndexPattern, IndexPatternLayer } from '../../../types';
import { adjustTimeScaleLabelSuffix } from '../../time_scale_utils';
import type { ReferenceBasedIndexPatternColumn } from '../column_types';

View file

@ -7,7 +7,7 @@
import { Query } from 'src/plugins/data/public';
import type { Operation } from '../../../types';
import { TimeScaleUnit } from '../../time_scale';
import type { TimeScaleUnit } from '../../../../common/expressions';
import type { OperationType } from '../definitions';
export interface BaseIndexPatternColumn extends Operation {

View file

@ -17,7 +17,7 @@ import { RangeEditor } from './range_editor';
import { OperationDefinition } from '../index';
import { FieldBasedIndexPatternColumn } from '../column_types';
import { updateColumnParam } from '../../layer_helpers';
import { supportedFormats } from '../../../format_column';
import { supportedFormats } from '../../../../../common/expressions';
import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants';
import { IndexPattern, IndexPatternField } from '../../../types';
import { getInvalidFieldMessage, isValidNumber } from '../helpers';

View file

@ -32,7 +32,7 @@ import { getSortScoreByPriority } from './operations';
import { generateId } from '../../id_generator';
import { ReferenceBasedIndexPatternColumn } from './definitions/column_types';
import { FormulaIndexPatternColumn, regenerateLayerFromAst } from './definitions/formula';
import { TimeScaleUnit } from '../time_scale';
import type { TimeScaleUnit } from '../../../common/expressions';
interface ColumnAdvancedParams {
filter?: Query | undefined;

View file

@ -6,7 +6,7 @@
*/
import type { IndexPatternLayer } from '../types';
import type { TimeScaleUnit } from '../time_scale';
import type { TimeScaleUnit } from '../../../common/expressions';
import type { IndexPatternColumn } from './definitions';
import { adjustTimeScaleLabelSuffix, adjustTimeScaleOnOtherColumnChange } from './time_scale_utils';

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { unitSuffixesLong } from '../suffix_formatter';
import type { TimeScaleUnit } from '../time_scale';
import { unitSuffixesLong } from '../../../common/suffix_formatter';
import type { TimeScaleUnit } from '../../../common/expressions';
import type { IndexPatternLayer } from '../types';
import type { IndexPatternColumn } from './definitions';

View file

@ -1,150 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment-timezone';
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { search } from '../../../../../src/plugins/data/public';
import { buildResultColumns } from '../../../../../src/plugins/expressions/common';
export type TimeScaleUnit = 's' | 'm' | 'h' | 'd';
export interface TimeScaleArgs {
dateColumnId: string;
inputColumnId: string;
outputColumnId: string;
targetUnit: TimeScaleUnit;
outputColumnName?: string;
}
const unitInMs: Record<TimeScaleUnit, number> = {
s: 1000,
m: 1000 * 60,
h: 1000 * 60 * 60,
d: 1000 * 60 * 60 * 24,
};
export function getTimeScaleFunction(data: DataPublicPluginStart) {
const timeScale: ExpressionFunctionDefinition<
'lens_time_scale',
Datatable,
TimeScaleArgs,
Promise<Datatable>
> = {
name: 'lens_time_scale',
type: 'datatable',
help: '',
args: {
dateColumnId: {
types: ['string'],
help: '',
required: true,
},
inputColumnId: {
types: ['string'],
help: '',
required: true,
},
outputColumnId: {
types: ['string'],
help: '',
required: true,
},
outputColumnName: {
types: ['string'],
help: '',
},
targetUnit: {
types: ['string'],
options: ['s', 'm', 'h', 'd'],
help: '',
required: true,
},
},
inputTypes: ['datatable'],
async fn(
input,
{ dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs
) {
const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId);
if (!dateColumnDefinition) {
throw new Error(
i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', {
defaultMessage: 'Specified dateColumnId {columnId} does not exist.',
values: {
columnId: dateColumnId,
},
})
);
}
const resultColumns = buildResultColumns(
input,
outputColumnId,
inputColumnId,
outputColumnName,
{ allowColumnOverwrite: true }
);
if (!resultColumns) {
return input;
}
const targetUnitInMs = unitInMs[targetUnit];
const timeInfo = search.aggs.getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition);
const intervalDuration = timeInfo?.interval && search.aggs.parseInterval(timeInfo.interval);
if (!timeInfo || !intervalDuration) {
throw new Error(
i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', {
defaultMessage: 'Could not fetch date histogram information',
})
);
}
// the datemath plugin always parses dates by using the current default moment time zone.
// to use the configured time zone, we are switching just for the bounds calculation.
const defaultTimezone = moment().zoneName();
moment.tz.setDefault(timeInfo.timeZone);
const timeBounds =
timeInfo.timeRange && data.query.timefilter.timefilter.calculateBounds(timeInfo.timeRange);
const result = {
...input,
columns: resultColumns,
rows: input.rows.map((row) => {
const newRow = { ...row };
let startOfBucket = moment(row[dateColumnId]);
let endOfBucket = startOfBucket.clone().add(intervalDuration);
if (timeBounds && timeBounds.min) {
startOfBucket = moment.max(startOfBucket, timeBounds.min);
}
if (timeBounds && timeBounds.max) {
endOfBucket = moment.min(endOfBucket, timeBounds.max);
}
const bucketSize = endOfBucket.diff(startOfBucket);
const factor = bucketSize / targetUnitInMs;
const currentValue = newRow[inputColumnId];
if (currentValue != null) {
newRow[outputColumnId] = Number(currentValue) / factor;
}
return newRow;
}),
};
// reset default moment timezone
moment.tz.setDefault(defaultTimezone);
return result;
},
};
return timeScale;
}

View file

@ -22,9 +22,10 @@ import {
import { IndexPatternColumn } from './indexpattern';
import { operationDefinitionMap } from './operations';
import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types';
import { OriginalColumn } from './rename_columns';
import { dateHistogramOperation } from './operations/definitions';
type OriginalColumn = { id: string } & IndexPatternColumn;
function getExpressionForLayer(
layer: IndexPatternLayer,
indexPattern: IndexPattern,

View file

@ -5,13 +5,13 @@
* 2.0.
*/
import { metricChart, MetricChart } from './expression';
import { LensMultiTable } from '../types';
import { MetricChart, metricChart } from './expression';
import { MetricConfig } from '../../common/expressions';
import React from 'react';
import { shallow } from 'enzyme';
import { MetricConfig } from './types';
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
import { IFieldFormat } from '../../../../../src/plugins/data/public';
import type { LensMultiTable } from '../../common';
function sampleArgs() {
const data: LensMultiTable = {

View file

@ -9,75 +9,19 @@ import './expression.scss';
import { I18nProvider } from '@kbn/i18n/react';
import React from 'react';
import ReactDOM from 'react-dom';
import {
ExpressionFunctionDefinition,
import type {
ExpressionRenderDefinition,
IInterpreterRenderHandlers,
} from '../../../../../src/plugins/expressions/public';
import { MetricConfig } from './types';
import { FormatFactory, LensMultiTable } from '../types';
import { AutoScale } from './auto_scale';
import { VisualizationContainer } from '../visualization_container';
import { EmptyPlaceholder } from '../shared_components';
import { LensIconChartMetric } from '../assets/chart_metric';
import type { FormatFactory } from '../../common';
import type { MetricChartProps } from '../../common/expressions';
export interface MetricChartProps {
data: LensMultiTable;
args: MetricConfig;
}
export interface MetricRender {
type: 'render';
as: 'lens_metric_chart_renderer';
value: MetricChartProps;
}
export const metricChart: ExpressionFunctionDefinition<
'lens_metric_chart',
LensMultiTable,
Omit<MetricConfig, 'layerId'>,
MetricRender
> = {
name: 'lens_metric_chart',
type: 'render',
help: 'A metric chart',
args: {
title: {
types: ['string'],
help: 'The chart title.',
},
description: {
types: ['string'],
help: '',
},
metricTitle: {
types: ['string'],
help: 'The title of the metric shown.',
},
accessor: {
types: ['string'],
help: 'The column whose value is being displayed',
},
mode: {
types: ['string'],
options: ['reduced', 'full'],
default: 'full',
help:
'The display mode of the chart - reduced will only show the metric itself without min size',
},
},
inputTypes: ['lens_multitable'],
fn(data, args) {
return {
type: 'render',
as: 'lens_metric_chart_renderer',
value: {
data,
args,
},
} as MetricRender;
},
};
export { metricChart } from '../../common/expressions';
export type { MetricState, MetricConfig } from '../../common/expressions';
export const getMetricChartRenderer = (
formatFactory: Promise<FormatFactory>

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import { CoreSetup } from 'kibana/public';
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import { EditorFrameSetup, FormatFactory } from '../types';
import type { CoreSetup } from 'kibana/public';
import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import type { EditorFrameSetup } from '../types';
import type { FormatFactory } from '../../common';
export interface MetricVisualizationPluginSetupPlugins {
expressions: ExpressionsSetup;

View file

@ -6,7 +6,7 @@
*/
import { SuggestionRequest, VisualizationSuggestion, TableSuggestion } from '../types';
import { MetricState } from './types';
import type { MetricState } from '../../common/expressions';
import { LensIconChartMetric } from '../assets/chart_metric';
/**

View file

@ -6,7 +6,7 @@
*/
import { metricVisualization } from './visualization';
import { MetricState } from './types';
import type { MetricState } from '../../common/expressions';
import { createMockDatasource, createMockFramePublicAPI } from '../mocks';
import { generateId } from '../id_generator';
import { DatasourcePublicAPI, FramePublicAPI } from '../types';

View file

@ -10,7 +10,7 @@ import { Ast } from '@kbn/interpreter/target/common';
import { getSuggestions } from './metric_suggestions';
import { LensIconChartMetric } from '../assets/chart_metric';
import { Visualization, OperationMetadata, DatasourcePublicAPI } from '../types';
import { MetricState } from './types';
import type { MetricState } from '../../common/expressions';
const toExpression = (
state: MetricState,

View file

@ -8,108 +8,18 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
import { Position } from '@elastic/charts';
import { I18nProvider } from '@kbn/i18n/react';
import {
import type {
IInterpreterRenderHandlers,
ExpressionRenderDefinition,
ExpressionFunctionDefinition,
} from 'src/plugins/expressions/public';
import { LensMultiTable, FormatFactory, LensFilterEvent } from '../types';
import { PieExpressionProps, PieExpressionArgs } from './types';
import type { LensFilterEvent } from '../types';
import { PieComponent } from './render_function';
import { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public';
import type { FormatFactory } from '../../common';
import type { PieExpressionProps } from '../../common/expressions';
import type { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public';
export interface PieRender {
type: 'render';
as: 'lens_pie_renderer';
value: PieExpressionProps;
}
export const pie: ExpressionFunctionDefinition<
'lens_pie',
LensMultiTable,
PieExpressionArgs,
PieRender
> = {
name: 'lens_pie',
type: 'render',
help: i18n.translate('xpack.lens.pie.expressionHelpLabel', {
defaultMessage: 'Pie renderer',
}),
args: {
title: {
types: ['string'],
help: 'The chart title.',
},
description: {
types: ['string'],
help: '',
},
groups: {
types: ['string'],
multi: true,
help: '',
},
metric: {
types: ['string'],
help: '',
},
shape: {
types: ['string'],
options: ['pie', 'donut', 'treemap'],
help: '',
},
hideLabels: {
types: ['boolean'],
help: '',
},
numberDisplay: {
types: ['string'],
options: ['hidden', 'percent', 'value'],
help: '',
},
categoryDisplay: {
types: ['string'],
options: ['default', 'inside', 'hide'],
help: '',
},
legendDisplay: {
types: ['string'],
options: ['default', 'show', 'hide'],
help: '',
},
nestedLegend: {
types: ['boolean'],
help: '',
},
legendPosition: {
types: ['string'],
options: [Position.Top, Position.Right, Position.Bottom, Position.Left],
help: '',
},
percentDecimals: {
types: ['number'],
help: '',
},
palette: {
default: `{theme "palette" default={system_palette name="default"} }`,
help: '',
types: ['palette'],
},
},
inputTypes: ['lens_multitable'],
fn(data: LensMultiTable, args: PieExpressionArgs) {
return {
type: 'render',
as: 'lens_pie_renderer',
value: {
data,
args,
},
};
},
};
export { pie } from '../../common/expressions';
export const getPieRenderer = (dependencies: {
formatFactory: Promise<FormatFactory>;

View file

@ -5,11 +5,12 @@
* 2.0.
*/
import { CoreSetup } from 'src/core/public';
import { ExpressionsSetup } from 'src/plugins/expressions/public';
import { EditorFrameSetup, FormatFactory } from '../types';
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import type { CoreSetup } from 'src/core/public';
import type { ExpressionsSetup } from 'src/plugins/expressions/public';
import type { EditorFrameSetup } from '../types';
import type { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
import type { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import type { FormatFactory } from '../../common';
export interface PieVisualizationPluginSetupPlugins {
editorFrame: EditorFrameSetup;

View file

@ -16,9 +16,9 @@ import {
Chart,
} from '@elastic/charts';
import { shallow } from 'enzyme';
import { LensMultiTable } from '../types';
import type { LensMultiTable } from '../../common';
import type { PieExpressionArgs } from '../../common/expressions';
import { PieComponent } from './render_function';
import { PieExpressionArgs } from './types';
import { VisualizationContainer } from '../visualization_container';
import { EmptyPlaceholder } from '../shared_components';
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';

View file

@ -24,10 +24,11 @@ import {
ElementClickListener,
} from '@elastic/charts';
import { RenderMode } from 'src/plugins/expressions';
import { FormatFactory, LensFilterEvent } from '../types';
import type { LensFilterEvent } from '../types';
import { VisualizationContainer } from '../visualization_container';
import { CHART_NAMES, DEFAULT_PERCENT_DECIMALS } from './constants';
import { PieExpressionProps } from './types';
import type { FormatFactory } from '../../common';
import type { PieExpressionProps } from '../../common/expressions';
import { getSliceValue, getFilterContext } from './render_helpers';
import { EmptyPlaceholder } from '../shared_components';
import './visualization.scss';

View file

@ -8,7 +8,7 @@
import { PaletteOutput } from 'src/plugins/charts/public';
import { DataType, SuggestionRequest } from '../types';
import { suggestions } from './suggestions';
import { PieVisualizationState } from './types';
import type { PieVisualizationState } from '../../common/expressions';
describe('suggestions', () => {
describe('pie', () => {

View file

@ -7,8 +7,8 @@
import { partition } from 'lodash';
import { i18n } from '@kbn/i18n';
import { SuggestionRequest, VisualizationSuggestion } from '../types';
import { PieVisualizationState } from './types';
import type { SuggestionRequest, VisualizationSuggestion } from '../types';
import type { PieVisualizationState } from '../../common/expressions';
import { CHART_NAMES, MAX_PIE_BUCKETS, MAX_TREEMAP_BUCKETS } from './constants';
function shouldReject({ table, keptLayerIds }: SuggestionRequest<PieVisualizationState>) {

View file

@ -9,7 +9,7 @@ import { Ast } from '@kbn/interpreter/common';
import { PaletteRegistry } from 'src/plugins/charts/public';
import { Operation, DatasourcePublicAPI } from '../types';
import { DEFAULT_PERCENT_DECIMALS } from './constants';
import { PieVisualizationState } from './types';
import type { PieVisualizationState } from '../../common/expressions';
export function toExpression(
state: PieVisualizationState,

View file

@ -15,10 +15,10 @@ import {
EuiRange,
EuiHorizontalRule,
} from '@elastic/eui';
import { Position } from '@elastic/charts';
import { PaletteRegistry } from 'src/plugins/charts/public';
import type { Position } from '@elastic/charts';
import type { PaletteRegistry } from 'src/plugins/charts/public';
import { DEFAULT_PERCENT_DECIMALS } from './constants';
import { PieVisualizationState, SharedPieLayerState } from './types';
import type { PieVisualizationState, SharedPieLayerState } from '../../common/expressions';
import { VisualizationDimensionEditorProps, VisualizationToolbarProps } from '../types';
import { ToolbarPopover, LegendSettingsPopover, useDebouncedValue } from '../shared_components';
import { PalettePicker } from '../shared_components';

Some files were not shown because too many files have changed in this diff Show more