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
x-pack/plugins/kubernetes_security @elastic/sec-cloudnative-integrations
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/license_api_guard @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/kubernetes-security-plugin": "link:x-pack/plugins/kubernetes_security",
"@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/license-api-guard-plugin": "link:x-pack/plugins/license_api_guard",
"@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:
@ -22,13 +31,13 @@ const metricChart = new MetricChart({
},
},
},
formulaAPI,
}),
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:
@ -45,13 +54,72 @@ const xyChart = new XYChart({
},
},
}],
formulaAPI,
options: {
buckets: {type: 'date_histogram'},
},
})],
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:
@ -69,10 +137,13 @@ const xyChart = new XYChart({
},
},
}],
formulaAPI,
options: {
buckets: {type: 'date_histogram'},
},
}),
new XYReferenceLineLayer({
data: [{
value: "1",
format: {
id: 'percent',
@ -84,10 +155,11 @@ const xyChart = new XYChart({
}),
],
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.
@ -115,13 +187,16 @@ const xyChart = new XYChart({
},
},
}],
formulaAPI,
options: {
buckets: {type: 'date_histogram'},
},
}),
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:
@ -150,10 +225,10 @@ const builder = new LensAttributesBuilder({
},
},
},
formulaAPI,
}),
dataView,
})
formulaAPI
}),
});
const lensAttributes = builder.build();
@ -163,4 +238,4 @@ const lensAttributes = builder.build();
viewMode={ViewMode.VIEW}
...
/>
```
```

View file

@ -1,13 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
* 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 { 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 {
private static instance: DataViewCache;

View file

@ -1,8 +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.
* 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 'jest-canvas-mock';
@ -19,7 +20,7 @@ import {
} from './visualization_types';
import type { FormulaPublicApi, GenericIndexPatternColumn } from '@kbn/lens-plugin/public';
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 = {
id: 'mock-id',
@ -85,7 +86,7 @@ const REFERENCE_LINE_LAYER: ReferenceBasedIndexPatternColumn = {
scale: 'ratio',
};
const getFormula = (value: string): FormulaConfig => ({
const getFormula = (value: string): FormulaValueConfig => ({
value,
format: {
id: 'percent',
@ -106,10 +107,10 @@ describe('lens_attributes_builder', () => {
const metriChart = new MetricChart({
layers: new MetricLayer({
data: getFormula(AVERAGE_CPU_USER_FORMULA),
formulaAPI,
}),
dataView: mockDataView,
formulaAPI,
});
const builder = new LensAttributesBuilder({ visualization: metriChart });
const {
@ -148,10 +149,10 @@ describe('lens_attributes_builder', () => {
options: {
showTrendLine: true,
},
formulaAPI,
}),
dataView: mockDataView,
formulaAPI,
});
const builder = new LensAttributesBuilder({ visualization: metriChart });
const {
@ -204,10 +205,13 @@ describe('lens_attributes_builder', () => {
layers: [
new XYDataLayer({
data: [getFormula(AVERAGE_CPU_USER_FORMULA)],
formulaAPI,
options: {
buckets: { type: 'date_histogram' },
},
}),
],
dataView: mockDataView,
formulaAPI,
});
const builder = new LensAttributesBuilder({ visualization: xyChart });
const {
@ -248,13 +252,23 @@ describe('lens_attributes_builder', () => {
layers: [
new XYDataLayer({
data: [getFormula(AVERAGE_CPU_USER_FORMULA)],
formulaAPI,
options: {
buckets: { type: 'date_histogram' },
},
}),
new XYReferenceLinesLayer({
data: [getFormula('1')],
data: [
{
value: '1',
format: {
id: 'percent',
},
},
],
}),
],
dataView: mockDataView,
formulaAPI,
});
const builder = new LensAttributesBuilder({ visualization: xyChart });
const {
@ -316,10 +330,13 @@ describe('lens_attributes_builder', () => {
layers: [
new XYDataLayer({
data: [getFormula(AVERAGE_CPU_USER_FORMULA), getFormula(AVERAGE_CPU_SYSTEM_FORMULA)],
formulaAPI,
options: {
buckets: { type: 'date_histogram' },
},
}),
],
dataView: mockDataView,
formulaAPI,
});
const builder = new LensAttributesBuilder({ visualization: xyChart });
const {

View file

@ -1,15 +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.
* 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 {
LensAttributes,
LensVisualizationState,
Chart,
VisualizationAttributesBuilder,
} from '../types';
} from './types';
import { DataViewCache } from './data_view_cache';
import { getAdhocDataView } from './utils';
@ -17,12 +19,12 @@ export class LensAttributesBuilder<T extends Chart<LensVisualizationState>>
implements VisualizationAttributesBuilder
{
private dataViewCache: DataViewCache;
constructor(private state: { visualization: T }) {
constructor(private lens: { visualization: T }) {
this.dataViewCache = DataViewCache.getInstance();
}
build(): LensAttributes {
const { visualization } = this.state;
const { visualization } = this.lens;
return {
title: visualization.getTitle(),
visualizationType: visualization.getVisualizationType(),
@ -34,10 +36,17 @@ export class LensAttributesBuilder<T extends Chart<LensVisualizationState>>
},
},
internalReferences: visualization.getReferences(),
// EmbeddableComponent receive filters.
filters: [],
// EmbeddableComponent receive query.
query: { language: 'kuery', query: '' },
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
* 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.
* 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 {
import type {
DateHistogramIndexPatternColumn,
PersistedIndexPatternLayer,
TermsIndexPatternColumn,
@ -17,12 +19,27 @@ export const DEFAULT_AD_HOC_DATA_VIEW_ID = 'infra_lens_ad_hoc_default';
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 = ({
columnName,
overrides,
options,
}: {
columnName: string;
overrides?: Partial<Pick<DateHistogramIndexPatternColumn, 'sourceField' | 'params'>>;
options?: Partial<
Pick<DateHistogramIndexPatternColumn, 'sourceField'> & {
params: DateHistogramColumnParams;
}
>;
}) => {
return {
[columnName]: {
@ -32,32 +49,32 @@ export const getHistogramColumn = ({
operationType: 'date_histogram',
scale: 'interval',
sourceField: '@timestamp',
...overrides,
params: { interval: 'auto', ...overrides?.params },
...options,
params: { interval: 'auto', ...options?.params },
} as DateHistogramIndexPatternColumn,
};
};
export const getTopValuesColumn = ({
columnName,
overrides,
field,
options,
}: {
columnName: string;
overrides?: Partial<Pick<TermsIndexPatternColumn, 'sourceField'>> & {
breakdownSize?: number;
};
field: string;
options?: Partial<TopValuesColumnParams>;
}): PersistedIndexPatternLayer['columns'] => {
const { breakdownSize = DEFAULT_BREAKDOWN_SIZE, sourceField } = overrides ?? {};
const { size = DEFAULT_BREAKDOWN_SIZE, ...params } = options ?? {};
return {
[columnName]: {
label: `Top ${breakdownSize} values of ${sourceField}`,
label: `Top ${size} values of ${field}`,
dataType: 'string',
operationType: 'terms',
scale: 'ordinal',
sourceField,
sourceField: field,
isBucketed: true,
params: {
size: breakdownSize,
size,
orderBy: {
type: 'alphabetical',
fallback: false,
@ -72,6 +89,7 @@ export const getTopValuesColumn = ({
exclude: [],
includeIsRegex: false,
excludeIsRegex: false,
...params,
},
} as TermsIndexPatternColumn,
};

View file

@ -1,8 +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.
* 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 { 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
* 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.
* 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 { FormulaPublicApi, PersistedIndexPatternLayer } from '@kbn/lens-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 {
constructor(private formulaConfig: FormulaConfig, private formulaAPI: FormulaPublicApi) {}
constructor(private valueConfig: FormulaValueConfig) {}
getFormulaConfig(): FormulaConfig {
return this.formulaConfig;
getValueConfig(): FormulaValueConfig {
return this.valueConfig;
}
getData(
id: string,
baseLayer: PersistedIndexPatternLayer,
dataView: DataView
dataView: DataView,
formulaAPI: FormulaPublicApi
): PersistedIndexPatternLayer {
const { value, ...rest } = this.getFormulaConfig();
const formulaLayer = this.formulaAPI.insertOrReplaceFormulaColumn(
const { value, ...rest } = this.getValueConfig();
const formulaLayer = formulaAPI.insertOrReplaceFormulaColumn(
id,
{ formula: value, ...rest },
baseLayer,

View file

@ -1,23 +1,24 @@
/*
* 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.
* 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 { PersistedIndexPatternLayer } from '@kbn/lens-plugin/public';
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 {
constructor(private formulaConfig: FormulaConfig) {}
export class ReferenceLineColumn implements StaticChartColumn {
constructor(private valueConfig: StaticValueConfig) {}
getFormulaConfig(): FormulaConfig {
return this.formulaConfig;
getValueConfig(): StaticValueConfig {
return this.valueConfig;
}
getData(id: string, baseLayer: PersistedIndexPatternLayer): PersistedIndexPatternLayer {
const { label, ...params } = this.getFormulaConfig();
const { label, ...params } = this.getValueConfig();
return {
linkToLayers: [],
columnOrder: [...baseLayer.columnOrder, id],

View file

@ -1,13 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
* 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 { MetricLayer, type MetricLayerOptions } from './metric_layer';
export { XYDataLayer, type XYLayerOptions } from './xy_data_layer';
export { XYReferenceLinesLayer } from './xy_reference_lines_layer';
export { FormulaColumn as FormulaDataColumn } from './column/formula';
export { ReferenceLineColumn } from './column/reference_line';
export { FormulaColumn as FormulaDataColumn } from './columns/formula';
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
* 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.
* 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';
@ -13,9 +14,9 @@ import type {
MetricVisualizationState,
PersistedIndexPatternLayer,
} 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 { FormulaColumn } from './column/formula';
import { FormulaColumn } from './columns/formula';
const HISTOGRAM_COLUMN_NAME = 'x_date_histogram';
@ -27,28 +28,32 @@ export interface MetricLayerOptions {
}
interface MetricLayerConfig {
data: FormulaConfig;
data: FormulaValueConfig;
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> {
private column: ChartColumn;
constructor(private layerConfig: MetricLayerConfig) {
this.column = new FormulaColumn(layerConfig.data, layerConfig.formulaAPI);
this.column = new FormulaColumn(layerConfig.data);
}
getLayer(
layerId: string,
accessorId: string,
dataView: DataView
chartDataView: DataView,
formulaAPI: FormulaPublicApi
): FormBasedPersistedState['layers'] {
const baseLayer: PersistedIndexPatternLayer = {
columnOrder: [HISTOGRAM_COLUMN_NAME],
columns: getHistogramColumn({
columnName: HISTOGRAM_COLUMN_NAME,
overrides: {
sourceField: dataView.timeFieldName,
options: {
sourceField: (this.layerConfig.dataView ?? chartDataView).timeFieldName,
params: {
interval: 'auto',
includeEmptyRows: true,
@ -66,23 +71,29 @@ export class MetricLayer implements ChartLayer<MetricVisualizationState> {
columnOrder: [],
columns: {},
},
dataView
this.layerConfig.dataView ?? chartDataView,
formulaAPI
),
},
...(this.layerConfig.options?.showTrendLine
? {
[`${layerId}_trendline`]: {
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 [
...getDefaultReferences(dataView, layerId),
...getDefaultReferences(dataView, `${layerId}_trendline`),
...getDefaultReferences(this.layerConfig.dataView ?? chartDataView, layerId),
...getDefaultReferences(this.layerConfig.dataView ?? chartDataView, `${layerId}_trendline`),
];
}
@ -107,6 +118,10 @@ export class MetricLayer implements ChartLayer<MetricVisualizationState> {
};
}
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
* 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.
* 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';
@ -12,42 +13,42 @@ import type {
PersistedIndexPatternLayer,
XYReferenceLineLayerConfig,
} from '@kbn/lens-plugin/public';
import type { ChartColumn, ChartLayer, FormulaConfig } from '../../../types';
import type { ChartLayer, StaticValueConfig, StaticChartColumn } from '../../types';
import { getDefaultReferences } from '../../utils';
import { ReferenceLineColumn } from './column/reference_line';
import { ReferenceLineColumn } from './columns/reference_line';
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> {
private column: ChartColumn[];
constructor(layerConfig: XYReferenceLinesLayerConfig) {
private column: StaticChartColumn[];
constructor(private layerConfig: XYReferenceLinesLayerConfig) {
this.column = layerConfig.data.map((p) => new ReferenceLineColumn(p));
}
getName(): string | undefined {
return this.column[0].getFormulaConfig().label;
return this.column[0].getValueConfig().label;
}
getLayer(
layerId: string,
accessorId: string,
dataView: DataView
): FormBasedPersistedState['layers'] {
getLayer(layerId: string, accessorId: string): FormBasedPersistedState['layers'] {
const baseLayer = { columnOrder: [], columns: {} } as PersistedIndexPatternLayer;
return {
[`${layerId}_reference`]: this.column.reduce((acc, curr, index) => {
return {
...acc,
...curr.getData(`${accessorId}_${index}_reference_column`, acc, dataView),
...curr.getData(`${accessorId}_${index}_reference_column`, acc),
};
}, baseLayer),
};
}
getReference(layerId: string, dataView: DataView): SavedObjectReference[] {
return getDefaultReferences(dataView, `${layerId}_reference`);
getReference(layerId: string, chartDataView: DataView): SavedObjectReference[] {
return getDefaultReferences(this.layerConfig.dataView ?? chartDataView, `${layerId}_reference`);
}
getLayerConfig(layerId: string, accessorId: string): XYReferenceLineLayerConfig {
@ -56,10 +57,14 @@ export class XYReferenceLinesLayer implements ChartLayer<XYReferenceLineLayerCon
layerType: 'referenceLine',
accessors: this.column.map((_, index) => `${accessorId}_${index}_reference_column`),
yConfig: this.column.map((layer, index) => ({
color: layer.getFormulaConfig().color,
color: layer.getValueConfig().color,
forAccessor: `${accessorId}_${index}_reference_column`,
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
* 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.
* 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 { FormBasedPersistedState, MetricVisualizationState } from '@kbn/lens-plugin/public';
import type { SavedObjectReference } from '@kbn/core/server';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { Chart, ChartConfig, ChartLayer } from '../types';
import { DEFAULT_LAYER_ID } from '../utils';
import type { Chart, ChartConfig, ChartLayer } from '../../types';
const ACCESSOR = 'metric_formula_accessor';
export class MetricChart implements Chart<MetricVisualizationState> {
@ -22,7 +22,12 @@ export class MetricChart implements Chart<MetricVisualizationState> {
}
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 {
@ -33,8 +38,10 @@ export class MetricChart implements Chart<MetricVisualizationState> {
return this.chartConfig.layers.getReference(DEFAULT_LAYER_ID, this.chartConfig.dataView);
}
getDataView(): DataView {
return this.chartConfig.dataView;
getDataViews(): DataView[] {
return [this.chartConfig.dataView, this.chartConfig.layers.getDataView()].filter(
(x): x is DataView => !!x
);
}
getTitle(): string {

View file

@ -1,8 +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.
* 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 {
@ -13,8 +14,8 @@ import type {
} from '@kbn/lens-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { SavedObjectReference } from '@kbn/core/server';
import type { Chart, ChartConfig, ChartLayer } from '../types';
import { DEFAULT_LAYER_ID } from '../utils';
import type { Chart, ChartConfig, ChartLayer } from '../../types';
const ACCESSOR = 'formula_accessor';
@ -27,6 +28,7 @@ export interface XYVisualOptions {
}
export class XYChart implements Chart<XYState> {
private _layers: Array<ChartLayer<XYLayerConfig>> | null = null;
constructor(
private chartConfig: ChartConfig<Array<ChartLayer<XYLayerConfig>>> & {
visualOptions?: XYVisualOptions;
@ -37,13 +39,28 @@ export class XYChart implements Chart<XYState> {
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'] {
return this.chartConfig.layers.reduce((acc, curr, index) => {
return this.layers.reduce((acc, curr, index) => {
const layerId = `${DEFAULT_LAYER_ID}_${index}`;
const accessorId = `${ACCESSOR}_${index}`;
return {
...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,
curveType: this.chartConfig.visualOptions?.lineInterpolation ?? 'LINEAR',
curveType: this.chartConfig.visualOptions?.lineInterpolation,
emphasizeFitting: !this.chartConfig.visualOptions?.showDottedLine,
};
}
getReferences(): SavedObjectReference[] {
return this.chartConfig.layers.flatMap((p, index) => {
return this.layers.flatMap((p, index) => {
const layerId = `${DEFAULT_LAYER_ID}_${index}`;
return p.getReference(layerId, this.chartConfig.dataView);
});
}
getDataView(): DataView {
return this.chartConfig.dataView;
getDataViews(): DataView[] {
return [
this.chartConfig.dataView,
...this.chartConfig.layers.map((p) => p.getDataView()).filter((x): x is DataView => !!x),
];
}
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/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/license-api-guard-plugin": ["x-pack/plugins/license_api_guard"],

View file

@ -9,14 +9,6 @@ export type {
HostsLensFormulas,
HostsLensMetricChartFormulas,
HostsLensLineChartFormulas,
LensAttributes,
FormulaConfig,
Chart,
LensVisualizationState,
} from './types';
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 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 { TOOLTIP } from './translations';
import type { FormulaConfig } from '../../../types';
import type { MetricLayerOptions } from '../../visualization_types';
export const KPI_CHART_HEIGHT = 150;
export const AVERAGE_SUBTITLE = i18n.translate(
'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'> {
layers: Layer<MetricLayerOptions, FormulaConfig, 'data'>;
layers: UseLensAttributesMetricLayerConfig;
toolTip: string;
}
@ -36,12 +33,14 @@ export const KPI_CHARTS: KPIChartProps[] = [
layers: {
data: {
...hostLensFormulas.cpuUsage,
format: {
...hostLensFormulas.cpuUsage.format,
params: {
decimals: 1,
},
},
format: hostLensFormulas.cpuUsage.format
? {
...hostLensFormulas.cpuUsage.format,
params: {
decimals: 1,
},
}
: undefined,
},
layerType: 'data',
options: {
@ -62,12 +61,14 @@ export const KPI_CHARTS: KPIChartProps[] = [
layers: {
data: {
...hostLensFormulas.normalizedLoad1m,
format: {
...hostLensFormulas.normalizedLoad1m.format,
params: {
decimals: 1,
},
},
format: hostLensFormulas.normalizedLoad1m.format
? {
...hostLensFormulas.normalizedLoad1m.format,
params: {
decimals: 1,
},
}
: undefined,
},
layerType: 'data',
options: {
@ -85,12 +86,14 @@ export const KPI_CHARTS: KPIChartProps[] = [
layers: {
data: {
...hostLensFormulas.memoryUsage,
format: {
...hostLensFormulas.memoryUsage.format,
params: {
decimals: 1,
},
},
format: hostLensFormulas.memoryUsage.format
? {
...hostLensFormulas.memoryUsage.format,
params: {
decimals: 1,
},
}
: undefined,
},
layerType: 'data',
options: {
@ -108,12 +111,14 @@ export const KPI_CHARTS: KPIChartProps[] = [
layers: {
data: {
...hostLensFormulas.diskSpaceUsage,
format: {
...hostLensFormulas.diskSpaceUsage.format,
params: {
decimals: 1,
},
},
format: hostLensFormulas.diskSpaceUsage.format
? {
...hostLensFormulas.diskSpaceUsage.format,
params: {
decimals: 1,
},
}
: undefined,
},
layerType: 'data',
options: {

View file

@ -5,9 +5,9 @@
* 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',
value: '(average(system.cpu.user.pct) + average(system.cpu.system.pct)) / max(system.cpu.cores)',
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: "counter_rate(max(system.diskio.read.count), kql='system.diskio.read.count: *')",
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: "counter_rate(max(system.diskio.read.bytes), kql='system.diskio.read.bytes: *')",
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: '1 - average(system.filesystem.used.pct)',
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: 'average(system.filesystem.free)',
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: 'average(system.filesystem.used.pct)',
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: "counter_rate(max(system.diskio.write.count), kql='system.diskio.write.count: *')",
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: "counter_rate(max(system.diskio.write.bytes), kql='system.diskio.write.bytes: *')",
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: 'unique_count(host.name)',
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: 'differences(cumulative_sum(count()))',
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: 'max(system.memory.total) - average(system.memory.actual.used.bytes)',
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: 'average(system.memory.actual.used.pct)',
format: {

View file

@ -5,9 +5,9 @@
* 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',
value: 'average(system.load.1) / max(system.load.cores)',
format: {

View file

@ -5,9 +5,9 @@
* 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)',
value:
"average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)",

View file

@ -5,9 +5,9 @@
* 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)',
value:
"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.
*/
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';
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 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 { TimeRange } from '@kbn/es-query';
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 type { Layer } from '../../../../../hooks/use_lens_attributes';
import { LensChart, type LensChartProps } from '../../../../lens';
import {
type FormulaConfig,
hostLensFormulas,
type XYLayerOptions,
type XYVisualOptions,
} from '../../../../../common/visualizations';
import { LensChart, type LensChartProps, HostMetricsExplanationContent } from '../../../../lens';
import { hostLensFormulas } from '../../../../../common/visualizations';
import { METRIC_CHART_HEIGHT } from '../../../constants';
import { Popover } from '../../common/popover';
type DataViewOrigin = 'logs' | 'metrics';
interface MetricChartConfig extends Pick<LensChartProps, 'id' | 'title' | 'overrides'> {
layers: Array<Layer<XYLayerOptions, FormulaConfig[]>>;
layers: UseLensAttributesXYLayerConfig;
toolTip: string;
}

View file

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

View file

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

View file

@ -6,15 +6,11 @@
*/
import React, { useMemo } from 'react';
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import type { XYVisualOptions } from '@kbn/lens-embeddable-utils';
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 { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
import type {
FormulaConfig,
XYLayerOptions,
XYVisualOptions,
} from '../../../../../../common/visualizations';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
import { buildCombinedHostsFilter } from '../../../../../../utils/filters/build';
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'> {
title: string;
layers: Array<Layer<XYLayerOptions, FormulaConfig[]>>;
layers: UseLensAttributesXYLayerConfig;
visualOptions?: XYVisualOptions;
}

View file

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

View file

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

View file

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