Move Lens attribute builder to a package (#163422)

closes [#163491](https://github.com/elastic/kibana/issues/163491)

## Summary

This PR creates a new package that contains a utility API that helps to
generate the JSON with the attributes required to render a Lens chart
with the `EmbeddableComponent`.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Carlos Crespo 2023-08-14 11:46:47 +02:00 committed by GitHub
parent 6e241a8b02
commit 281cc224c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 786 additions and 477 deletions

1
.github/CODEOWNERS vendored
View file

@ -460,6 +460,7 @@ src/plugins/kibana_usage_collection @elastic/kibana-core
src/plugins/kibana_utils @elastic/kibana-app-services src/plugins/kibana_utils @elastic/kibana-app-services
x-pack/plugins/kubernetes_security @elastic/sec-cloudnative-integrations x-pack/plugins/kubernetes_security @elastic/sec-cloudnative-integrations
packages/kbn-language-documentation-popover @elastic/kibana-visualizations packages/kbn-language-documentation-popover @elastic/kibana-visualizations
packages/kbn-lens-embeddable-utils @elastic/infra-monitoring-ui
x-pack/plugins/lens @elastic/kibana-visualizations x-pack/plugins/lens @elastic/kibana-visualizations
x-pack/plugins/license_api_guard @elastic/platform-deployment-management x-pack/plugins/license_api_guard @elastic/platform-deployment-management
x-pack/plugins/license_management @elastic/platform-deployment-management x-pack/plugins/license_management @elastic/platform-deployment-management

View file

@ -481,6 +481,7 @@
"@kbn/kibana-utils-plugin": "link:src/plugins/kibana_utils", "@kbn/kibana-utils-plugin": "link:src/plugins/kibana_utils",
"@kbn/kubernetes-security-plugin": "link:x-pack/plugins/kubernetes_security", "@kbn/kubernetes-security-plugin": "link:x-pack/plugins/kubernetes_security",
"@kbn/language-documentation-popover": "link:packages/kbn-language-documentation-popover", "@kbn/language-documentation-popover": "link:packages/kbn-language-documentation-popover",
"@kbn/lens-embeddable-utils": "link:packages/kbn-lens-embeddable-utils",
"@kbn/lens-plugin": "link:x-pack/plugins/lens", "@kbn/lens-plugin": "link:x-pack/plugins/lens",
"@kbn/license-api-guard-plugin": "link:x-pack/plugins/license_api_guard", "@kbn/license-api-guard-plugin": "link:x-pack/plugins/license_api_guard",
"@kbn/license-management-plugin": "link:x-pack/plugins/license_management", "@kbn/license-management-plugin": "link:x-pack/plugins/license_management",

View file

@ -1,11 +1,20 @@
# Lens Attributes Builder # @kbn/lens-embeddable-utils
The Lens Attributes Builder is a utility for creating JSON objects used to render charts with Lens. It simplifies the process of configuring and building the necessary attributes for different chart types. ## Lens Attributes Builder
## Usage The Lens Attributes Builder is a utility for creating JSON objects used to render charts with Lens. It simplifies the process of configuring and building the necessary attributes for different chart types.
### Creating a Metric Chart **Notes**:
This utililty is primarily used by Infra Observability UI and not meant to be used as an official solution provided by the Lens team.
- The tool has partial support of Lens charts, currently limited to XY and Metric charts.
- XY Bucket and Breakdown dimensions are limited respectively to Date Histogram and Top values.
### Usage
#### Creating a Metric Chart
To create a metric chart, use the `MetricChart` class and provide the required configuration. Here's an example: To create a metric chart, use the `MetricChart` class and provide the required configuration. Here's an example:
@ -22,13 +31,13 @@ const metricChart = new MetricChart({
}, },
}, },
}, },
formulaAPI,
}), }),
dataView, dataView,
formulaAPI
}); });
``` ```
### Creating an XY Chart #### Creating an XY Chart
To create an XY chart, use the `XYChart` class and provide the required configuration. Here's an example: To create an XY chart, use the `XYChart` class and provide the required configuration. Here's an example:
@ -45,13 +54,72 @@ const xyChart = new XYChart({
}, },
}, },
}], }],
formulaAPI, options: {
buckets: {type: 'date_histogram'},
},
})], })],
dataView, dataView,
formulaAPI
}); });
``` ```
### Adding Multiple Layers to an XY Chart #### Variations of the XY Chart
XYChart has different series type variations. Here is an example of how to build a line (default) and area charts
#### Line chart
```ts
const xyChart = new XYChart({
layers: [new XYDataLayer({
data: [{
label: 'Inbound (RX)',
value: "average(system.load.1) / max(system.load.cores)",
format: {
id: 'percent',
params: {
decimals: 1,
},
},
}],
options: {
buckets: {type: 'date_histogram'},
seriesType: 'line' // default. it doesn't need to be informed.
}
})],
dataView,
formulaAPI
});
```
#### Area chart
```ts
const xyChart = new XYChart({
layers: [new XYDataLayer({
data: [{
label: 'Inbound (RX)',
value: "average(system.load.1) / max(system.load.cores)",
format: {
id: 'percent',
params: {
decimals: 1,
},
},
}],
options: {
buckets: {type: 'date_histogram'},
seriesType: 'area'
}
})],
dataView,
formulaAPI
});
```
#### Adding Multiple Layers to an XY Chart
An XY chart can have multiple layers. Here's an example of containing a Reference Line Layer: An XY chart can have multiple layers. Here's an example of containing a Reference Line Layer:
@ -69,10 +137,13 @@ const xyChart = new XYChart({
}, },
}, },
}], }],
formulaAPI, options: {
buckets: {type: 'date_histogram'},
},
}), }),
new XYReferenceLineLayer({ new XYReferenceLineLayer({
data: [{ data: [{
value: "1", value: "1",
format: { format: {
id: 'percent', id: 'percent',
@ -84,10 +155,11 @@ const xyChart = new XYChart({
}), }),
], ],
dataView, dataView,
formulaAPI
}); });
``` ```
### Adding Multiple Data Sources in the Same Layer #### Adding Multiple Data Sources in the Same Layer
In an XY chart, it's possible to define multiple data sources within the same layer. In an XY chart, it's possible to define multiple data sources within the same layer.
@ -115,13 +187,16 @@ const xyChart = new XYChart({
}, },
}, },
}], }],
formulaAPI, options: {
buckets: {type: 'date_histogram'},
},
}), }),
dataView, dataView,
formulaAPI
}); });
``` ```
### Building Lens Chart Attributes #### Building Lens Chart Attributes
The `LensAttributesBuilder` is responsible for creating the full JSON object that combines the attributes returned by the chart classes. Here's an example: The `LensAttributesBuilder` is responsible for creating the full JSON object that combines the attributes returned by the chart classes. Here's an example:
@ -150,10 +225,10 @@ const builder = new LensAttributesBuilder({
}, },
}, },
}, },
formulaAPI,
}), }),
dataView, dataView,
}) formulaAPI
}),
}); });
const lensAttributes = builder.build(); const lensAttributes = builder.build();
@ -163,4 +238,4 @@ const lensAttributes = builder.build();
viewMode={ViewMode.VIEW} viewMode={ViewMode.VIEW}
... ...
/> />
``` ```

View file

@ -1,13 +1,14 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import { DataViewSpec, DataView } from '@kbn/data-plugin/common'; import { DataViewSpec, DataView } from '@kbn/data-plugin/common';
export const DEFAULT_AD_HOC_DATA_VIEW_ID = 'infra_lens_ad_hoc_default'; export const DEFAULT_AD_HOC_DATA_VIEW_ID = 'lens_ad_hoc_default';
export class DataViewCache { export class DataViewCache {
private static instance: DataViewCache; private static instance: DataViewCache;

View file

@ -1,8 +1,9 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import 'jest-canvas-mock'; import 'jest-canvas-mock';
@ -19,7 +20,7 @@ import {
} from './visualization_types'; } from './visualization_types';
import type { FormulaPublicApi, GenericIndexPatternColumn } from '@kbn/lens-plugin/public'; import type { FormulaPublicApi, GenericIndexPatternColumn } from '@kbn/lens-plugin/public';
import { ReferenceBasedIndexPatternColumn } from '@kbn/lens-plugin/public/datasources/form_based/operations/definitions/column_types'; import { ReferenceBasedIndexPatternColumn } from '@kbn/lens-plugin/public/datasources/form_based/operations/definitions/column_types';
import type { FormulaConfig } from '../types'; import type { FormulaValueConfig } from './types';
const mockDataView = { const mockDataView = {
id: 'mock-id', id: 'mock-id',
@ -85,7 +86,7 @@ const REFERENCE_LINE_LAYER: ReferenceBasedIndexPatternColumn = {
scale: 'ratio', scale: 'ratio',
}; };
const getFormula = (value: string): FormulaConfig => ({ const getFormula = (value: string): FormulaValueConfig => ({
value, value,
format: { format: {
id: 'percent', id: 'percent',
@ -106,10 +107,10 @@ describe('lens_attributes_builder', () => {
const metriChart = new MetricChart({ const metriChart = new MetricChart({
layers: new MetricLayer({ layers: new MetricLayer({
data: getFormula(AVERAGE_CPU_USER_FORMULA), data: getFormula(AVERAGE_CPU_USER_FORMULA),
formulaAPI,
}), }),
dataView: mockDataView, dataView: mockDataView,
formulaAPI,
}); });
const builder = new LensAttributesBuilder({ visualization: metriChart }); const builder = new LensAttributesBuilder({ visualization: metriChart });
const { const {
@ -148,10 +149,10 @@ describe('lens_attributes_builder', () => {
options: { options: {
showTrendLine: true, showTrendLine: true,
}, },
formulaAPI,
}), }),
dataView: mockDataView, dataView: mockDataView,
formulaAPI,
}); });
const builder = new LensAttributesBuilder({ visualization: metriChart }); const builder = new LensAttributesBuilder({ visualization: metriChart });
const { const {
@ -204,10 +205,13 @@ describe('lens_attributes_builder', () => {
layers: [ layers: [
new XYDataLayer({ new XYDataLayer({
data: [getFormula(AVERAGE_CPU_USER_FORMULA)], data: [getFormula(AVERAGE_CPU_USER_FORMULA)],
formulaAPI, options: {
buckets: { type: 'date_histogram' },
},
}), }),
], ],
dataView: mockDataView, dataView: mockDataView,
formulaAPI,
}); });
const builder = new LensAttributesBuilder({ visualization: xyChart }); const builder = new LensAttributesBuilder({ visualization: xyChart });
const { const {
@ -248,13 +252,23 @@ describe('lens_attributes_builder', () => {
layers: [ layers: [
new XYDataLayer({ new XYDataLayer({
data: [getFormula(AVERAGE_CPU_USER_FORMULA)], data: [getFormula(AVERAGE_CPU_USER_FORMULA)],
formulaAPI, options: {
buckets: { type: 'date_histogram' },
},
}), }),
new XYReferenceLinesLayer({ new XYReferenceLinesLayer({
data: [getFormula('1')], data: [
{
value: '1',
format: {
id: 'percent',
},
},
],
}), }),
], ],
dataView: mockDataView, dataView: mockDataView,
formulaAPI,
}); });
const builder = new LensAttributesBuilder({ visualization: xyChart }); const builder = new LensAttributesBuilder({ visualization: xyChart });
const { const {
@ -316,10 +330,13 @@ describe('lens_attributes_builder', () => {
layers: [ layers: [
new XYDataLayer({ new XYDataLayer({
data: [getFormula(AVERAGE_CPU_USER_FORMULA), getFormula(AVERAGE_CPU_SYSTEM_FORMULA)], data: [getFormula(AVERAGE_CPU_USER_FORMULA), getFormula(AVERAGE_CPU_SYSTEM_FORMULA)],
formulaAPI, options: {
buckets: { type: 'date_histogram' },
},
}), }),
], ],
dataView: mockDataView, dataView: mockDataView,
formulaAPI,
}); });
const builder = new LensAttributesBuilder({ visualization: xyChart }); const builder = new LensAttributesBuilder({ visualization: xyChart });
const { const {

View file

@ -1,15 +1,17 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import type { import type {
LensAttributes, LensAttributes,
LensVisualizationState, LensVisualizationState,
Chart, Chart,
VisualizationAttributesBuilder, VisualizationAttributesBuilder,
} from '../types'; } from './types';
import { DataViewCache } from './data_view_cache'; import { DataViewCache } from './data_view_cache';
import { getAdhocDataView } from './utils'; import { getAdhocDataView } from './utils';
@ -17,12 +19,12 @@ export class LensAttributesBuilder<T extends Chart<LensVisualizationState>>
implements VisualizationAttributesBuilder implements VisualizationAttributesBuilder
{ {
private dataViewCache: DataViewCache; private dataViewCache: DataViewCache;
constructor(private state: { visualization: T }) { constructor(private lens: { visualization: T }) {
this.dataViewCache = DataViewCache.getInstance(); this.dataViewCache = DataViewCache.getInstance();
} }
build(): LensAttributes { build(): LensAttributes {
const { visualization } = this.state; const { visualization } = this.lens;
return { return {
title: visualization.getTitle(), title: visualization.getTitle(),
visualizationType: visualization.getVisualizationType(), visualizationType: visualization.getVisualizationType(),
@ -34,10 +36,17 @@ export class LensAttributesBuilder<T extends Chart<LensVisualizationState>>
}, },
}, },
internalReferences: visualization.getReferences(), internalReferences: visualization.getReferences(),
// EmbeddableComponent receive filters.
filters: [], filters: [],
// EmbeddableComponent receive query.
query: { language: 'kuery', query: '' }, query: { language: 'kuery', query: '' },
visualization: visualization.getVisualizationState(), visualization: visualization.getVisualizationState(),
adHocDataViews: getAdhocDataView(this.dataViewCache.getSpec(visualization.getDataView())), // Getting the spec from a data view is a heavy operation, that's why the result is cached.
adHocDataViews: getAdhocDataView(
visualization
.getDataViews()
.reduce((acc, curr) => ({ ...acc, ...this.dataViewCache.getSpec(curr) }), {})
),
}, },
}; };
} }

View file

@ -0,0 +1,91 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { SavedObjectReference } from '@kbn/core/server';
import type { DataView } from '@kbn/data-views-plugin/common';
import type {
FormBasedPersistedState,
MetricVisualizationState,
PersistedIndexPatternLayer,
TypedLensByValueInput,
XYState,
FormulaPublicApi,
XYLayerConfig,
} from '@kbn/lens-plugin/public';
export type LensAttributes = TypedLensByValueInput['attributes'];
// Attributes
export type LensVisualizationState = XYState | MetricVisualizationState;
export interface VisualizationAttributesBuilder {
build(): LensAttributes;
}
// Column
export interface BaseChartColumn<TValueConfig extends StaticValueConfig | FormulaValueConfig> {
getValueConfig(): TValueConfig;
}
export interface ChartColumn extends BaseChartColumn<FormulaValueConfig> {
getData(
id: string,
baseLayer: PersistedIndexPatternLayer,
dataView: DataView,
formulaAPI: FormulaPublicApi
): PersistedIndexPatternLayer;
}
export interface StaticChartColumn extends BaseChartColumn<StaticValueConfig> {
getData(id: string, baseLayer: PersistedIndexPatternLayer): PersistedIndexPatternLayer;
}
// Layer
export type LensLayerConfig = XYLayerConfig | MetricVisualizationState;
export interface ChartLayer<TLayerConfig extends LensLayerConfig> {
getName(): string | undefined;
getLayer(
layerId: string,
accessorId: string,
dataView: DataView,
formulaAPI: FormulaPublicApi
): FormBasedPersistedState['layers'];
getReference(layerId: string, dataView: DataView): SavedObjectReference[];
getLayerConfig(layerId: string, acessorId: string): TLayerConfig;
getDataView(): DataView | undefined;
}
// Chart
export interface Chart<TVisualizationState extends LensVisualizationState> {
getTitle(): string;
getVisualizationType(): string;
getLayers(): FormBasedPersistedState['layers'];
getVisualizationState(): TVisualizationState;
getReferences(): SavedObjectReference[];
getDataViews(): DataView[];
}
export interface ChartConfig<
TLayer extends ChartLayer<LensLayerConfig> | Array<ChartLayer<LensLayerConfig>>
> {
formulaAPI: FormulaPublicApi;
dataView: DataView;
layers: TLayer;
title?: string;
}
// Formula
type LensFormula = Parameters<FormulaPublicApi['insertOrReplaceFormulaColumn']>[1];
export type FormulaValueConfig = Omit<LensFormula, 'formula'> & {
color?: string;
value: string;
};
export type StaticValueConfig = Omit<LensFormula, 'formula'> & {
color?: string;
value: string;
};

View file

@ -1,10 +1,12 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import {
import type {
DateHistogramIndexPatternColumn, DateHistogramIndexPatternColumn,
PersistedIndexPatternLayer, PersistedIndexPatternLayer,
TermsIndexPatternColumn, TermsIndexPatternColumn,
@ -17,12 +19,27 @@ export const DEFAULT_AD_HOC_DATA_VIEW_ID = 'infra_lens_ad_hoc_default';
const DEFAULT_BREAKDOWN_SIZE = 10; const DEFAULT_BREAKDOWN_SIZE = 10;
export function nonNullable<T>(v: T): v is NonNullable<T> {
return v != null;
}
export type DateHistogramColumnParams = DateHistogramIndexPatternColumn['params'];
export type TopValuesColumnParams = Pick<
TermsIndexPatternColumn['params'],
'size' | 'orderDirection' | 'orderBy'
>;
export const getHistogramColumn = ({ export const getHistogramColumn = ({
columnName, columnName,
overrides, options,
}: { }: {
columnName: string; columnName: string;
overrides?: Partial<Pick<DateHistogramIndexPatternColumn, 'sourceField' | 'params'>>; options?: Partial<
Pick<DateHistogramIndexPatternColumn, 'sourceField'> & {
params: DateHistogramColumnParams;
}
>;
}) => { }) => {
return { return {
[columnName]: { [columnName]: {
@ -32,32 +49,32 @@ export const getHistogramColumn = ({
operationType: 'date_histogram', operationType: 'date_histogram',
scale: 'interval', scale: 'interval',
sourceField: '@timestamp', sourceField: '@timestamp',
...overrides, ...options,
params: { interval: 'auto', ...overrides?.params }, params: { interval: 'auto', ...options?.params },
} as DateHistogramIndexPatternColumn, } as DateHistogramIndexPatternColumn,
}; };
}; };
export const getTopValuesColumn = ({ export const getTopValuesColumn = ({
columnName, columnName,
overrides, field,
options,
}: { }: {
columnName: string; columnName: string;
overrides?: Partial<Pick<TermsIndexPatternColumn, 'sourceField'>> & { field: string;
breakdownSize?: number; options?: Partial<TopValuesColumnParams>;
};
}): PersistedIndexPatternLayer['columns'] => { }): PersistedIndexPatternLayer['columns'] => {
const { breakdownSize = DEFAULT_BREAKDOWN_SIZE, sourceField } = overrides ?? {}; const { size = DEFAULT_BREAKDOWN_SIZE, ...params } = options ?? {};
return { return {
[columnName]: { [columnName]: {
label: `Top ${breakdownSize} values of ${sourceField}`, label: `Top ${size} values of ${field}`,
dataType: 'string', dataType: 'string',
operationType: 'terms', operationType: 'terms',
scale: 'ordinal', scale: 'ordinal',
sourceField, sourceField: field,
isBucketed: true, isBucketed: true,
params: { params: {
size: breakdownSize, size,
orderBy: { orderBy: {
type: 'alphabetical', type: 'alphabetical',
fallback: false, fallback: false,
@ -72,6 +89,7 @@ export const getTopValuesColumn = ({
exclude: [], exclude: [],
includeIsRegex: false, includeIsRegex: false,
excludeIsRegex: false, excludeIsRegex: false,
...params,
}, },
} as TermsIndexPatternColumn, } as TermsIndexPatternColumn,
}; };

View file

@ -1,8 +1,9 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
export { XYChart, type XYVisualOptions } from './xy_chart'; export { XYChart, type XYVisualOptions } from './xy_chart';

View file

@ -1,28 +1,30 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import type { FormulaPublicApi, PersistedIndexPatternLayer } from '@kbn/lens-plugin/public'; import type { FormulaPublicApi, PersistedIndexPatternLayer } from '@kbn/lens-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public';
import type { FormulaConfig, ChartColumn } from '../../../../types'; import type { FormulaValueConfig, ChartColumn } from '../../../types';
export class FormulaColumn implements ChartColumn { export class FormulaColumn implements ChartColumn {
constructor(private formulaConfig: FormulaConfig, private formulaAPI: FormulaPublicApi) {} constructor(private valueConfig: FormulaValueConfig) {}
getFormulaConfig(): FormulaConfig { getValueConfig(): FormulaValueConfig {
return this.formulaConfig; return this.valueConfig;
} }
getData( getData(
id: string, id: string,
baseLayer: PersistedIndexPatternLayer, baseLayer: PersistedIndexPatternLayer,
dataView: DataView dataView: DataView,
formulaAPI: FormulaPublicApi
): PersistedIndexPatternLayer { ): PersistedIndexPatternLayer {
const { value, ...rest } = this.getFormulaConfig(); const { value, ...rest } = this.getValueConfig();
const formulaLayer = this.formulaAPI.insertOrReplaceFormulaColumn( const formulaLayer = formulaAPI.insertOrReplaceFormulaColumn(
id, id,
{ formula: value, ...rest }, { formula: value, ...rest },
baseLayer, baseLayer,

View file

@ -1,23 +1,24 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import type { PersistedIndexPatternLayer } from '@kbn/lens-plugin/public'; import type { PersistedIndexPatternLayer } from '@kbn/lens-plugin/public';
import type { ReferenceBasedIndexPatternColumn } from '@kbn/lens-plugin/public/datasources/form_based/operations/definitions/column_types'; import type { ReferenceBasedIndexPatternColumn } from '@kbn/lens-plugin/public/datasources/form_based/operations/definitions/column_types';
import type { FormulaConfig, ChartColumn } from '../../../../types'; import type { StaticChartColumn, StaticValueConfig } from '../../../types';
export class ReferenceLineColumn implements ChartColumn { export class ReferenceLineColumn implements StaticChartColumn {
constructor(private formulaConfig: FormulaConfig) {} constructor(private valueConfig: StaticValueConfig) {}
getFormulaConfig(): FormulaConfig { getValueConfig(): StaticValueConfig {
return this.formulaConfig; return this.valueConfig;
} }
getData(id: string, baseLayer: PersistedIndexPatternLayer): PersistedIndexPatternLayer { getData(id: string, baseLayer: PersistedIndexPatternLayer): PersistedIndexPatternLayer {
const { label, ...params } = this.getFormulaConfig(); const { label, ...params } = this.getValueConfig();
return { return {
linkToLayers: [], linkToLayers: [],
columnOrder: [...baseLayer.columnOrder, id], columnOrder: [...baseLayer.columnOrder, id],

View file

@ -1,13 +1,14 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
export { MetricLayer, type MetricLayerOptions } from './metric_layer'; export { MetricLayer, type MetricLayerOptions } from './metric_layer';
export { XYDataLayer, type XYLayerOptions } from './xy_data_layer'; export { XYDataLayer, type XYLayerOptions } from './xy_data_layer';
export { XYReferenceLinesLayer } from './xy_reference_lines_layer'; export { XYReferenceLinesLayer } from './xy_reference_lines_layer';
export { FormulaColumn as FormulaDataColumn } from './column/formula'; export { FormulaColumn as FormulaDataColumn } from './columns/formula';
export { ReferenceLineColumn } from './column/reference_line'; export { ReferenceLineColumn } from './columns/reference_line';

View file

@ -1,8 +1,9 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import type { SavedObjectReference } from '@kbn/core/server'; import type { SavedObjectReference } from '@kbn/core/server';
@ -13,9 +14,9 @@ import type {
MetricVisualizationState, MetricVisualizationState,
PersistedIndexPatternLayer, PersistedIndexPatternLayer,
} from '@kbn/lens-plugin/public'; } from '@kbn/lens-plugin/public';
import type { ChartColumn, ChartLayer, FormulaConfig } from '../../../types'; import type { ChartColumn, ChartLayer, FormulaValueConfig } from '../../types';
import { getDefaultReferences, getHistogramColumn } from '../../utils'; import { getDefaultReferences, getHistogramColumn } from '../../utils';
import { FormulaColumn } from './column/formula'; import { FormulaColumn } from './columns/formula';
const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; const HISTOGRAM_COLUMN_NAME = 'x_date_histogram';
@ -27,28 +28,32 @@ export interface MetricLayerOptions {
} }
interface MetricLayerConfig { interface MetricLayerConfig {
data: FormulaConfig; data: FormulaValueConfig;
options?: MetricLayerOptions; options?: MetricLayerOptions;
formulaAPI: FormulaPublicApi; /**
* It is possible to define a specific dataView for the layer. It will override the global chart one
**/
dataView?: DataView;
} }
export class MetricLayer implements ChartLayer<MetricVisualizationState> { export class MetricLayer implements ChartLayer<MetricVisualizationState> {
private column: ChartColumn; private column: ChartColumn;
constructor(private layerConfig: MetricLayerConfig) { constructor(private layerConfig: MetricLayerConfig) {
this.column = new FormulaColumn(layerConfig.data, layerConfig.formulaAPI); this.column = new FormulaColumn(layerConfig.data);
} }
getLayer( getLayer(
layerId: string, layerId: string,
accessorId: string, accessorId: string,
dataView: DataView chartDataView: DataView,
formulaAPI: FormulaPublicApi
): FormBasedPersistedState['layers'] { ): FormBasedPersistedState['layers'] {
const baseLayer: PersistedIndexPatternLayer = { const baseLayer: PersistedIndexPatternLayer = {
columnOrder: [HISTOGRAM_COLUMN_NAME], columnOrder: [HISTOGRAM_COLUMN_NAME],
columns: getHistogramColumn({ columns: getHistogramColumn({
columnName: HISTOGRAM_COLUMN_NAME, columnName: HISTOGRAM_COLUMN_NAME,
overrides: { options: {
sourceField: dataView.timeFieldName, sourceField: (this.layerConfig.dataView ?? chartDataView).timeFieldName,
params: { params: {
interval: 'auto', interval: 'auto',
includeEmptyRows: true, includeEmptyRows: true,
@ -66,23 +71,29 @@ export class MetricLayer implements ChartLayer<MetricVisualizationState> {
columnOrder: [], columnOrder: [],
columns: {}, columns: {},
}, },
dataView this.layerConfig.dataView ?? chartDataView,
formulaAPI
), ),
}, },
...(this.layerConfig.options?.showTrendLine ...(this.layerConfig.options?.showTrendLine
? { ? {
[`${layerId}_trendline`]: { [`${layerId}_trendline`]: {
linkToLayers: [layerId], linkToLayers: [layerId],
...this.column.getData(`${accessorId}_trendline`, baseLayer, dataView), ...this.column.getData(
`${accessorId}_trendline`,
baseLayer,
this.layerConfig.dataView ?? chartDataView,
formulaAPI
),
}, },
} }
: {}), : {}),
}; };
} }
getReference(layerId: string, dataView: DataView): SavedObjectReference[] { getReference(layerId: string, chartDataView: DataView): SavedObjectReference[] {
return [ return [
...getDefaultReferences(dataView, layerId), ...getDefaultReferences(this.layerConfig.dataView ?? chartDataView, layerId),
...getDefaultReferences(dataView, `${layerId}_trendline`), ...getDefaultReferences(this.layerConfig.dataView ?? chartDataView, `${layerId}_trendline`),
]; ];
} }
@ -107,6 +118,10 @@ export class MetricLayer implements ChartLayer<MetricVisualizationState> {
}; };
} }
getName(): string | undefined { getName(): string | undefined {
return this.column.getFormulaConfig().label; return this.column.getValueConfig().label;
}
getDataView(): DataView | undefined {
return this.layerConfig.dataView;
} }
} }

View file

@ -0,0 +1,177 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { SavedObjectReference } from '@kbn/core/server';
import type { DataView } from '@kbn/data-views-plugin/common';
import type {
FormulaPublicApi,
FormBasedPersistedState,
PersistedIndexPatternLayer,
XYDataLayerConfig,
SeriesType,
TermsIndexPatternColumn,
DateHistogramIndexPatternColumn,
} from '@kbn/lens-plugin/public';
import type { ChartColumn, ChartLayer, FormulaValueConfig } from '../../types';
import {
getDefaultReferences,
getHistogramColumn,
getTopValuesColumn,
nonNullable,
type TopValuesColumnParams,
type DateHistogramColumnParams,
} from '../../utils';
import { FormulaColumn } from './columns/formula';
const BREAKDOWN_COLUMN_NAME = 'aggs_breakdown';
const HISTOGRAM_COLUMN_NAME = 'x_date_histogram';
interface TopValuesBucketedColumn {
type: 'top_values';
field: TermsIndexPatternColumn['sourceField'];
params?: Partial<TopValuesColumnParams>;
}
interface DateHistogramBucketedColumn {
type: 'date_histogram';
field?: DateHistogramIndexPatternColumn['sourceField'];
params?: Partial<DateHistogramColumnParams>;
}
export interface XYLayerOptions {
// Add more types as support for them is implemented
breakdown?: TopValuesBucketedColumn;
// Add more types as support for them is implemented
buckets?: DateHistogramBucketedColumn;
seriesType?: SeriesType;
}
interface XYLayerConfig {
data: FormulaValueConfig[];
options?: XYLayerOptions;
/**
* It is possible to define a specific dataView for the layer. It will override the global chart one
**/
dataView?: DataView;
}
export class XYDataLayer implements ChartLayer<XYDataLayerConfig> {
private column: ChartColumn[];
private layerConfig: XYLayerConfig;
constructor(layerConfig: XYLayerConfig) {
this.column = layerConfig.data.map((dataItem) => new FormulaColumn(dataItem));
this.layerConfig = {
...layerConfig,
options: {
...layerConfig.options,
buckets: {
type: 'date_histogram',
...layerConfig.options?.buckets,
},
},
};
}
getName(): string | undefined {
return this.column[0].getValueConfig().label;
}
getBaseLayer(dataView: DataView, options: XYLayerOptions) {
return {
...(options.buckets?.type === 'date_histogram'
? getHistogramColumn({
columnName: HISTOGRAM_COLUMN_NAME,
options: {
...options.buckets.params,
sourceField: options.buckets.field ?? dataView.timeFieldName,
},
})
: {}),
...(options.breakdown?.type === 'top_values'
? {
...getTopValuesColumn({
columnName: BREAKDOWN_COLUMN_NAME,
field: options?.breakdown.field,
options: {
...options.breakdown.params,
},
}),
}
: {}),
};
}
getLayer(
layerId: string,
accessorId: string,
chartDataView: DataView,
formulaAPI: FormulaPublicApi
): FormBasedPersistedState['layers'] {
const columnOrder: string[] = [];
if (this.layerConfig.options?.breakdown?.type === 'top_values') {
columnOrder.push(BREAKDOWN_COLUMN_NAME);
}
if (this.layerConfig.options?.buckets?.type === 'date_histogram') {
columnOrder.push(HISTOGRAM_COLUMN_NAME);
}
const baseLayer: PersistedIndexPatternLayer = {
columnOrder,
columns: {
...this.getBaseLayer(
this.layerConfig.dataView ?? chartDataView,
this.layerConfig.options ?? {}
),
},
};
return {
[layerId]: this.column.reduce(
(acc, curr, index) => ({
...acc,
...curr.getData(
`${accessorId}_${index}`,
acc,
this.layerConfig.dataView ?? chartDataView,
formulaAPI
),
}),
baseLayer
),
};
}
getReference(layerId: string, chartDataView: DataView): SavedObjectReference[] {
return getDefaultReferences(this.layerConfig.dataView ?? chartDataView, layerId);
}
getLayerConfig(layerId: string, accessorId: string): XYDataLayerConfig {
return {
layerId,
seriesType: this.layerConfig.options?.seriesType ?? 'line',
accessors: this.column.map((_, index) => `${accessorId}_${index}`),
yConfig: this.layerConfig.data
.map(({ color }, index) =>
color ? { forAccessor: `${accessorId}_${index}`, color } : undefined
)
.filter(nonNullable),
layerType: 'data',
xAccessor:
this.layerConfig.options?.buckets?.type === 'date_histogram'
? HISTOGRAM_COLUMN_NAME
: undefined,
splitAccessor:
this.layerConfig.options?.breakdown?.type === 'top_values'
? BREAKDOWN_COLUMN_NAME
: undefined,
};
}
getDataView(): DataView | undefined {
return this.layerConfig.dataView;
}
}

View file

@ -1,8 +1,9 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import type { SavedObjectReference } from '@kbn/core/server'; import type { SavedObjectReference } from '@kbn/core/server';
@ -12,42 +13,42 @@ import type {
PersistedIndexPatternLayer, PersistedIndexPatternLayer,
XYReferenceLineLayerConfig, XYReferenceLineLayerConfig,
} from '@kbn/lens-plugin/public'; } from '@kbn/lens-plugin/public';
import type { ChartColumn, ChartLayer, FormulaConfig } from '../../../types'; import type { ChartLayer, StaticValueConfig, StaticChartColumn } from '../../types';
import { getDefaultReferences } from '../../utils'; import { getDefaultReferences } from '../../utils';
import { ReferenceLineColumn } from './column/reference_line'; import { ReferenceLineColumn } from './columns/reference_line';
interface XYReferenceLinesLayerConfig { interface XYReferenceLinesLayerConfig {
data: FormulaConfig[]; data: StaticValueConfig[];
/**
* It is possible to define a specific dataView for the layer. It will override the global chart one
**/
dataView?: DataView;
} }
export class XYReferenceLinesLayer implements ChartLayer<XYReferenceLineLayerConfig> { export class XYReferenceLinesLayer implements ChartLayer<XYReferenceLineLayerConfig> {
private column: ChartColumn[]; private column: StaticChartColumn[];
constructor(layerConfig: XYReferenceLinesLayerConfig) { constructor(private layerConfig: XYReferenceLinesLayerConfig) {
this.column = layerConfig.data.map((p) => new ReferenceLineColumn(p)); this.column = layerConfig.data.map((p) => new ReferenceLineColumn(p));
} }
getName(): string | undefined { getName(): string | undefined {
return this.column[0].getFormulaConfig().label; return this.column[0].getValueConfig().label;
} }
getLayer( getLayer(layerId: string, accessorId: string): FormBasedPersistedState['layers'] {
layerId: string,
accessorId: string,
dataView: DataView
): FormBasedPersistedState['layers'] {
const baseLayer = { columnOrder: [], columns: {} } as PersistedIndexPatternLayer; const baseLayer = { columnOrder: [], columns: {} } as PersistedIndexPatternLayer;
return { return {
[`${layerId}_reference`]: this.column.reduce((acc, curr, index) => { [`${layerId}_reference`]: this.column.reduce((acc, curr, index) => {
return { return {
...acc, ...acc,
...curr.getData(`${accessorId}_${index}_reference_column`, acc, dataView), ...curr.getData(`${accessorId}_${index}_reference_column`, acc),
}; };
}, baseLayer), }, baseLayer),
}; };
} }
getReference(layerId: string, dataView: DataView): SavedObjectReference[] { getReference(layerId: string, chartDataView: DataView): SavedObjectReference[] {
return getDefaultReferences(dataView, `${layerId}_reference`); return getDefaultReferences(this.layerConfig.dataView ?? chartDataView, `${layerId}_reference`);
} }
getLayerConfig(layerId: string, accessorId: string): XYReferenceLineLayerConfig { getLayerConfig(layerId: string, accessorId: string): XYReferenceLineLayerConfig {
@ -56,10 +57,14 @@ export class XYReferenceLinesLayer implements ChartLayer<XYReferenceLineLayerCon
layerType: 'referenceLine', layerType: 'referenceLine',
accessors: this.column.map((_, index) => `${accessorId}_${index}_reference_column`), accessors: this.column.map((_, index) => `${accessorId}_${index}_reference_column`),
yConfig: this.column.map((layer, index) => ({ yConfig: this.column.map((layer, index) => ({
color: layer.getFormulaConfig().color, color: layer.getValueConfig().color,
forAccessor: `${accessorId}_${index}_reference_column`, forAccessor: `${accessorId}_${index}_reference_column`,
axisMode: 'left', axisMode: 'left',
})), })),
}; };
} }
getDataView(): DataView | undefined {
return this.layerConfig.dataView;
}
} }

View file

@ -1,17 +1,17 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import type { FormBasedPersistedState, MetricVisualizationState } from '@kbn/lens-plugin/public'; import type { FormBasedPersistedState, MetricVisualizationState } from '@kbn/lens-plugin/public';
import type { SavedObjectReference } from '@kbn/core/server'; import type { SavedObjectReference } from '@kbn/core/server';
import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public';
import type { Chart, ChartConfig, ChartLayer } from '../types';
import { DEFAULT_LAYER_ID } from '../utils'; import { DEFAULT_LAYER_ID } from '../utils';
import type { Chart, ChartConfig, ChartLayer } from '../../types';
const ACCESSOR = 'metric_formula_accessor'; const ACCESSOR = 'metric_formula_accessor';
export class MetricChart implements Chart<MetricVisualizationState> { export class MetricChart implements Chart<MetricVisualizationState> {
@ -22,7 +22,12 @@ export class MetricChart implements Chart<MetricVisualizationState> {
} }
getLayers(): FormBasedPersistedState['layers'] { getLayers(): FormBasedPersistedState['layers'] {
return this.chartConfig.layers.getLayer(DEFAULT_LAYER_ID, ACCESSOR, this.chartConfig.dataView); return this.chartConfig.layers.getLayer(
DEFAULT_LAYER_ID,
ACCESSOR,
this.chartConfig.dataView,
this.chartConfig.formulaAPI
);
} }
getVisualizationState(): MetricVisualizationState { getVisualizationState(): MetricVisualizationState {
@ -33,8 +38,10 @@ export class MetricChart implements Chart<MetricVisualizationState> {
return this.chartConfig.layers.getReference(DEFAULT_LAYER_ID, this.chartConfig.dataView); return this.chartConfig.layers.getReference(DEFAULT_LAYER_ID, this.chartConfig.dataView);
} }
getDataView(): DataView { getDataViews(): DataView[] {
return this.chartConfig.dataView; return [this.chartConfig.dataView, this.chartConfig.layers.getDataView()].filter(
(x): x is DataView => !!x
);
} }
getTitle(): string { getTitle(): string {

View file

@ -1,8 +1,9 @@
/* /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License * 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 and the Server Side Public License, v 1; you may not use this file except
* 2.0. * in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ */
import type { import type {
@ -13,8 +14,8 @@ import type {
} from '@kbn/lens-plugin/public'; } from '@kbn/lens-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public';
import type { SavedObjectReference } from '@kbn/core/server'; import type { SavedObjectReference } from '@kbn/core/server';
import type { Chart, ChartConfig, ChartLayer } from '../types';
import { DEFAULT_LAYER_ID } from '../utils'; import { DEFAULT_LAYER_ID } from '../utils';
import type { Chart, ChartConfig, ChartLayer } from '../../types';
const ACCESSOR = 'formula_accessor'; const ACCESSOR = 'formula_accessor';
@ -27,6 +28,7 @@ export interface XYVisualOptions {
} }
export class XYChart implements Chart<XYState> { export class XYChart implements Chart<XYState> {
private _layers: Array<ChartLayer<XYLayerConfig>> | null = null;
constructor( constructor(
private chartConfig: ChartConfig<Array<ChartLayer<XYLayerConfig>>> & { private chartConfig: ChartConfig<Array<ChartLayer<XYLayerConfig>>> & {
visualOptions?: XYVisualOptions; visualOptions?: XYVisualOptions;
@ -37,13 +39,28 @@ export class XYChart implements Chart<XYState> {
return 'lnsXY'; return 'lnsXY';
} }
private get layers() {
if (!this._layers) {
this._layers = Array.isArray(this.chartConfig.layers)
? this.chartConfig.layers
: [this.chartConfig.layers];
}
return this._layers;
}
getLayers(): FormBasedPersistedState['layers'] { getLayers(): FormBasedPersistedState['layers'] {
return this.chartConfig.layers.reduce((acc, curr, index) => { return this.layers.reduce((acc, curr, index) => {
const layerId = `${DEFAULT_LAYER_ID}_${index}`; const layerId = `${DEFAULT_LAYER_ID}_${index}`;
const accessorId = `${ACCESSOR}_${index}`; const accessorId = `${ACCESSOR}_${index}`;
return { return {
...acc, ...acc,
...curr.getLayer(layerId, accessorId, this.chartConfig.dataView), ...curr.getLayer(
layerId,
accessorId,
this.chartConfig.dataView,
this.chartConfig.formulaAPI
),
}; };
}, {}); }, {});
} }
@ -59,26 +76,29 @@ export class XYChart implements Chart<XYState> {
}), }),
], ],
}), }),
fittingFunction: this.chartConfig.visualOptions?.missingValues ?? 'Zero', fittingFunction: this.chartConfig.visualOptions?.missingValues ?? 'None',
endValue: this.chartConfig.visualOptions?.endValues, endValue: this.chartConfig.visualOptions?.endValues,
curveType: this.chartConfig.visualOptions?.lineInterpolation ?? 'LINEAR', curveType: this.chartConfig.visualOptions?.lineInterpolation,
emphasizeFitting: !this.chartConfig.visualOptions?.showDottedLine, emphasizeFitting: !this.chartConfig.visualOptions?.showDottedLine,
}; };
} }
getReferences(): SavedObjectReference[] { getReferences(): SavedObjectReference[] {
return this.chartConfig.layers.flatMap((p, index) => { return this.layers.flatMap((p, index) => {
const layerId = `${DEFAULT_LAYER_ID}_${index}`; const layerId = `${DEFAULT_LAYER_ID}_${index}`;
return p.getReference(layerId, this.chartConfig.dataView); return p.getReference(layerId, this.chartConfig.dataView);
}); });
} }
getDataView(): DataView { getDataViews(): DataView[] {
return this.chartConfig.dataView; return [
this.chartConfig.dataView,
...this.chartConfig.layers.map((p) => p.getDataView()).filter((x): x is DataView => !!x),
];
} }
getTitle(): string { getTitle(): string {
return this.chartConfig.title ?? this.chartConfig.layers[0].getName() ?? ''; return this.chartConfig.title ?? this.layers[0].getName() ?? '';
} }
} }

View file

@ -0,0 +1,27 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './attribute_builder/types';
export type {
MetricLayerOptions,
XYLayerOptions,
XYVisualOptions,
} from './attribute_builder/visualization_types';
export {
FormulaDataColumn,
MetricChart,
MetricLayer,
ReferenceLineColumn,
XYChart,
XYDataLayer,
XYReferenceLinesLayer,
} from './attribute_builder/visualization_types';
export { LensAttributesBuilder } from './attribute_builder/lens_attributes_builder';

View file

@ -0,0 +1,14 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-lens-embeddable-utils'],
setupFiles: ['jest-canvas-mock'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-browser",
"id": "@kbn/lens-embeddable-utils",
"owner": "@elastic/infra-monitoring-ui"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/lens-embeddable-utils",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": ["jest", "node"]
},
"include": ["**/*.ts"],
"exclude": ["target/**/*"],
"kbn_references": ["@kbn/core", "@kbn/data-plugin", "@kbn/data-views-plugin", "@kbn/lens-plugin"]
}

View file

@ -914,6 +914,8 @@
"@kbn/kubernetes-security-plugin/*": ["x-pack/plugins/kubernetes_security/*"], "@kbn/kubernetes-security-plugin/*": ["x-pack/plugins/kubernetes_security/*"],
"@kbn/language-documentation-popover": ["packages/kbn-language-documentation-popover"], "@kbn/language-documentation-popover": ["packages/kbn-language-documentation-popover"],
"@kbn/language-documentation-popover/*": ["packages/kbn-language-documentation-popover/*"], "@kbn/language-documentation-popover/*": ["packages/kbn-language-documentation-popover/*"],
"@kbn/lens-embeddable-utils": ["packages/kbn-lens-embeddable-utils"],
"@kbn/lens-embeddable-utils/*": ["packages/kbn-lens-embeddable-utils/*"],
"@kbn/lens-plugin": ["x-pack/plugins/lens"], "@kbn/lens-plugin": ["x-pack/plugins/lens"],
"@kbn/lens-plugin/*": ["x-pack/plugins/lens/*"], "@kbn/lens-plugin/*": ["x-pack/plugins/lens/*"],
"@kbn/license-api-guard-plugin": ["x-pack/plugins/license_api_guard"], "@kbn/license-api-guard-plugin": ["x-pack/plugins/license_api_guard"],

View file

@ -9,14 +9,6 @@ export type {
HostsLensFormulas, HostsLensFormulas,
HostsLensMetricChartFormulas, HostsLensMetricChartFormulas,
HostsLensLineChartFormulas, HostsLensLineChartFormulas,
LensAttributes,
FormulaConfig,
Chart,
LensVisualizationState,
} from './types'; } from './types';
export { hostLensFormulas } from './constants'; export { hostLensFormulas } from './constants';
export * from './lens/visualization_types';
export { LensAttributesBuilder } from './lens/lens_attributes_builder';

View file

@ -7,13 +7,10 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import type { Layer } from '../../../../../hooks/use_lens_attributes'; import { UseLensAttributesMetricLayerConfig } from '../../../../../hooks/use_lens_attributes';
import { hostLensFormulas } from '../../../constants'; import { hostLensFormulas } from '../../../constants';
import { TOOLTIP } from './translations'; import { TOOLTIP } from './translations';
import type { FormulaConfig } from '../../../types';
import type { MetricLayerOptions } from '../../visualization_types';
export const KPI_CHART_HEIGHT = 150; export const KPI_CHART_HEIGHT = 150;
export const AVERAGE_SUBTITLE = i18n.translate( export const AVERAGE_SUBTITLE = i18n.translate(
'xpack.infra.assetDetailsEmbeddable.overview.kpi.subtitle.average', 'xpack.infra.assetDetailsEmbeddable.overview.kpi.subtitle.average',
@ -23,7 +20,7 @@ export const AVERAGE_SUBTITLE = i18n.translate(
); );
export interface KPIChartProps extends Pick<TypedLensByValueInput, 'id' | 'title' | 'overrides'> { export interface KPIChartProps extends Pick<TypedLensByValueInput, 'id' | 'title' | 'overrides'> {
layers: Layer<MetricLayerOptions, FormulaConfig, 'data'>; layers: UseLensAttributesMetricLayerConfig;
toolTip: string; toolTip: string;
} }
@ -36,12 +33,14 @@ export const KPI_CHARTS: KPIChartProps[] = [
layers: { layers: {
data: { data: {
...hostLensFormulas.cpuUsage, ...hostLensFormulas.cpuUsage,
format: { format: hostLensFormulas.cpuUsage.format
...hostLensFormulas.cpuUsage.format, ? {
params: { ...hostLensFormulas.cpuUsage.format,
decimals: 1, params: {
}, decimals: 1,
}, },
}
: undefined,
}, },
layerType: 'data', layerType: 'data',
options: { options: {
@ -62,12 +61,14 @@ export const KPI_CHARTS: KPIChartProps[] = [
layers: { layers: {
data: { data: {
...hostLensFormulas.normalizedLoad1m, ...hostLensFormulas.normalizedLoad1m,
format: { format: hostLensFormulas.normalizedLoad1m.format
...hostLensFormulas.normalizedLoad1m.format, ? {
params: { ...hostLensFormulas.normalizedLoad1m.format,
decimals: 1, params: {
}, decimals: 1,
}, },
}
: undefined,
}, },
layerType: 'data', layerType: 'data',
options: { options: {
@ -85,12 +86,14 @@ export const KPI_CHARTS: KPIChartProps[] = [
layers: { layers: {
data: { data: {
...hostLensFormulas.memoryUsage, ...hostLensFormulas.memoryUsage,
format: { format: hostLensFormulas.memoryUsage.format
...hostLensFormulas.memoryUsage.format, ? {
params: { ...hostLensFormulas.memoryUsage.format,
decimals: 1, params: {
}, decimals: 1,
}, },
}
: undefined,
}, },
layerType: 'data', layerType: 'data',
options: { options: {
@ -108,12 +111,14 @@ export const KPI_CHARTS: KPIChartProps[] = [
layers: { layers: {
data: { data: {
...hostLensFormulas.diskSpaceUsage, ...hostLensFormulas.diskSpaceUsage,
format: { format: hostLensFormulas.diskSpaceUsage.format
...hostLensFormulas.diskSpaceUsage.format, ? {
params: { ...hostLensFormulas.diskSpaceUsage.format,
decimals: 1, params: {
}, decimals: 1,
}, },
}
: undefined,
}, },
layerType: 'data', layerType: 'data',
options: { options: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const cpuUsage: FormulaConfig = { export const cpuUsage: FormulaValueConfig = {
label: 'CPU Usage', label: 'CPU Usage',
value: '(average(system.cpu.user.pct) + average(system.cpu.system.pct)) / max(system.cpu.cores)', value: '(average(system.cpu.user.pct) + average(system.cpu.system.pct)) / max(system.cpu.cores)',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const diskIORead: FormulaConfig = { export const diskIORead: FormulaValueConfig = {
label: 'Disk Read IOPS', label: 'Disk Read IOPS',
value: "counter_rate(max(system.diskio.read.count), kql='system.diskio.read.count: *')", value: "counter_rate(max(system.diskio.read.count), kql='system.diskio.read.count: *')",
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const diskReadThroughput: FormulaConfig = { export const diskReadThroughput: FormulaValueConfig = {
label: 'Disk Read Throughput', label: 'Disk Read Throughput',
value: "counter_rate(max(system.diskio.read.bytes), kql='system.diskio.read.bytes: *')", value: "counter_rate(max(system.diskio.read.bytes), kql='system.diskio.read.bytes: *')",
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const diskSpaceAvailability: FormulaConfig = { export const diskSpaceAvailability: FormulaValueConfig = {
label: 'Disk Space Availability', label: 'Disk Space Availability',
value: '1 - average(system.filesystem.used.pct)', value: '1 - average(system.filesystem.used.pct)',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const diskSpaceAvailable: FormulaConfig = { export const diskSpaceAvailable: FormulaValueConfig = {
label: 'Disk Space Available', label: 'Disk Space Available',
value: 'average(system.filesystem.free)', value: 'average(system.filesystem.free)',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const diskSpaceUsage: FormulaConfig = { export const diskSpaceUsage: FormulaValueConfig = {
label: 'Disk Space Usage', label: 'Disk Space Usage',
value: 'average(system.filesystem.used.pct)', value: 'average(system.filesystem.used.pct)',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const diskIOWrite: FormulaConfig = { export const diskIOWrite: FormulaValueConfig = {
label: 'Disk Write IOPS', label: 'Disk Write IOPS',
value: "counter_rate(max(system.diskio.write.count), kql='system.diskio.write.count: *')", value: "counter_rate(max(system.diskio.write.count), kql='system.diskio.write.count: *')",
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const diskWriteThroughput: FormulaConfig = { export const diskWriteThroughput: FormulaValueConfig = {
label: 'Disk Write Throughput', label: 'Disk Write Throughput',
value: "counter_rate(max(system.diskio.write.bytes), kql='system.diskio.write.bytes: *')", value: "counter_rate(max(system.diskio.write.bytes), kql='system.diskio.write.bytes: *')",
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const hostCount: FormulaConfig = { export const hostCount: FormulaValueConfig = {
label: 'Hosts', label: 'Hosts',
value: 'unique_count(host.name)', value: 'unique_count(host.name)',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const logRate: FormulaConfig = { export const logRate: FormulaValueConfig = {
label: 'Log Rate', label: 'Log Rate',
value: 'differences(cumulative_sum(count()))', value: 'differences(cumulative_sum(count()))',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const memoryFree: FormulaConfig = { export const memoryFree: FormulaValueConfig = {
label: 'Memory Free', label: 'Memory Free',
value: 'max(system.memory.total) - average(system.memory.actual.used.bytes)', value: 'max(system.memory.total) - average(system.memory.actual.used.bytes)',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const memoryUsage: FormulaConfig = { export const memoryUsage: FormulaValueConfig = {
label: 'Memory Usage', label: 'Memory Usage',
value: 'average(system.memory.actual.used.pct)', value: 'average(system.memory.actual.used.pct)',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const normalizedLoad1m: FormulaConfig = { export const normalizedLoad1m: FormulaValueConfig = {
label: 'Normalized Load', label: 'Normalized Load',
value: 'average(system.load.1) / max(system.load.cores)', value: 'average(system.load.1) / max(system.load.cores)',
format: { format: {

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const rx: FormulaConfig = { export const rx: FormulaValueConfig = {
label: 'Network Inbound (RX)', label: 'Network Inbound (RX)',
value: value:
"average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)", "average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)",

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { FormulaConfig } from '../../../types'; import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
export const tx: FormulaConfig = { export const tx: FormulaValueConfig = {
label: 'Network Outbound (TX)', label: 'Network Outbound (TX)',
value: value:
"average(host.network.egress.bytes) * 8 / (max(metricset.period, kql='host.network.egress.bytes: *') / 1000)", "average(host.network.egress.bytes) * 8 / (max(metricset.period, kql='host.network.egress.bytes: *') / 1000)",

View file

@ -1,108 +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 type { SavedObjectReference } from '@kbn/core/server';
import type { DataView } from '@kbn/data-views-plugin/common';
import type {
FormulaPublicApi,
FormBasedPersistedState,
PersistedIndexPatternLayer,
XYDataLayerConfig,
SeriesType,
} from '@kbn/lens-plugin/public';
import type { ChartColumn, ChartLayer, FormulaConfig } from '../../../types';
import { getDefaultReferences, getHistogramColumn, getTopValuesColumn } from '../../utils';
import { FormulaColumn } from './column/formula';
const BREAKDOWN_COLUMN_NAME = 'aggs_breakdown';
const HISTOGRAM_COLUMN_NAME = 'x_date_histogram';
export interface XYLayerOptions {
breakdown?: {
size: number;
sourceField: string;
};
seriesType?: SeriesType;
}
interface XYLayerConfig {
data: FormulaConfig[];
options?: XYLayerOptions;
formulaAPI: FormulaPublicApi;
}
export class XYDataLayer implements ChartLayer<XYDataLayerConfig> {
private column: ChartColumn[];
constructor(private layerConfig: XYLayerConfig) {
this.column = layerConfig.data.map((p) => new FormulaColumn(p, layerConfig.formulaAPI));
}
getName(): string | undefined {
return this.column[0].getFormulaConfig().label;
}
getBaseLayer(dataView: DataView, options?: XYLayerOptions) {
return {
...getHistogramColumn({
columnName: HISTOGRAM_COLUMN_NAME,
overrides: {
sourceField: dataView.timeFieldName,
},
}),
...(options?.breakdown
? {
...getTopValuesColumn({
columnName: BREAKDOWN_COLUMN_NAME,
overrides: {
sourceField: options?.breakdown.sourceField,
breakdownSize: options?.breakdown.size,
},
}),
}
: {}),
};
}
getLayer(
layerId: string,
accessorId: string,
dataView: DataView
): FormBasedPersistedState['layers'] {
const baseLayer: PersistedIndexPatternLayer = {
columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME],
columns: {
...this.getBaseLayer(dataView, this.layerConfig.options),
},
};
return {
[layerId]: this.column.reduce(
(acc, curr, index) => ({
...acc,
...curr.getData(`${accessorId}_${index}`, acc, dataView),
}),
baseLayer
),
};
}
getReference(layerId: string, dataView: DataView): SavedObjectReference[] {
return getDefaultReferences(dataView, layerId);
}
getLayerConfig(layerId: string, accessorId: string): XYDataLayerConfig {
return {
layerId,
seriesType: this.layerConfig.options?.seriesType ?? 'line',
accessors: this.column.map((_, index) => `${accessorId}_${index}`),
yConfig: [],
layerType: 'data',
xAccessor: HISTOGRAM_COLUMN_NAME,
splitAccessor: this.layerConfig.options?.breakdown ? BREAKDOWN_COLUMN_NAME : undefined,
};
}
}

View file

@ -5,75 +5,7 @@
* 2.0. * 2.0.
*/ */
import type { SavedObjectReference } from '@kbn/core/server';
import type { DataView } from '@kbn/data-views-plugin/common';
import type {
FormBasedPersistedState,
MetricVisualizationState,
PersistedIndexPatternLayer,
TypedLensByValueInput,
XYState,
FormulaPublicApi,
XYLayerConfig,
} from '@kbn/lens-plugin/public';
import { hostLensFormulas } from './constants'; import { hostLensFormulas } from './constants';
export type LensAttributes = TypedLensByValueInput['attributes'];
// Attributes
export type LensVisualizationState = XYState | MetricVisualizationState;
export interface VisualizationAttributesBuilder {
build(): LensAttributes;
}
// Column
export interface ChartColumn {
getData(
id: string,
baseLayer: PersistedIndexPatternLayer,
dataView: DataView
): PersistedIndexPatternLayer;
getFormulaConfig(): FormulaConfig;
}
// Layer
export type LensLayerConfig = XYLayerConfig | MetricVisualizationState;
export interface ChartLayer<TLayerConfig extends LensLayerConfig> {
getName(): string | undefined;
getLayer(
layerId: string,
accessorId: string,
dataView: DataView
): FormBasedPersistedState['layers'];
getReference(layerId: string, dataView: DataView): SavedObjectReference[];
getLayerConfig(layerId: string, acessorId: string): TLayerConfig;
}
// Chart
export interface Chart<TVisualizationState extends LensVisualizationState> {
getTitle(): string;
getVisualizationType(): string;
getLayers(): FormBasedPersistedState['layers'];
getVisualizationState(): TVisualizationState;
getReferences(): SavedObjectReference[];
getDataView(): DataView;
}
export interface ChartConfig<
TLayer extends ChartLayer<LensLayerConfig> | Array<ChartLayer<LensLayerConfig>>
> {
dataView: DataView;
layers: TLayer;
title?: string;
}
// Formula
type LensFormula = Parameters<FormulaPublicApi['insertOrReplaceFormulaColumn']>[1];
export type FormulaConfig = Omit<LensFormula, 'format' | 'formula'> & {
color?: string;
format: NonNullable<LensFormula['format']>;
value: string;
};
export type HostsLensFormulas = keyof typeof hostLensFormulas; export type HostsLensFormulas = keyof typeof hostLensFormulas;
export type HostsLensMetricChartFormulas = Exclude<HostsLensFormulas, 'diskIORead' | 'diskIOWrite'>; export type HostsLensMetricChartFormulas = Exclude<HostsLensFormulas, 'diskIORead' | 'diskIOWrite'>;

View file

@ -11,22 +11,17 @@ import { i18n } from '@kbn/i18n';
import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public';
import type { TimeRange } from '@kbn/es-query'; import type { TimeRange } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { HostMetricsExplanationContent } from '../../../../lens/metric_explanation/host_metrics_explanation_content'; import type { XYVisualOptions } from '@kbn/lens-embeddable-utils';
import { UseLensAttributesXYLayerConfig } from '../../../../../hooks/use_lens_attributes';
import { buildCombinedHostsFilter } from '../../../../../utils/filters/build'; import { buildCombinedHostsFilter } from '../../../../../utils/filters/build';
import type { Layer } from '../../../../../hooks/use_lens_attributes'; import { LensChart, type LensChartProps, HostMetricsExplanationContent } from '../../../../lens';
import { LensChart, type LensChartProps } from '../../../../lens'; import { hostLensFormulas } from '../../../../../common/visualizations';
import {
type FormulaConfig,
hostLensFormulas,
type XYLayerOptions,
type XYVisualOptions,
} from '../../../../../common/visualizations';
import { METRIC_CHART_HEIGHT } from '../../../constants'; import { METRIC_CHART_HEIGHT } from '../../../constants';
import { Popover } from '../../common/popover'; import { Popover } from '../../common/popover';
type DataViewOrigin = 'logs' | 'metrics'; type DataViewOrigin = 'logs' | 'metrics';
interface MetricChartConfig extends Pick<LensChartProps, 'id' | 'title' | 'overrides'> { interface MetricChartConfig extends Pick<LensChartProps, 'id' | 'title' | 'overrides'> {
layers: Array<Layer<XYLayerOptions, FormulaConfig[]>>; layers: UseLensAttributesXYLayerConfig;
toolTip: string; toolTip: string;
} }

View file

@ -11,10 +11,10 @@ import type { TimeRange } from '@kbn/es-query';
import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import { useEuiTheme } from '@elastic/eui'; import { useEuiTheme } from '@elastic/eui';
import { LensAttributes } from '@kbn/lens-embeddable-utils';
import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
import { ChartLoadingProgress, ChartPlaceholder } from './chart_placeholder'; import { ChartLoadingProgress, ChartPlaceholder } from './chart_placeholder';
import { parseDateRange } from '../../utils/datemath'; import { parseDateRange } from '../../utils/datemath';
import { LensAttributes } from '../../common/visualizations';
export type LensWrapperProps = Omit< export type LensWrapperProps = Omit<
TypedLensByValueInput, TypedLensByValueInput,

View file

@ -57,9 +57,15 @@ describe('useHostTable hook', () => {
data: [normalizedLoad1m], data: [normalizedLoad1m],
layerType: 'data', layerType: 'data',
options: { options: {
buckets: {
type: 'date_histogram',
},
breakdown: { breakdown: {
size: 10, field: 'host.name',
sourceField: 'host.name', type: 'top_values',
params: {
size: 10,
},
}, },
}, },
}, },

View file

@ -12,13 +12,14 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import useAsync from 'react-use/lib/useAsync'; import useAsync from 'react-use/lib/useAsync';
import { FormulaPublicApi, LayerType as LensLayerType } from '@kbn/lens-plugin/public'; import { FormulaPublicApi } from '@kbn/lens-plugin/public';
import { InfraClientSetupDeps } from '../types';
import { import {
type XYLayerOptions, type XYLayerOptions,
type MetricLayerOptions, type MetricLayerOptions,
type FormulaConfig, type FormulaValueConfig,
type LensAttributes, type LensAttributes,
type StaticValueConfig,
type LensVisualizationState,
type XYVisualOptions, type XYVisualOptions,
type Chart, type Chart,
LensAttributesBuilder, LensAttributesBuilder,
@ -27,48 +28,48 @@ import {
XYChart, XYChart,
MetricChart, MetricChart,
XYReferenceLinesLayer, XYReferenceLinesLayer,
LensVisualizationState, } from '@kbn/lens-embeddable-utils';
} from '../common/visualizations';
import { InfraClientSetupDeps } from '../types';
import { useLazyRef } from './use_lazy_ref'; import { useLazyRef } from './use_lazy_ref';
type LayerOptions = XYLayerOptions | MetricLayerOptions; type Options = XYLayerOptions | MetricLayerOptions;
type ChartType = 'lnsXY' | 'lnsMetric';
type VisualOptions = XYVisualOptions;
export type LayerType = Exclude<LensLayerType, 'annotations' | 'metricTrendline'>;
export interface Layer< interface StaticValueLayer {
TLayerOptions extends LayerOptions, data: StaticValueConfig[];
TFormulaConfig extends FormulaConfig | FormulaConfig[], layerType: 'referenceLine';
TLayerType extends LayerType = LayerType
> {
layerType: TLayerType;
data: TFormulaConfig;
options?: TLayerOptions;
} }
interface UseLensAttributesBaseParams< interface FormulaValueLayer<
TOptions extends LayerOptions, TOptions extends Options,
TLayers extends Array<Layer<TOptions, FormulaConfig[]>> | Layer<TOptions, FormulaConfig> TData extends FormulaValueConfig[] | FormulaValueConfig
> { > {
options?: TOptions;
data: TData;
layerType: 'data';
}
type XYLayerConfig = StaticValueLayer | FormulaValueLayer<XYLayerOptions, FormulaValueConfig[]>;
export type UseLensAttributesXYLayerConfig = XYLayerConfig | XYLayerConfig[];
export type UseLensAttributesMetricLayerConfig = FormulaValueLayer<
MetricLayerOptions,
FormulaValueConfig
>;
interface UseLensAttributesBaseParams {
dataView?: DataView; dataView?: DataView;
layers: TLayers;
title?: string; title?: string;
} }
interface UseLensAttributesXYChartParams interface UseLensAttributesXYChartParams extends UseLensAttributesBaseParams {
extends UseLensAttributesBaseParams< layers: UseLensAttributesXYLayerConfig;
XYLayerOptions,
Array<Layer<XYLayerOptions, FormulaConfig[], 'data' | 'referenceLine'>>
> {
visualizationType: 'lnsXY'; visualizationType: 'lnsXY';
visualOptions?: XYVisualOptions; visualOptions?: XYVisualOptions;
} }
interface UseLensAttributesMetricChartParams interface UseLensAttributesMetricChartParams extends UseLensAttributesBaseParams {
extends UseLensAttributesBaseParams< layers: UseLensAttributesMetricLayerConfig;
MetricLayerOptions,
Layer<MetricLayerOptions, FormulaConfig, 'data'>
> {
visualizationType: 'lnsMetric'; visualizationType: 'lnsMetric';
} }
@ -76,13 +77,7 @@ export type UseLensAttributesParams =
| UseLensAttributesXYChartParams | UseLensAttributesXYChartParams
| UseLensAttributesMetricChartParams; | UseLensAttributesMetricChartParams;
export const useLensAttributes = ({ export const useLensAttributes = ({ dataView, ...params }: UseLensAttributesParams) => {
dataView,
layers,
title,
visualizationType,
...extraParams
}: UseLensAttributesParams) => {
const { const {
services: { lens }, services: { lens },
} = useKibana<InfraClientSetupDeps>(); } = useKibana<InfraClientSetupDeps>();
@ -99,10 +94,7 @@ export const useLensAttributes = ({
visualization: chartFactory({ visualization: chartFactory({
dataView, dataView,
formulaAPI, formulaAPI,
layers, ...params,
title,
visualizationType,
...extraParams,
}), }),
}); });
@ -163,9 +155,9 @@ export const useLensAttributes = ({
); );
const getFormula = () => { const getFormula = () => {
const firstDataLayer = [...(Array.isArray(layers) ? layers : [layers])].find( const firstDataLayer = [
(p) => p.layerType === 'data' ...(Array.isArray(params.layers) ? params.layers : [params.layers]),
); ].find((p) => p.layerType === 'data');
if (!firstDataLayer) { if (!firstDataLayer) {
return ''; return '';
@ -181,80 +173,70 @@ export const useLensAttributes = ({
return { formula: getFormula(), attributes: attributes.current, getExtraActions, error }; return { formula: getFormula(), attributes: attributes.current, getExtraActions, error };
}; };
const chartFactory = < const chartFactory = ({
TOptions,
TLayers extends Array<Layer<TOptions, FormulaConfig[]>> | Layer<TOptions, FormulaConfig>
>({
dataView, dataView,
formulaAPI, formulaAPI,
layers, ...params
title,
visualizationType,
visualOptions,
}: { }: {
dataView: DataView; dataView: DataView;
formulaAPI: FormulaPublicApi; formulaAPI: FormulaPublicApi;
visualizationType: ChartType; } & UseLensAttributesParams): Chart<LensVisualizationState> => {
layers: TLayers; switch (params.visualizationType) {
title?: string;
visualOptions?: VisualOptions;
}): Chart<LensVisualizationState> => {
switch (visualizationType) {
case 'lnsXY': case 'lnsXY':
if (!Array.isArray(layers)) { if (!Array.isArray(params.layers)) {
throw new Error(`Invalid layers type. Expected an array of layers.`); throw new Error(`Invalid layers type. Expected an array of layers.`);
} }
const getLayerClass = (layerType: LayerType) => { const xyLayerFactory = (layer: XYLayerConfig) => {
switch (layerType) { switch (layer.layerType) {
case 'data': { case 'data': {
return XYDataLayer; return new XYDataLayer({
data: layer.data,
options: layer.options,
});
} }
case 'referenceLine': { case 'referenceLine': {
return XYReferenceLinesLayer; return new XYReferenceLinesLayer({
data: layer.data,
});
} }
default: default:
throw new Error(`Invalid layerType: ${layerType}`); throw new Error(`Invalid layerType`);
} }
}; };
return new XYChart({ return new XYChart({
dataView, dataView,
layers: layers.map((layerItem) => { formulaAPI,
const Layer = getLayerClass(layerItem.layerType); layers: params.layers.map((layerItem) => {
return new Layer({ return xyLayerFactory(layerItem);
data: layerItem.data,
formulaAPI,
options: layerItem.options,
});
}), }),
title, title: params.title,
visualOptions, visualOptions: params.visualOptions,
}); });
case 'lnsMetric': case 'lnsMetric':
if (Array.isArray(layers)) { if (Array.isArray(params.layers)) {
throw new Error(`Invalid layers type. Expected a single layer object.`); throw new Error(`Invalid layers type. Expected a single layer object.`);
} }
return new MetricChart({ return new MetricChart({
dataView, dataView,
formulaAPI,
layers: new MetricLayer({ layers: new MetricLayer({
data: layers.data, data: params.layers.data,
formulaAPI, options: params.layers.options,
options: layers.options,
}), }),
title, title: params.title,
}); });
default: default:
throw new Error(`Unsupported chart type: ${visualizationType}`); throw new Error(`Unsupported chart type`);
} }
}; };
const getOpenInLensAction = (onExecute: () => void): Action => { const getOpenInLensAction = (onExecute: () => void): Action => {
return { return {
id: 'openInLens', id: 'openInLens',
getDisplayName(_context: ActionExecutionContext): string { getDisplayName(_context: ActionExecutionContext): string {
return i18n.translate('xpack.infra.hostsViewPage.tabs.metricsCharts.actions.openInLines', { return i18n.translate('xpack.infra.hostsViewPage.tabs.metricsCharts.actions.openInLines', {
defaultMessage: 'Open in Lens', defaultMessage: 'Open in Lens',

View file

@ -6,15 +6,11 @@
*/ */
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import type { XYVisualOptions } from '@kbn/lens-embeddable-utils';
import { LensChart } from '../../../../../../components/lens'; import { LensChart } from '../../../../../../components/lens';
import type { Layer } from '../../../../../../hooks/use_lens_attributes'; import type { UseLensAttributesXYLayerConfig } from '../../../../../../hooks/use_lens_attributes';
import { useMetricsDataViewContext } from '../../../hooks/use_data_view'; import { useMetricsDataViewContext } from '../../../hooks/use_data_view';
import { useUnifiedSearchContext } from '../../../hooks/use_unified_search'; import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
import type {
FormulaConfig,
XYLayerOptions,
XYVisualOptions,
} from '../../../../../../common/visualizations';
import { useHostsViewContext } from '../../../hooks/use_hosts_view'; import { useHostsViewContext } from '../../../hooks/use_hosts_view';
import { buildCombinedHostsFilter } from '../../../../../../utils/filters/build'; import { buildCombinedHostsFilter } from '../../../../../../utils/filters/build';
import { useHostsTableContext } from '../../../hooks/use_hosts_table'; import { useHostsTableContext } from '../../../hooks/use_hosts_table';
@ -23,7 +19,7 @@ import { METRIC_CHART_HEIGHT } from '../../../constants';
export interface MetricChartProps extends Pick<TypedLensByValueInput, 'id' | 'overrides'> { export interface MetricChartProps extends Pick<TypedLensByValueInput, 'id' | 'overrides'> {
title: string; title: string;
layers: Array<Layer<XYLayerOptions, FormulaConfig[]>>; layers: UseLensAttributesXYLayerConfig;
visualOptions?: XYVisualOptions; visualOptions?: XYVisualOptions;
} }

View file

@ -6,15 +6,10 @@
*/ */
import React from 'react'; import React from 'react';
import { EuiFlexGrid, EuiFlexItem, EuiText } from '@elastic/eui'; import { EuiFlexGrid, EuiFlexItem, EuiText, EuiFlexGroup, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { EuiSpacer } from '@elastic/eui'; import type { XYLayerOptions, XYVisualOptions } from '@kbn/lens-embeddable-utils';
import { EuiFlexGroup } from '@elastic/eui'; import { hostLensFormulas } from '../../../../../../common/visualizations';
import {
hostLensFormulas,
type XYVisualOptions,
type XYLayerOptions,
} from '../../../../../../common/visualizations';
import { HostMetricsExplanationContent } from '../../../../../../components/lens'; import { HostMetricsExplanationContent } from '../../../../../../components/lens';
import { MetricChart, MetricChartProps } from './metric_chart'; import { MetricChart, MetricChartProps } from './metric_chart';
import { Popover } from '../../table/popover'; import { Popover } from '../../table/popover';
@ -22,8 +17,11 @@ import { Popover } from '../../table/popover';
const DEFAULT_BREAKDOWN_SIZE = 20; const DEFAULT_BREAKDOWN_SIZE = 20;
const XY_LAYER_OPTIONS: XYLayerOptions = { const XY_LAYER_OPTIONS: XYLayerOptions = {
breakdown: { breakdown: {
size: DEFAULT_BREAKDOWN_SIZE, type: 'top_values',
sourceField: 'host.name', field: 'host.name',
params: {
size: DEFAULT_BREAKDOWN_SIZE,
},
}, },
}; };

View file

@ -69,6 +69,7 @@
"@kbn/logs-shared-plugin", "@kbn/logs-shared-plugin",
"@kbn/licensing-plugin", "@kbn/licensing-plugin",
"@kbn/aiops-utils", "@kbn/aiops-utils",
"@kbn/lens-embeddable-utils"
], ],
"exclude": ["target/**/*"] "exclude": ["target/**/*"]
} }

View file

@ -5984,6 +5984,10 @@
version "0.0.0" version "0.0.0"
uid "" uid ""
"@kbn/lens-embeddable-utils@link:packages/kbn-lens-embeddable-utils":
version "0.0.0"
uid ""
"@kbn/visualizations-plugin@link:src/plugins/visualizations": "@kbn/visualizations-plugin@link:src/plugins/visualizations":
version "0.0.0" version "0.0.0"
uid "" uid ""