diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts index 301374747f8b..59b2ae473cac 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts @@ -48,6 +48,9 @@ const names: Record = { visualizations: i18n.translate('advancedSettings.categoryNames.visualizationsLabel', { defaultMessage: 'Visualizations', }), + visbuilder: i18n.translate('advancedSettings.categoryNames.visbuilderLabel', { + defaultMessage: 'VisBuilder', + }), discover: i18n.translate('advancedSettings.categoryNames.discoverLabel', { defaultMessage: 'Discover', }), diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index 2062cb2a6fe7..f936d1eff65c 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -134,3 +134,4 @@ export { UnmappedTypeStrings, ExpressionValueRender as Render, } from '../common'; +export { getExpressionsService } from './services'; diff --git a/src/plugins/vis_builder/common/constants.ts b/src/plugins/vis_builder/common/constants.ts new file mode 100644 index 000000000000..0a3d2b6cf41b --- /dev/null +++ b/src/plugins/vis_builder/common/constants.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const VISBUILDER_ENABLE_VEGA_SETTING = 'visbuilder:enableVega'; diff --git a/src/plugins/vis_builder/public/application/components/workspace.tsx b/src/plugins/vis_builder/public/application/components/workspace.tsx index 62f92b835050..2596c149169c 100644 --- a/src/plugins/vis_builder/public/application/components/workspace.tsx +++ b/src/plugins/vis_builder/public/application/components/workspace.tsx @@ -13,6 +13,7 @@ import { validateSchemaState, validateAggregations } from '../utils/validations' import { useTypedDispatch, useTypedSelector, setUIStateState } from '../utils/state_management'; import { useAggs, useVisualizationType } from '../utils/use'; import { PersistedState } from '../../../../visualizations/public'; +import { VISBUILDER_ENABLE_VEGA_SETTING } from '../../../common/constants'; import hand_field from '../../assets/hand_field.svg'; import fields_bg from '../../assets/fields_bg.svg'; @@ -27,6 +28,7 @@ export const WorkspaceUI = () => { notifications: { toasts }, data, uiActions, + uiSettings, }, } = useOpenSearchDashboards(); const { toExpression, ui } = useVisualizationType(); @@ -37,6 +39,7 @@ export const WorkspaceUI = () => { filters: data.query.filterManager.getFilters(), timeRange: data.query.timefilter.timefilter.getTime(), }); + const useVega = uiSettings.get(VISBUILDER_ENABLE_VEGA_SETTING); const rootState = useTypedSelector((state) => state); const dispatch = useTypedDispatch(); // Visualizations require the uiState object to persist even when the expression changes @@ -81,12 +84,20 @@ export const WorkspaceUI = () => { return; } - const exp = await toExpression(rootState, searchContext); + const exp = await toExpression(rootState, searchContext, useVega); setExpression(exp); } loadExpression(); - }, [rootState, toExpression, toasts, ui.containerConfig.data.schemas, searchContext, aggConfigs]); + }, [ + rootState, + toExpression, + toasts, + ui.containerConfig.data.schemas, + searchContext, + aggConfigs, + useVega, + ]); useLayoutEffect(() => { const subscription = data.query.state$.subscribe(({ state }) => { diff --git a/src/plugins/vis_builder/public/plugin.ts b/src/plugins/vis_builder/public/plugin.ts index 87ac09588d4d..20b13281e53b 100644 --- a/src/plugins/vis_builder/public/plugin.ts +++ b/src/plugins/vis_builder/public/plugin.ts @@ -56,6 +56,7 @@ import { withNotifyOnErrors, } from '../../opensearch_dashboards_utils/public'; import { opensearchFilters } from '../../data/public'; +import { createRawDataVisFn } from './visualizations/vega/utils/expression_helper'; export class VisBuilderPlugin implements @@ -74,7 +75,7 @@ export class VisBuilderPlugin public setup( core: CoreSetup, - { embeddable, visualizations, data }: VisBuilderPluginSetupDependencies + { embeddable, visualizations, data, expressions: exp }: VisBuilderPluginSetupDependencies ) { const { appMounted, appUnMounted, stop: stopUrlTracker } = createOsdUrlTracker({ baseUrl: core.http.basePath.prepend(`/app/${PLUGIN_ID}`), @@ -107,6 +108,7 @@ export class VisBuilderPlugin // Register Default Visualizations const typeService = this.typeService; registerDefaultTypes(typeService.setup()); + exp.registerFunction(createRawDataVisFn()); // Register the plugin to core core.application.register({ diff --git a/src/plugins/vis_builder/public/services/type_service/types.ts b/src/plugins/vis_builder/public/services/type_service/types.ts index bc0a5cfe6c61..0c232829431c 100644 --- a/src/plugins/vis_builder/public/services/type_service/types.ts +++ b/src/plugins/vis_builder/public/services/type_service/types.ts @@ -31,6 +31,7 @@ export interface VisualizationTypeOptions { }; readonly toExpression: ( state: RenderState, - searchContext: IExpressionLoaderParams['searchContext'] + searchContext: IExpressionLoaderParams['searchContext'], + useVega: boolean ) => Promise; } diff --git a/src/plugins/vis_builder/public/services/type_service/visualization_type.tsx b/src/plugins/vis_builder/public/services/type_service/visualization_type.tsx index 0c2fadf3cf38..dfa76faa32dc 100644 --- a/src/plugins/vis_builder/public/services/type_service/visualization_type.tsx +++ b/src/plugins/vis_builder/public/services/type_service/visualization_type.tsx @@ -18,7 +18,8 @@ export class VisualizationType implements IVisualizationType { public readonly ui: IVisualizationType['ui']; public readonly toExpression: ( state: RenderState, - searchContext: IExpressionLoaderParams['searchContext'] + searchContext: IExpressionLoaderParams['searchContext'], + useVega: boolean ) => Promise; constructor(options: VisualizationTypeOptions) { diff --git a/src/plugins/vis_builder/public/types.ts b/src/plugins/vis_builder/public/types.ts index 61088400d92d..a930fdcbbc8d 100644 --- a/src/plugins/vis_builder/public/types.ts +++ b/src/plugins/vis_builder/public/types.ts @@ -8,7 +8,7 @@ import { SavedObject, SavedObjectsStart } from '../../saved_objects/public'; import { EmbeddableSetup, EmbeddableStart } from '../../embeddable/public'; import { DashboardStart } from '../../dashboard/public'; import { VisualizationsSetup } from '../../visualizations/public'; -import { ExpressionsStart } from '../../expressions/public'; +import { ExpressionsStart, ExpressionsPublicPlugin } from '../../expressions/public'; import { NavigationPublicPluginStart } from '../../navigation/public'; import { DataPublicPluginStart } from '../../data/public'; import { TypeServiceSetup, TypeServiceStart } from './services/type_service'; @@ -28,6 +28,7 @@ export interface VisBuilderPluginSetupDependencies { embeddable: EmbeddableSetup; visualizations: VisualizationsSetup; data: DataPublicPluginSetup; + expressions: ReturnType; } export interface VisBuilderPluginStartDependencies { embeddable: EmbeddableStart; diff --git a/src/plugins/vis_builder/public/visualizations/common/expression_helpers.ts b/src/plugins/vis_builder/public/visualizations/common/expression_helpers.ts index f50ab9172cdb..29b9645c8f0c 100644 --- a/src/plugins/vis_builder/public/visualizations/common/expression_helpers.ts +++ b/src/plugins/vis_builder/public/visualizations/common/expression_helpers.ts @@ -7,9 +7,8 @@ import { cloneDeep } from 'lodash'; import { OpenSearchaggsExpressionFunctionDefinition } from '../../../../data/public'; import { ExpressionFunctionOpenSearchDashboards } from '../../../../expressions'; import { buildExpressionFunction } from '../../../../expressions/public'; -import { VisualizationState } from '../../application/utils/state_management'; +import { VisualizationState, StyleState } from '../../application/utils/state_management'; import { getSearchService, getIndexPatterns } from '../../plugin_services'; -import { StyleState } from '../../application/utils/state_management'; export const getAggExpressionFunctions = async ( visualization: VisualizationState, @@ -49,3 +48,47 @@ export const getAggExpressionFunctions = async ( expressionFns: [opensearchDashboards, opensearchaggs], }; }; + +export const getAggExpressionFunctionsWithContext = async ( + visualization: VisualizationState, + style?: StyleState, + searchContext?: IExpressionLoaderParams['searchContext'] +) => { + const { activeVisualization, indexPattern: indexId = '' } = visualization; + const { aggConfigParams } = activeVisualization || {}; + + const indexPatternsService = getIndexPatterns(); + const indexPattern = await indexPatternsService.get(indexId); + const aggConfigs = getSearchService().aggs.createAggConfigs( + indexPattern, + cloneDeep(aggConfigParams) + ); + + const opensearchDashboards = buildExpressionFunction( + 'opensearchDashboards', + {} + ); + + const opensearchDashboardsContext = buildExpressionFunction('opensearch_dashboards_context', { + timeRange: JSON.stringify(searchContext?.timeRange || {}), + filters: JSON.stringify(searchContext?.filters || []), + query: JSON.stringify(searchContext?.query || []), + }); + + const opensearchaggs = buildExpressionFunction( + 'opensearchaggs', + { + index: indexId, + metricsAtAllLevels: style?.showMetricsAtAllLevels || false, + partialRows: style?.showPartialRows || false, + aggConfigs: JSON.stringify(aggConfigs.aggs), + includeFormatHints: false, + } + ); + + return { + aggConfigs, + indexPattern, + expressionFns: [opensearchDashboards, opensearchDashboardsContext, opensearchaggs], + }; +}; diff --git a/src/plugins/vis_builder/public/visualizations/vega/build_spec_vega.ts b/src/plugins/vis_builder/public/visualizations/vega/build_spec_vega.ts new file mode 100644 index 000000000000..f1c7a4fcd22a --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/build_spec_vega.ts @@ -0,0 +1,126 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { buildEncoding } from './components/encoding'; +import { buildMark } from './components/mark'; +import { buildLegend } from './components/legend'; +import { VegaSpec, AxisFormats } from './utils/types'; +import { StyleState } from '../../application/utils/state_management'; + +/** + * Builds a Vega specification based on the provided data, visual configuration, and style. + * + * @param {object} data - The data object containing series and axis information. + * @param {any} visConfig - The visual configuration settings. + * @param {StyleState} style - The style configuration for the visualization. + * @returns {VegaSpec} The complete Vega specification. + */ +export const buildVegaSpecViaVega = (data: any, visConfig: any, style: StyleState): VegaSpec => { + const { dimensions, addLegend, legendPosition } = visConfig; + const { type } = style; + const { + xAxisFormat, + xAxisLabel, + yAxisFormat, + yAxisLabel, + zAxisFormat, + series: transformedData, + } = data; + + const formats: AxisFormats = { + xAxisFormat, + xAxisLabel, + yAxisFormat, + yAxisLabel, + zAxisFormat, + }; + + const spec: VegaSpec = { + $schema: 'https://vega.github.io/schema/vega/v5.json', + padding: 5, + data: [ + { + name: 'source', + values: transformedData, + }, + { + name: 'splits', + source: 'source', + transform: [ + { + type: 'aggregate', + groupby: ['split'], + }, + ], + }, + ], + signals: [ + { name: 'splitCount', update: 'length(data("splits"))' }, + { name: 'chartWidth', update: 'width / splitCount - 10' }, + ], + scales: [ + { + name: 'splitScale', + type: 'band', + domain: { data: 'splits', field: 'split' }, + range: 'width', + padding: 0.1, + }, + { + name: 'color', + type: 'ordinal', + domain: { data: 'source', field: 'series' }, + range: 'category', + }, + ], + layout: { + columns: { signal: 'splitCount' }, + padding: { row: 40, column: 20 }, + }, + marks: [ + { + type: 'group', + from: { data: 'splits' }, + encode: { + enter: { + width: { signal: 'chartWidth' }, + height: { signal: 'height' }, + stroke: { value: '#ccc' }, + strokeWidth: { value: 1 }, + }, + }, + signals: [{ name: 'width', update: 'chartWidth' }], + scales: buildEncoding(dimensions, formats, true), + axes: [ + { + orient: 'bottom', + scale: 'xscale', + zindex: 1, + labelAngle: -90, + labelAlign: 'right', + labelBaseline: 'middle', + }, + { orient: 'left', scale: 'yscale', zindex: 1 }, + ], + title: { + text: { signal: 'parent.split' }, + anchor: 'middle', + offset: 10, + limit: { signal: 'chartWidth' }, + wrap: true, + align: 'center', + }, + marks: buildMark(type, true), + }, + ], + }; + + // Add legend if specified + if (addLegend) { + spec.legends = [buildLegend(legendPosition, true)]; + } + + return spec; +}; diff --git a/src/plugins/vis_builder/public/visualizations/vega/build_spec_vega_lite.ts b/src/plugins/vis_builder/public/visualizations/vega/build_spec_vega_lite.ts new file mode 100644 index 000000000000..c457f8f17b52 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/build_spec_vega_lite.ts @@ -0,0 +1,87 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { buildEncoding } from './components/encoding'; +import { buildMark } from './components/mark'; +import { buildTooltip } from './components/tooltip'; +import { buildLegend } from './components/legend'; +import { StyleState } from '../../application/utils/state_management'; +import { VegaLiteSpec, AxisFormats } from './utils/types'; + +/** + * Builds a Vega-Lite specification based on the provided data, visual configuration, and style. + * + * @param {any} data - The data configuration, normally including axis formats and transformed data. + * @param {any} visConfig - The visual configuration including dimensions and display options. + * @param {StyleState} style - The StyleState defined in style slice. + * @returns {VegaLiteSpec} The complete Vega-Lite specification. + */ +export const buildVegaSpecViaVegaLite = ( + data: any, + visConfig: any, + style: StyleState +): VegaLiteSpec => { + const { dimensions, addLegend, legendPosition, addTooltip } = visConfig; + const { type } = style; + const { + xAxisFormat, + xAxisLabel, + yAxisFormat, + yAxisLabel, + zAxisFormat, + series: transformedData, + } = data; + + const formats: AxisFormats = { + xAxisFormat, + xAxisLabel, + yAxisFormat, + yAxisLabel, + zAxisFormat, + }; + + // Build the base Vega-Lite specification + const baseSpec: VegaSpec = { + $schema: 'https://vega.github.io/schema/vega-lite/v5.json', + data: { values: transformedData }, + mark: buildMark(type), + encoding: buildEncoding(dimensions, formats), + }; + + // Handle special case for line charts with dot size + if (dimensions.z) { + baseSpec.layer = [ + { + mark: { type: 'line', point: false }, + encoding: buildEncoding(dimensions, formats), + }, + { + mark: { type: 'point', filled: true }, + encoding: { + ...buildEncoding(dimensions, formats), + size: { + field: 'z', + type: 'quantitative', + legend: null, + }, + }, + }, + ]; + } + + // Add legend if specified + if (addLegend) { + baseSpec.config = { + legend: buildLegend(legendPosition), + }; + } + + // Add tooltip if specified + if (addTooltip) { + buildTooltip(baseSpec, dimensions, formats); + } + + return baseSpec; +}; diff --git a/src/plugins/vis_builder/public/visualizations/vega/build_vega_spec.ts b/src/plugins/vis_builder/public/visualizations/vega/build_vega_spec.ts new file mode 100644 index 000000000000..369359f7f80c --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/build_vega_spec.ts @@ -0,0 +1,39 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { VisualizationState, StyleState } from '../../application/utils/state_management'; +import { flattenDataHandler } from './utils/helpers'; +import { buildVegaSpecViaVegaLite } from './build_spec_vega_lite'; +import { buildVegaSpecViaVega } from './build_spec_vega'; + +/** + * Builds a Vega or Vega-Lite specification based on the provided context, visual configuration, and style. + * + * @param {any} context - The context data for the visualization. + * @param {any} visConfig - The visual configuration settings. + * @param {VisualizationState} visualization - The visualization object (not used in this function, consider removing if unnecessary). + * @param {StyleState} style - The style configuration for the visualization. + * @returns {any} The complete Vega or Vega-Lite specification. + */ +export const buildVegaSpec = ( + context: any, + visConfig: VisConfig, + visualization: any, + style: Style +): any => { + const { dimensions } = visConfig; + + // Transform the data using the flattenDataHandler + const transformedData = flattenDataHandler(context, dimensions, 'series'); + + // Determine whether to use Vega or Vega-Lite based on the presence of split dimensions + if (dimensions.splitRow || dimensions.splitColumn) { + // Use Vega for more complex, split visualizations + return buildVegaSpecViaVega(transformedData, visConfig, style); + } else { + // Use Vega-Lite for simpler visualizations + return buildVegaSpecViaVegaLite(transformedData, visConfig, style); + } +}; diff --git a/src/plugins/vis_builder/public/visualizations/vega/components/encoding.ts b/src/plugins/vis_builder/public/visualizations/vega/components/encoding.ts new file mode 100644 index 000000000000..282aee58990c --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/components/encoding.ts @@ -0,0 +1,155 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { mapFieldTypeToVegaType } from '../utils/helpers'; +import { AxisFormats } from '../utils/types'; + +interface EncodingChannel { + field: string; + type: string; + axis?: { title: string }; + legend?: { title: string | null }; +} + +interface VegaEncoding { + [key: string]: EncodingChannel; +} + +interface VegaScale { + name: string; + type: string; + domain: { + data: string; + field: string; + filter?: string; + }; + range: string; + padding?: number; + nice?: boolean; + zero?: boolean; +} + +/** + * Builds encoding configuration for Vega or Vega-Lite specifications. + * + * @param {any} dimensions - The dimensions of the data. + * @param {AxisFormats} formats - The formatting information for axes. + * @param {boolean} isVega - Whether to build for Vega (true) or Vega-Lite (false). + * @returns {VegaEncoding | VegaScale[]} The encoding configuration. + */ +export const buildEncoding = ( + dimensions: any, + formats: any, + isVega: boolean = false +): VegaEncoding | VegaScale[] => { + const { xAxisFormat, xAxisLabel, yAxisFormat, yAxisLabel, zAxisFormat } = formats; + + if (isVega) { + return buildVegaScales(dimensions, formats); + } + + return buildVegaLiteEncoding(dimensions, formats); +}; + +/** + * Builds encoding configuration for Vega-Lite specifications. + * + * @param {any} dimensions - The dimensions of the data. + * @param {any} formats - The formatting information for axes. + * @returns {VegaEncoding} The Vega-Lite encoding configuration. + */ +const buildVegaLiteEncoding = (dimensions: any, formats: any): VegaEncoding => { + const { xAxisFormat, xAxisLabel, yAxisFormat, yAxisLabel, zAxisFormat } = formats; + const encoding: VegaEncoding = {}; + + // Handle x-axis + encoding.x = buildAxisEncoding('x', dimensions.x, xAxisFormat, xAxisLabel); + + // Handle y-axis + encoding.y = buildAxisEncoding('y', dimensions.y, yAxisFormat, yAxisLabel); + + // Handle color encoding for multiple y dimensions or series + if (dimensions.y && dimensions.y.length > 1) { + encoding.color = buildColorEncoding('series', 'nominal'); + } else if (dimensions.series) { + encoding.color = buildColorEncoding('series', mapFieldTypeToVegaType(zAxisFormat?.id || '')); + } + + return encoding; +}; + +/** + * Builds scale configurations for Vega specifications. + * + * @param {any} dimensions - The dimensions of the data. + * @param {any} formats - The formatting information for axes. + * @returns {VegaScale[]} The Vega scale configurations. + */ +const buildVegaScales = (dimensions: any, formats: any): VegaScale[] => { + const scales: VegaScale[] = [ + { + name: 'xscale', + type: 'band', + domain: { data: 'source', field: 'x', filter: 'datum.split == parent.split' }, + range: 'width', + padding: 0.2, + }, + { + name: 'yscale', + type: 'linear', + domain: { data: 'source', field: 'y', filter: 'datum.split == parent.split' }, + range: 'height', + nice: true, + zero: true, + }, + ]; + + if (dimensions.z) { + scales.push({ + name: 'size', + type: 'linear', + domain: { data: 'source', field: 'z' }, + }); + } + + return scales; +}; + +/** + * Builds encoding for an axis. + * + * @param {string} field - The field name ('x' or 'y'). + * @param {any[]} dimension - The dimension data. + * @param {AxisFormat} axisFormat - The axis format information. + * @param {string} axisLabel - The axis label. + * @returns {EncodingChannel} The axis encoding configuration. + */ +const buildAxisEncoding = ( + field: string, + dimension: any[] | undefined, + axisFormat: AxisFormat, + axisLabel: string +): EncodingChannel => { + return { + field, + type: dimension ? mapFieldTypeToVegaType(axisFormat.id) : 'ordinal', + axis: { title: axisLabel }, + }; +}; + +/** + * Builds encoding for color. + * + * @param {string} field - The field name for color encoding. + * @param {string} type - The data type for color encoding. + * @returns {EncodingChannel} The color encoding configuration. + */ +const buildColorEncoding = (field: string, type: string): EncodingChannel => { + return { + field, + type, + legend: { title: null }, + }; +}; diff --git a/src/plugins/vis_builder/public/visualizations/vega/components/legend.ts b/src/plugins/vis_builder/public/visualizations/vega/components/legend.ts new file mode 100644 index 000000000000..1c102714ebb3 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/components/legend.ts @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Define types for legend positions and legend configurations +type LegendPosition = 'top' | 'bottom' | 'left' | 'right'; + +interface VegaLegendConfig { + fill?: string; + orient: LegendPosition; + [key: string]: any; // For any additional properties +} + +interface VegaLiteLegendConfig { + orient: LegendPosition; + [key: string]: any; // For any additional properties +} + +/** + * Builds a legend configuration for Vega or Vega-Lite specifications. + * + * @param {LegendPosition} legendPosition - The position of the legend ('top', 'bottom', 'left', 'right'). + * @param {boolean} isVega - Whether to build for Vega (true) or Vega-Lite (false). + * @returns {VegaLegendConfig | VegaLiteLegendConfig} The legend configuration object. + */ +export const buildLegend = ( + legendPosition: LegendPosition, + isVega: boolean = false +): VegaLegendConfig | VegaLiteLegendConfig => { + if (isVega) { + return buildVegaLegend(legendPosition); + } + return buildVegaLiteLegend(legendPosition); +}; + +/** + * Builds a legend configuration specifically for Vega specifications. + * + * @param {LegendPosition} legendPosition - The position of the legend. + * @returns {VegaLegendConfig} The Vega legend configuration object. + */ +const buildVegaLegend = (legendPosition: LegendPosition): VegaLegendConfig => { + return { + fill: 'color', + orient: legendPosition, + }; +}; + +/** + * Builds a legend configuration specifically for Vega-Lite specifications. + * + * @param {LegendPosition} legendPosition - The position of the legend. + * @returns {VegaLiteLegendConfig} The Vega-Lite legend configuration object. + */ +const buildVegaLiteLegend = (legendPosition: LegendPosition): VegaLiteLegendConfig => { + return { + orient: legendPosition, + }; +}; diff --git a/src/plugins/vis_builder/public/visualizations/vega/components/mark.ts b/src/plugins/vis_builder/public/visualizations/vega/components/mark.ts new file mode 100644 index 000000000000..b46f11d7081a --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/components/mark.ts @@ -0,0 +1,193 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { mapChartTypeToVegaType } from '../utils/helpers'; + +type VegaMarkType = 'line' | 'rect' | 'area' | 'symbol' | 'bar' | 'point' | 'circle' | 'square'; + +interface VegaMark { + type: VegaMarkType; + from?: { data: string }; + encode?: { + enter?: Record; + update?: Record; + }; +} + +interface BaseVegaLiteMark { + type: VegaMarkType; + tooltip?: boolean; + [key: string]: any; +} + +interface LineVegaLiteMark extends BaseVegaLiteMark { + type: 'line'; + point?: boolean | { filled?: boolean; size?: number }; +} + +interface AreaVegaLiteMark extends BaseVegaLiteMark { + type: 'area'; + line?: boolean; +} + +interface BarVegaLiteMark extends BaseVegaLiteMark { + type: 'bar'; + cornerRadius?: number; +} + +type VegaLiteMark = BaseVegaLiteMark | LineVegaLiteMark | AreaVegaLiteMark | BarVegaLiteMark; + +/** + * Builds a mark configuration for Vega or Vega-Lite based on the chart type. + * + * @param {string} chartType - The type of chart to build the mark for. + * @param {boolean} isVega - Whether to build for Vega (true) or Vega-Lite (false). + * @returns {VegaMark[] | VegaLiteMark} The mark configuration. + */ +export const buildMark = ( + chartType: string, + isVega: boolean = false +): VegaMark[] | VegaLiteMark => { + const vegaType = mapChartTypeToVegaType(chartType) as VegaMarkType; + + if (isVega) { + return buildMarkForVega(vegaType); + } + + return buildMarkForVegaLite(vegaType); +}; + +/** + * Builds a mark configuration for Vega-Lite based on the chart type. + * + * @param {VegaMarkType} vegaType - The type of Vega mark to build. + * @returns {VegaLiteMark} The Vega-Lite mark configuration. + */ +const buildMarkForVegaLite = (vegaType: VegaMarkType): VegaLiteMark => { + switch (vegaType) { + case 'line': + return { type: 'line', point: true }; + case 'area': + return { type: 'area', line: true }; + case 'rect': + case 'bar': + return { type: 'bar' }; + default: + return { type: vegaType }; + } +}; + +/** + * Builds a mark configuration for Vega based on the chart type. + * + * @param {VegaMarkType} chartType - The type of chart to build the mark for. + * @returns {VegaMark[]} An array of mark configurations. + */ +const buildMarkForVega = (chartType: VegaMarkType): VegaMark[] => { + switch (chartType) { + case 'line': + return buildMarkForLine(); + case 'rect': + return buildMarkForHistogram(); + case 'area': + return buildMarkForArea(); + default: + return buildMarkForLine(); + } +}; + +/** + * Builds a mark configuration for a line chart in Vega. + * + * @returns {VegaMark[]} An array of mark configurations for line and point marks. + */ +const buildMarkForLine = (): VegaMark[] => [ + { + type: 'line', + from: { data: 'source' }, + encode: { + enter: { + x: { scale: 'xscale', field: 'x' }, + y: { scale: 'yscale', field: 'y' }, + }, + update: { + opacity: { value: 1 }, + defined: { signal: 'datum.split == parent.split' }, + }, + }, + }, + { + type: 'symbol', + from: { data: 'source' }, + encode: { + enter: { + x: { scale: 'xscale', field: 'x' }, + y: { scale: 'yscale', field: 'y' }, + fill: { scale: 'color', field: 'series' }, + }, + update: { + opacity: { signal: 'datum.split == parent.split ? 1 : 0' }, + }, + }, + }, +]; + +/** + * Builds a mark configuration for a histogram in Vega. + * + * @returns {VegaMark[]} An array with a single mark configuration for rect marks. + */ +const buildMarkForHistogram = (): VegaMark[] => [ + { + type: 'rect', + from: { data: 'source' }, + encode: { + enter: { + x: { scale: 'xscale', field: 'x' }, + width: { scale: 'xscale', band: 1 }, + y: { scale: 'yscale', field: 'y' }, + y2: { scale: 'yscale', value: 0 }, + fill: { scale: 'color', field: 'series' }, + }, + update: { + opacity: { signal: 'datum.split == parent.split ? 1 : 0' }, + }, + }, + }, +]; + +/** + * Builds a mark configuration for an area chart in Vega. + * + * @returns {VegaMark[]} An array with a single mark configuration for grouped area marks. + */ +const buildMarkForArea = (): VegaMark[] => [ + { + type: 'group', + from: { + facet: { + name: 'series_data', + data: 'source', + groupby: 'series', + filter: 'datum.split == parent.split', + }, + }, + marks: [ + { + type: 'area', + from: { data: 'series_data' }, + encode: { + enter: { + x: { scale: 'xscale', field: 'x' }, + y: { scale: 'yscale', field: 'y' }, + y2: { scale: 'yscale', value: 0 }, + fill: { scale: 'color', field: 'series' }, + fillOpacity: { value: 0.7 }, + }, + }, + }, + ], + }, +]; diff --git a/src/plugins/vis_builder/public/visualizations/vega/components/tooltip.ts b/src/plugins/vis_builder/public/visualizations/vega/components/tooltip.ts new file mode 100644 index 000000000000..e8fe60137889 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/components/tooltip.ts @@ -0,0 +1,59 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AxisFormats, VegaLiteSpec } from './types'; + +/** + * Builds tooltip configuration for a dynamic Vega specification using OpenSearch data. + * + * @param {VegaLiteSpec} baseSpec - The base Vega Lite specification to modify. + * @param {any} dimensions - The dimensions of the data. + * @param {AxisFormats} formats - The formatting information for axes. + * @returns {void} - This function modifies the baseSpec object in place. + */ +export const buildTooltip = ( + baseSpec: VegaLiteSpec, + dimensions: any, + formats: AxisFormats +): void => { + const { xAxisLabel, yAxisLabel } = formats; + + // Configure tooltip based on the presence of yAxisLabel + if (!yAxisLabel) { + // If yAxisLabel is not provided, combine series and y value for tooltip + baseSpec.transform = [ + { + calculate: "datum.series + ': ' + datum.y", + as: 'metrics', + }, + ]; + + baseSpec.encoding.tooltip = [ + { field: 'x', type: 'nominal', title: xAxisLabel || '_all' }, + { field: 'metrics', type: 'nominal' }, + ]; + } else { + // If yAxisLabel is provided, use separate fields for x and y in tooltip + baseSpec.encoding.tooltip = [ + { field: 'x', type: 'nominal', title: xAxisLabel || '_all' }, + { field: 'y', type: 'nominal', title: yAxisLabel }, + ]; + } + + // Add z dimension to tooltip if it exists + if (dimensions.z && dimensions.z.length > 0) { + baseSpec.encoding.tooltip.push({ + field: 'z', + type: 'quantitative', + title: dimensions.z[0].label, + }); + } + + // Enable tooltip for the mark + baseSpec.mark = { + ...baseSpec.mark, + tooltip: true, + }; +}; diff --git a/src/plugins/vis_builder/public/visualizations/vega/utils/expression_helper.ts b/src/plugins/vis_builder/public/visualizations/vega/utils/expression_helper.ts new file mode 100644 index 000000000000..89a38be178a6 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/utils/expression_helper.ts @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + ExpressionFunctionDefinition, + OpenSearchDashboardsDatatable, + ExpressionValueBoxed, +} from '../../../../../expressions/common'; +import { getExpressionsService } from '../../../../../expressions/public'; + +/** + * Creates a function definition for raw data visualization. + * This function simply returns the input data without modification. + * + * @returns {ExpressionFunctionDefinition} The function definition for raw data visualization. + */ +export const createRawDataVisFn = (): ExpressionFunctionDefinition< + 'rawData', + OpenSearchDashboardsDatatable, + {}, + OpenSearchDashboardsDatatable +> => ({ + name: 'rawData', + type: 'opensearch_dashboards_datatable', + inputTypes: ['opensearch_dashboards_datatable'], + help: 'Returns raw data from opensearchaggs without modification', + args: {}, + fn(context: OpenSearchDashboardsDatatable): OpenSearchDashboardsDatatable { + // Simply return the input context, which should be the opensearchaggs result + return context; + }, +}); + +/** + * Executes an expression with the given context. + * + * @param {string} expression - The expression to execute. + * @param {any} context - The context to use for execution. + * @returns {Promise} A promise that resolves to the execution result. + * @throws {Error} If the expression service is not available or execution fails. + */ +export async function executeExpression( + expression: string, + context: any +): Promise { + const expressionService = getExpressionsService(); + + if (!expressionService) { + throw new Error('Expression service is not available'); + } + + try { + const result = await expressionService.execute(expression, { type: 'null' }, context); + return await result.getData(); + } catch (error) { + throw error; + } +} diff --git a/src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts b/src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts new file mode 100644 index 000000000000..bfd7814f9eea --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + vislibSeriesResponseHandler, + vislibSlicesResponseHandler, +} from '../../../../../vis_type_vislib/public'; +import { AxisFormats } from './types'; + +/** + * Sets axis properties (Format and Label) for x, y, and z axes + * @param {Object} converted - The object to set properties on + * @param {Array} group - The group containing axis information + */ +const setAxisProperties = (converted: any, group: any[]): void => { + const axes: Array = ['xAxis', 'yAxis', 'zAxis']; + const properties = ['Format', 'Label']; + + axes.forEach((axis) => { + properties.forEach((prop) => { + const key = `${axis}${prop}` as keyof AxisFormats; + converted[key] = group[0][key]; + }); + }); +}; + +/** + * Flattens series data into a single array of data points + * @param {Array} series - The series data to flatten + * @param {string|null} splitLabel - The label for the split, if any + * @returns {Array} Flattened array of data points + */ +const flattenSeries = (series, splitLabel = null) => + series.flatMap((s) => + s.values.map((v) => ({ + x: v.x, + y: v.y, + z: v.z, + series: s.label, + ...(splitLabel && { split: splitLabel }), + })) + ); + +export const flattenDataHandler = (context, dimensions, handlerType = 'series') => { + // Currently, our vislib only supports 'series' or 'slices' response types. + // This will need to be updated if more types are added in the future. + const handler = + handlerType === 'series' ? vislibSeriesResponseHandler : vislibSlicesResponseHandler; + const converted = handler(context, dimensions); + + if (handlerType === 'series') { + // Determine the group based on split dimensions + const group = dimensions.splitRow + ? converted.rows + : dimensions.splitColumn + ? converted.columns + : []; + + if (group && group.length !== 0) { + converted.series = group.flatMap((split) => flattenSeries(split.series, split.label)); + setAxisProperties(converted, group); + } else { + converted.series = flattenSeries(converted.series); + } + } else if (handlerType === 'slices') { + // TODO: Handle slices data, such as pie charts + // This section should be implemented when support for slice-based charts is added + } + + return converted; +}; + +/** + * Maps OpenSearch field types to Vega data types + * @param {string} fieldType - The OpenSearch field type + * @returns {string} The corresponding Vega data type + */ +export const mapFieldTypeToVegaType = (fieldType) => { + const typeMap = { + number: 'quantitative', + date: 'temporal', + time: 'temporal', + terms: 'nominal', + keyword: 'nominal', + ip: 'nominal', + boolean: 'nominal', + histogram: 'quantitative', + }; + + // Default to 'nominal' if the field type is not recognized + return typeMap[fieldType] || 'nominal'; +}; + +/** + * Maps chart types to Vega mark types + * @param {string} chartType - The chart type + * @returns {string} The corresponding Vega mark type + */ +export const mapChartTypeToVegaType = (chartType) => + chartType === 'histogram' ? 'rect' : chartType; diff --git a/src/plugins/vis_builder/public/visualizations/vega/utils/types.ts b/src/plugins/vis_builder/public/visualizations/vega/utils/types.ts new file mode 100644 index 000000000000..f24901e2c500 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/utils/types.ts @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface AxisFormat { + id: string; +} + +export interface AxisFormats { + xAxisLabel?: string; + yAxisLabel?: string; + zAxisLabel?: string; + xAxisFormat?: AxisFormat; + yAxisFormat?: AxisFormat; + zAxisFormat?: AxisFormat; +} + +// Define a VegaLiteSpec interface +export interface VegaLiteSpec { + $schema: string; + data: { + values: any[]; + }; + mark: { + type: string; + [key: string]: any; + }; + encoding: { + [key: string]: { + field: string; + type: string; + title?: string; + [key: string]: any; + }; + }; + transform?: Array<{ + calculate: string; + as: string; + }>; + layer?: VegaSpec[]; + config?: { + legend?: any; + [key: string]: any; + }; +} + +// Define a more general VegaSpec interface +export interface VegaSpec { + $schema: string; + padding?: number | { [key: string]: number }; + data: Array<{ + name: string; + values?: any[]; + source?: string; + transform?: Array<{ + type: string; + [key: string]: any; + }>; + }>; + signals?: Array<{ + name: string; + update: string; + [key: string]: any; + }>; + scales?: Array<{ + name: string; + type: string; + domain: any; + range: any; + [key: string]: any; + }>; + layout?: { + [key: string]: any; + }; + marks: Array<{ + type: string; + from?: any; + encode?: { + [key: string]: any; + }; + signals?: Array<{ + name: string; + update: string; + }>; + scales?: any[]; + axes?: Array<{ + orient: string; + scale: string; + [key: string]: any; + }>; + title?: { + [key: string]: any; + }; + marks?: any[]; + [key: string]: any; + }>; + legends?: Array<{ + [key: string]: any; + }>; + [key: string]: any; // Allow for additional properties +} diff --git a/src/plugins/vis_builder/public/visualizations/vislib/area/to_expression.ts b/src/plugins/vis_builder/public/visualizations/vislib/area/to_expression.ts index 4481dce24619..fa2b0ebd0224 100644 --- a/src/plugins/vis_builder/public/visualizations/vislib/area/to_expression.ts +++ b/src/plugins/vis_builder/public/visualizations/vislib/area/to_expression.ts @@ -10,20 +10,27 @@ import { IExpressionLoaderParams, } from '../../../../../expressions/public'; import { AreaOptionsDefaults } from './area_vis_type'; -import { getAggExpressionFunctions } from '../../common/expression_helpers'; +import { + getAggExpressionFunctions, + getAggExpressionFunctionsWithContext, +} from '../../common/expression_helpers'; import { VislibRootState, getValueAxes, getPipelineParams } from '../common'; import { createVis } from '../common/create_vis'; +import { buildPipeline } from '../../../../../visualizations/public'; +import { buildVegaSpec } from '../../vega/build_vega_spec'; +import { executeExpression } from '../../vega/utils/expression_helper'; export const toExpression = async ( { style: styleState, visualization }: VislibRootState, - searchContext: IExpressionLoaderParams['searchContext'] + searchContext: IExpressionLoaderParams['searchContext'], + useVega: boolean ) => { - const { aggConfigs, expressionFns, indexPattern } = await getAggExpressionFunctions( - visualization - ); + const { expressionFns, aggConfigs, indexPattern } = useVega + ? await getAggExpressionFunctionsWithContext(visualization, styleState, searchContext) + : await getAggExpressionFunctions(visualization); const { addLegend, addTooltip, legendPosition, type } = styleState; - const vis = await createVis(type, aggConfigs, indexPattern, searchContext?.timeRange); + const vis = await createVis(type, aggConfigs, indexPattern, searchContext); const params = getPipelineParams(); const dimensions = await buildVislibDimensions(vis, params); @@ -39,10 +46,35 @@ export const toExpression = async ( valueAxes, }; - const vislib = buildExpressionFunction('vislib', { - type, - visConfig: JSON.stringify(visConfig), - }); + if (useVega === true) { + const rawDataFn = buildExpressionFunction('rawData', {}); + const dataExpression = buildExpression([...expressionFns, rawDataFn]).toString(); + + // Execute the expression to get the raw data + const rawData = await executeExpression(dataExpression, searchContext); + + const vegaSpec = buildVegaSpec(rawData, visConfig, visualization, styleState); + + const visVega = await createVis('vega', aggConfigs, indexPattern, searchContext); + visVega.params = { + spec: JSON.stringify(vegaSpec), + }; + + const vegaExpression = await buildPipeline(visVega, { + timefilter: params.timefilter, + timeRange: params.timeRange, + abortSignal: undefined, + visLayers: undefined, + visAugmenterConfig: undefined, + }); + + return vegaExpression; + } else { + const vislib = buildExpressionFunction('vislib', { + type, + visConfig: JSON.stringify(visConfig), + }); - return buildExpression([...expressionFns, vislib]).toString(); + return buildExpression([...expressionFns, vislib]).toString(); + } }; diff --git a/src/plugins/vis_builder/public/visualizations/vislib/common/create_vis.ts b/src/plugins/vis_builder/public/visualizations/vislib/common/create_vis.ts index 209f4a2a50b8..421fb56bdcad 100644 --- a/src/plugins/vis_builder/public/visualizations/vislib/common/create_vis.ts +++ b/src/plugins/vis_builder/public/visualizations/vislib/common/create_vis.ts @@ -3,15 +3,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AggConfigs, IndexPattern, TimeRange } from '../../../../../data/public'; +import { AggConfigs, IndexPattern } from '../../../../../data/public'; import { Vis } from '../../../../../visualizations/public'; import { getSearchService } from '../../../plugin_services'; +import { IExpressionLoaderParams } from '../../../../../expressions/public'; export const createVis = async ( type: string, aggConfigs: AggConfigs, indexPattern: IndexPattern, - timeRange?: TimeRange + searchContext: IExpressionLoaderParams['searchContext'] ) => { const vis = new Vis(type); vis.data.aggs = aggConfigs; @@ -20,7 +21,7 @@ export const createVis = async ( const responseAggs = vis.data.aggs.getResponseAggs().filter((agg) => agg.enabled); responseAggs.forEach((agg) => { - agg.params.timeRange = timeRange; + agg.params.timeRange = searchContext?.timeRange; }); return vis; }; diff --git a/src/plugins/vis_builder/public/visualizations/vislib/histogram/to_expression.ts b/src/plugins/vis_builder/public/visualizations/vislib/histogram/to_expression.ts index 2f75ed326913..cf945e8db3c6 100644 --- a/src/plugins/vis_builder/public/visualizations/vislib/histogram/to_expression.ts +++ b/src/plugins/vis_builder/public/visualizations/vislib/histogram/to_expression.ts @@ -10,20 +10,27 @@ import { IExpressionLoaderParams, } from '../../../../../expressions/public'; import { HistogramOptionsDefaults } from './histogram_vis_type'; -import { getAggExpressionFunctions } from '../../common/expression_helpers'; +import { + getAggExpressionFunctions, + getAggExpressionFunctionsWithContext, +} from '../../common/expression_helpers'; import { VislibRootState, getValueAxes, getPipelineParams } from '../common'; import { createVis } from '../common/create_vis'; +import { buildPipeline } from '../../../../../visualizations/public'; +import { buildVegaSpec } from '../../vega/build_vega_spec'; +import { executeExpression } from '../../vega/utils/expression_helper'; export const toExpression = async ( { style: styleState, visualization }: VislibRootState, - searchContext: IExpressionLoaderParams['searchContext'] + searchContext: IExpressionLoaderParams['searchContext'], + useVega: boolean ) => { - const { aggConfigs, expressionFns, indexPattern } = await getAggExpressionFunctions( - visualization - ); + const { expressionFns, aggConfigs, indexPattern } = useVega + ? await getAggExpressionFunctionsWithContext(visualization, styleState, searchContext) + : await getAggExpressionFunctions(visualization); const { addLegend, addTooltip, legendPosition, type } = styleState; - const vis = await createVis(type, aggConfigs, indexPattern, searchContext?.timeRange); + const vis = await createVis(type, aggConfigs, indexPattern, searchContext); const params = getPipelineParams(); const dimensions = await buildVislibDimensions(vis, params); @@ -39,10 +46,35 @@ export const toExpression = async ( valueAxes, }; - const vislib = buildExpressionFunction('vislib', { - type, - visConfig: JSON.stringify(visConfig), - }); + if (useVega === true) { + const rawDataFn = buildExpressionFunction('rawData', {}); + const dataExpression = buildExpression([...expressionFns, rawDataFn]).toString(); + + // Execute the expression to get the raw data + const rawData = await executeExpression(dataExpression, searchContext); + + const vegaSpec = buildVegaSpec(rawData, visConfig, visualization, styleState); + + const visVega = await createVis('vega', aggConfigs, indexPattern, searchContext); + visVega.params = { + spec: JSON.stringify(vegaSpec), + }; + + const vegaExpression = await buildPipeline(visVega, { + timefilter: params.timefilter, + timeRange: params.timeRange, + abortSignal: undefined, + visLayers: undefined, + visAugmenterConfig: undefined, + }); + + return vegaExpression; + } else { + const vislib = buildExpressionFunction('vislib', { + type, + visConfig: JSON.stringify(visConfig), + }); - return buildExpression([...expressionFns, vislib]).toString(); + return buildExpression([...expressionFns, vislib]).toString(); + } }; diff --git a/src/plugins/vis_builder/public/visualizations/vislib/line/to_expression.ts b/src/plugins/vis_builder/public/visualizations/vislib/line/to_expression.ts index 41a6d505c724..b7717f1f5a6c 100644 --- a/src/plugins/vis_builder/public/visualizations/vislib/line/to_expression.ts +++ b/src/plugins/vis_builder/public/visualizations/vislib/line/to_expression.ts @@ -10,20 +10,27 @@ import { IExpressionLoaderParams, } from '../../../../../expressions/public'; import { LineOptionsDefaults } from './line_vis_type'; -import { getAggExpressionFunctions } from '../../common/expression_helpers'; +import { + getAggExpressionFunctions, + getAggExpressionFunctionsWithContext, +} from '../../common/expression_helpers'; import { VislibRootState, getValueAxes, getPipelineParams } from '../common'; import { createVis } from '../common/create_vis'; +import { buildPipeline } from '../../../../../visualizations/public'; +import { buildVegaSpec } from '../../vega/build_vega_spec'; +import { executeExpression } from '../../vega/utils/expression_helper'; export const toExpression = async ( { style: styleState, visualization }: VislibRootState, - searchContext: IExpressionLoaderParams['searchContext'] + searchContext: IExpressionLoaderParams['searchContext'], + useVega: boolean ) => { - const { aggConfigs, expressionFns, indexPattern } = await getAggExpressionFunctions( - visualization - ); + const { expressionFns, aggConfigs, indexPattern } = useVega + ? await getAggExpressionFunctionsWithContext(visualization, styleState, searchContext) + : await getAggExpressionFunctions(visualization); const { addLegend, addTooltip, legendPosition, type } = styleState; - const vis = await createVis(type, aggConfigs, indexPattern, searchContext?.timeRange); + const vis = await createVis(type, aggConfigs, indexPattern, searchContext); const params = getPipelineParams(); const dimensions = await buildVislibDimensions(vis, params); @@ -39,10 +46,34 @@ export const toExpression = async ( valueAxes, }; - const vislib = buildExpressionFunction('vislib', { - type, - visConfig: JSON.stringify(visConfig), - }); + if (useVega === true) { + const rawDataFn = buildExpressionFunction('rawData', {}); + const dataExpression = buildExpression([...expressionFns, rawDataFn]).toString(); + + // Execute the expression to get the raw data + const rawData = await executeExpression(dataExpression, searchContext); + + const vegaSpec = buildVegaSpec(rawData, visConfig, visualization, styleState); + + const visVega = await createVis('vega', aggConfigs, indexPattern, searchContext); + visVega.params = { + spec: JSON.stringify(vegaSpec), + }; + + const vegaExpression = await buildPipeline(visVega, { + timefilter: params.timefilter, + timeRange: params.timeRange, + abortSignal: undefined, + visLayers: undefined, + visAugmenterConfig: undefined, + }); - return buildExpression([...expressionFns, vislib]).toString(); + return vegaExpression; + } else { + const vislib = buildExpressionFunction('vislib', { + type, + visConfig: JSON.stringify(visConfig), + }); + return buildExpression([...expressionFns, vislib]).toString(); + } }; diff --git a/src/plugins/vis_builder/server/plugin.ts b/src/plugins/vis_builder/server/plugin.ts index d250c21f14ad..4922ec95e4df 100644 --- a/src/plugins/vis_builder/server/plugin.ts +++ b/src/plugins/vis_builder/server/plugin.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { i18n } from '@osd/i18n'; +import { schema } from '@osd/config-schema'; import { PluginInitializerContext, CoreSetup, @@ -14,6 +16,7 @@ import { import { VisBuilderPluginSetup, VisBuilderPluginStart } from './types'; import { capabilitiesProvider } from './capabilities_provider'; import { visBuilderSavedObjectType } from './saved_objects'; +import { VISBUILDER_ENABLE_VEGA_SETTING } from '../common/constants'; export class VisBuilderPlugin implements Plugin { private readonly logger: Logger; @@ -22,7 +25,7 @@ export class VisBuilderPlugin implements Plugin