Skip to content

Commit

Permalink
[Lens] New lens config builder api (elastic#169750)
Browse files Browse the repository at this point in the history
## Summary

resolves elastic#163293

Exposes config builder API to build lens configurations via much simpler
API which hides the complexity of lens and allows developers to easily
configure the chart.

sample usage:
```

const builder = new LensConfigBuilder(formulaPublicAPI, dataViewsPublicAPI);
const embeddableInput = await builder.build(
    {
      chartType: 'heatmap',
      title: 'test',
      dataset: {
        esql: 'from kibana_sample_data_ecommerce | count=count() by order_date, product.category.keyword',
      },
      layers: [
        {
          label: 'test',
          breakdown: 'product.category.keyword',
          xAxis: 'order_date',
          value: 'count',
        },
      ],
    }, {
      embeddable: true,
    }
  );
```

pr with sample app: elastic#171282

---------

Co-authored-by: Stratoula Kalafateli <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored Dec 6, 2023
1 parent 1022ccd commit 11451b4
Show file tree
Hide file tree
Showing 37 changed files with 3,787 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ src/plugins/kibana_usage_collection @elastic/kibana-core
src/plugins/kibana_utils @elastic/appex-sharedux
x-pack/plugins/kubernetes_security @elastic/kibana-cloud-security-posture
packages/kbn-language-documentation-popover @elastic/kibana-visualizations
packages/kbn-lens-embeddable-utils @elastic/obs-ux-infra_services-team
packages/kbn-lens-embeddable-utils @elastic/obs-ux-infra_services-team @elastic/kibana-visualizations
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
Expand Down
127 changes: 127 additions & 0 deletions packages/kbn-lens-embeddable-utils/config_builder/charts/gauge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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 { DataView, DataViewField, DataViewsContract } from '@kbn/data-views-plugin/common';
import { buildGauge } from './gauge';

const dataViews: Record<string, DataView> = {
test: {
id: 'test',
fields: {
getByName: (name: string) => {
switch (name) {
case '@timestamp':
return {
type: 'datetime',
} as unknown as DataViewField;
case 'category':
return {
type: 'string',
} as unknown as DataViewField;
case 'price':
return {
type: 'number',
} as unknown as DataViewField;
default:
return undefined;
}
},
} as any,
} as unknown as DataView,
};

function mockDataViewsService() {
return {
get: jest.fn(async (id: '1' | '2') => {
const result = {
...dataViews[id],
metaFields: [],
isPersisted: () => true,
toSpec: () => ({}),
};
return result;
}),
create: jest.fn(),
} as unknown as Pick<DataViewsContract, 'get' | 'create'>;
}

test('generates gauge chart config', async () => {
const result = await buildGauge(
{
chartType: 'gauge',
title: 'test',
dataset: {
esql: 'from test | count=count()',
},
value: 'count',
},
{
dataViewsAPI: mockDataViewsService() as any,
formulaAPI: {} as any,
}
);
expect(result).toMatchInlineSnapshot(`
Object {
"references": Array [
Object {
"id": "test",
"name": "indexpattern-datasource-layer-layer_0",
"type": "index-pattern",
},
],
"state": Object {
"adHocDataViews": Object {
"test": Object {},
},
"datasourceStates": Object {
"formBased": Object {
"layers": Object {},
},
"textBased": Object {
"layers": Object {
"layer_0": Object {
"allColumns": Array [
Object {
"columnId": "metric_formula_accessor",
"fieldName": "count",
},
],
"columns": Array [
Object {
"columnId": "metric_formula_accessor",
"fieldName": "count",
},
],
"index": "test",
"query": Object {
"esql": "from test | count=count()",
},
},
},
},
},
"filters": Array [],
"internalReferences": Array [],
"query": Object {
"language": "kuery",
"query": "",
},
"visualization": Object {
"labelMajorMode": "auto",
"layerId": "layer_0",
"layerType": "data",
"metricAccessor": "metric_formula_accessor",
"shape": "horizontalBullet",
"ticksPosition": "auto",
},
},
"title": "test",
"visualizationType": "lnsGauge",
}
`);
});
168 changes: 168 additions & 0 deletions packages/kbn-lens-embeddable-utils/config_builder/charts/gauge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* 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 {
FormBasedPersistedState,
FormulaPublicApi,
GaugeVisualizationState,
} from '@kbn/lens-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { BuildDependencies, DEFAULT_LAYER_ID, LensAttributes, LensGaugeConfig } from '../types';
import {
addLayerFormulaColumns,
buildDatasourceStates,
buildReferences,
getAdhocDataviews,
} from '../utils';
import { getFormulaColumn, getValueColumn } from '../columns';

const ACCESSOR = 'metric_formula_accessor';

function getAccessorName(type: 'goal' | 'max' | 'min' | 'secondary') {
return `${ACCESSOR}_${type}`;
}

function buildVisualizationState(config: LensGaugeConfig): GaugeVisualizationState {
const layer = config;

return {
layerId: DEFAULT_LAYER_ID,
layerType: 'data',
ticksPosition: 'auto',
shape: layer.shape || 'horizontalBullet',
labelMajorMode: 'auto',
metricAccessor: ACCESSOR,
...(layer.queryGoalValue
? {
goalAccessor: getAccessorName('goal'),
}
: {}),

...(layer.queryMaxValue
? {
maxAccessor: getAccessorName('max'),
showBar: true,
}
: {}),

...(layer.queryMinValue
? {
minAccessor: getAccessorName('min'),
}
: {}),
};
}

function buildFormulaLayer(
layer: LensGaugeConfig,
i: number,
dataView: DataView,
formulaAPI: FormulaPublicApi
): FormBasedPersistedState['layers'][0] {
const layers = {
[DEFAULT_LAYER_ID]: {
...getFormulaColumn(
ACCESSOR,
{
value: layer.value,
},
dataView,
formulaAPI
),
},
};

const defaultLayer = layers[DEFAULT_LAYER_ID];

if (layer.queryGoalValue) {
const columnName = getAccessorName('goal');
const formulaColumn = getFormulaColumn(
columnName,
{
value: layer.queryGoalValue,
},
dataView,
formulaAPI
);

addLayerFormulaColumns(defaultLayer, formulaColumn);
}

if (layer.queryMinValue) {
const columnName = getAccessorName('min');
const formulaColumn = getFormulaColumn(
columnName,
{
value: layer.queryMinValue,
},
dataView,
formulaAPI
);

addLayerFormulaColumns(defaultLayer, formulaColumn);
}

if (layer.queryMaxValue) {
const columnName = getAccessorName('max');
const formulaColumn = getFormulaColumn(
columnName,
{
value: layer.queryMaxValue,
},
dataView,
formulaAPI
);

addLayerFormulaColumns(defaultLayer, formulaColumn);
}

return defaultLayer;
}

function getValueColumns(layer: LensGaugeConfig) {
return [
getValueColumn(ACCESSOR, layer.value),
...(layer.queryMaxValue ? [getValueColumn(getAccessorName('max'), layer.queryMaxValue)] : []),
...(layer.queryMinValue
? [getValueColumn(getAccessorName('secondary'), layer.queryMinValue)]
: []),
...(layer.queryGoalValue
? [getValueColumn(getAccessorName('secondary'), layer.queryGoalValue)]
: []),
];
}

export async function buildGauge(
config: LensGaugeConfig,
{ dataViewsAPI, formulaAPI }: BuildDependencies
): Promise<LensAttributes> {
const dataviews: Record<string, DataView> = {};
const _buildFormulaLayer = (cfg: unknown, i: number, dataView: DataView) =>
buildFormulaLayer(cfg as LensGaugeConfig, i, dataView, formulaAPI);
const datasourceStates = await buildDatasourceStates(
config,
dataviews,
_buildFormulaLayer,
getValueColumns,
dataViewsAPI
);
return {
title: config.title,
visualizationType: 'lnsGauge',
references: buildReferences(dataviews),
state: {
datasourceStates,
internalReferences: [],
filters: [],
query: { language: 'kuery', query: '' },
visualization: buildVisualizationState(config),
// Getting the spec from a data view is a heavy operation, that's why the result is cached.
adHocDataViews: getAdhocDataviews(dataviews),
},
};
}
Loading

0 comments on commit 11451b4

Please sign in to comment.