From 46edbbecd6d8bcc1bf912250908d1249d38ab482 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Tue, 26 Oct 2021 08:40:59 -0700 Subject: [PATCH] [Canvas] Extract and inject references for by-value embeddables (#115124) * Extract/inject references for by-value embeddables in embeddable function Fixed server interpreter setup Register external functions in canvas_plugin_src plugin def * Fixed ref name in embeddable.inject * Fixed ts errors * Fix missing type error --- .../functions/browser/index.ts | 12 +- .../functions/external/embeddable.test.ts | 5 +- .../functions/external/embeddable.ts | 166 +++++++++++------- .../functions/external/index.ts | 20 ++- .../canvas/canvas_plugin_src/plugin.ts | 8 + .../canvas/i18n/functions/dict/embeddable.ts | 4 +- x-pack/plugins/canvas/server/plugin.ts | 9 +- .../canvas/server/setup_interpreter.ts | 12 +- x-pack/plugins/canvas/types/functions.ts | 10 +- 9 files changed, 157 insertions(+), 89 deletions(-) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/index.ts index 2cfdebafb70df42..d6d7a0f867849ca 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/index.ts @@ -6,7 +6,6 @@ */ import { functions as commonFunctions } from '../common'; -import { functions as externalFunctions } from '../external'; import { location } from './location'; import { markdown } from './markdown'; import { urlparam } from './urlparam'; @@ -14,13 +13,4 @@ import { escount } from './escount'; import { esdocs } from './esdocs'; import { essql } from './essql'; -export const functions = [ - location, - markdown, - urlparam, - escount, - esdocs, - essql, - ...commonFunctions, - ...externalFunctions, -]; +export const functions = [location, markdown, urlparam, escount, esdocs, essql, ...commonFunctions]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.test.ts index 9e8db9f7a5c1d49..001fb0e3f62e3ad 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.test.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { embeddable } from './embeddable'; +import { embeddableFunctionFactory } from './embeddable'; import { getQueryFilters } from '../../../common/lib/build_embeddable_filters'; import { ExpressionValueFilter } from '../../../types'; import { encode } from '../../../common/lib/embeddable_dataurl'; +import { InitializeArguments } from '.'; const filterContext: ExpressionValueFilter = { type: 'filter', @@ -32,7 +33,7 @@ const filterContext: ExpressionValueFilter = { }; describe('embeddable', () => { - const fn = embeddable().fn; + const fn = embeddableFunctionFactory({} as InitializeArguments)().fn; const config = { id: 'some-id', timerange: { from: '15m', to: 'now' }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts index f846f23ff7f73dc..7ef8f0a09eb9072 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts @@ -12,8 +12,9 @@ import { getFunctionHelp } from '../../../i18n'; import { SavedObjectReference } from '../../../../../../src/core/types'; import { getQueryFilters } from '../../../common/lib/build_embeddable_filters'; import { decode, encode } from '../../../common/lib/embeddable_dataurl'; +import { InitializeArguments } from '.'; -interface Arguments { +export interface Arguments { config: string; type: string; } @@ -31,77 +32,114 @@ const baseEmbeddableInput = { type Return = EmbeddableExpression; -export function embeddable(): ExpressionFunctionDefinition< +type EmbeddableFunction = ExpressionFunctionDefinition< 'embeddable', ExpressionValueFilter | null, Arguments, Return -> { - const { help, args: argHelp } = getFunctionHelp().embeddable; - - return { - name: 'embeddable', - help, - args: { - config: { - aliases: ['_'], - types: ['string'], - required: true, - help: argHelp.config, - }, - type: { - types: ['string'], - required: true, - help: argHelp.type, - }, - }, - context: { - types: ['filter'], - }, - type: EmbeddableExpressionType, - fn: (input, args) => { - const filters = input ? input.and : []; - - const embeddableInput = decode(args.config) as EmbeddableInput; - - return { - type: EmbeddableExpressionType, - input: { - ...baseEmbeddableInput, - ...embeddableInput, - filters: getQueryFilters(filters), +>; + +export function embeddableFunctionFactory({ + embeddablePersistableStateService, +}: InitializeArguments): () => EmbeddableFunction { + return function embeddable(): EmbeddableFunction { + const { help, args: argHelp } = getFunctionHelp().embeddable; + + return { + name: 'embeddable', + help, + args: { + config: { + aliases: ['_'], + types: ['string'], + required: true, + help: argHelp.config, }, - generatedAt: Date.now(), - embeddableType: args.type, - }; - }, - - extract(state) { - const input = decode(state.config[0] as string); - const refName = 'embeddable.id'; - - const references: SavedObjectReference[] = [ - { - name: refName, - type: state.type[0] as string, - id: input.savedObjectId as string, + type: { + types: ['string'], + required: true, + help: argHelp.type, }, - ]; + }, + context: { + types: ['filter'], + }, + type: EmbeddableExpressionType, + fn: (input, args) => { + const filters = input ? input.and : []; + + const embeddableInput = decode(args.config) as EmbeddableInput; - return { - state, - references, - }; - }, + return { + type: EmbeddableExpressionType, + input: { + ...baseEmbeddableInput, + ...embeddableInput, + filters: getQueryFilters(filters), + }, + generatedAt: Date.now(), + embeddableType: args.type, + }; + }, - inject(state, references) { - const reference = references.find((ref) => ref.name === 'embeddable.id'); - if (reference) { + extract(state) { const input = decode(state.config[0] as string); - input.savedObjectId = reference.id; - state.config[0] = encode(input); - } - return state; - }, + + // extracts references for by-reference embeddables + if (input.savedObjectId) { + const refName = 'embeddable.savedObjectId'; + + const references: SavedObjectReference[] = [ + { + name: refName, + type: state.type[0] as string, + id: input.savedObjectId as string, + }, + ]; + + return { + state, + references, + }; + } + + // extracts references for by-value embeddables + const { state: extractedState, references: extractedReferences } = + embeddablePersistableStateService.extract({ + ...input, + type: state.type[0], + }); + + const { type, ...extractedInput } = extractedState; + + return { + state: { ...state, config: [encode(extractedInput)], type: [type] }, + references: extractedReferences, + }; + }, + + inject(state, references) { + const input = decode(state.config[0] as string); + const savedObjectReference = references.find( + (ref) => ref.name === 'embeddable.savedObjectId' + ); + + // injects saved object id for by-references embeddable + if (savedObjectReference) { + input.savedObjectId = savedObjectReference.id; + state.config[0] = encode(input); + state.type[0] = savedObjectReference.type; + } else { + // injects references for by-value embeddables + const { type, ...injectedInput } = embeddablePersistableStateService.inject( + { ...input, type: state.type[0] }, + references + ); + state.config[0] = encode(injectedInput); + state.type[0] = type; + } + return state; + }, + }; }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/index.ts index e05e13efdb6cda9..1d69e181b5fd9d5 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/index.ts @@ -5,10 +5,26 @@ * 2.0. */ +import { EmbeddableStart } from 'src/plugins/embeddable/public'; +import { embeddableFunctionFactory } from './embeddable'; import { savedLens } from './saved_lens'; import { savedMap } from './saved_map'; import { savedSearch } from './saved_search'; import { savedVisualization } from './saved_visualization'; -import { embeddable } from './embeddable'; -export const functions = [embeddable, savedLens, savedMap, savedSearch, savedVisualization]; +export interface InitializeArguments { + embeddablePersistableStateService: { + extract: EmbeddableStart['extract']; + inject: EmbeddableStart['inject']; + }; +} + +export function initFunctions(initialize: InitializeArguments) { + return [ + embeddableFunctionFactory(initialize), + savedLens, + savedMap, + savedSearch, + savedVisualization, + ]; +} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts index a577ccf4cf41b8e..591795637aebea7 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts @@ -14,6 +14,7 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { Start as InspectorStart } from '../../../../src/plugins/inspector/public'; import { functions } from './functions/browser'; +import { initFunctions } from './functions/external'; import { typeFunctions } from './expression_types'; import { renderFunctions, renderFunctionFactories } from './renderers'; @@ -41,6 +42,13 @@ export class CanvasSrcPlugin implements Plugin plugins.canvas.addRenderers(renderFunctions); core.getStartServices().then(([coreStart, depsStart]) => { + const externalFunctions = initFunctions({ + embeddablePersistableStateService: { + extract: depsStart.embeddable.extract, + inject: depsStart.embeddable.inject, + }, + }); + plugins.canvas.addFunctions(externalFunctions); plugins.canvas.addRenderers( renderFunctionFactories.map((factory: any) => factory(coreStart, depsStart)) ); diff --git a/x-pack/plugins/canvas/i18n/functions/dict/embeddable.ts b/x-pack/plugins/canvas/i18n/functions/dict/embeddable.ts index 8945798df46ed40..279f58799e8c006 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/embeddable.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/embeddable.ts @@ -6,11 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -import { embeddable } from '../../../canvas_plugin_src/functions/external/embeddable'; +import { embeddableFunctionFactory } from '../../../canvas_plugin_src/functions/external/embeddable'; import { FunctionHelp } from '../function_help'; import { FunctionFactory } from '../../../types'; -export const help: FunctionHelp> = { +export const help: FunctionHelp>> = { help: i18n.translate('xpack.canvas.functions.embeddableHelpText', { defaultMessage: `Returns an embeddable with the provided configuration`, }), diff --git a/x-pack/plugins/canvas/server/plugin.ts b/x-pack/plugins/canvas/server/plugin.ts index 4071b891e4c3d70..ebe43ba76a46ac3 100644 --- a/x-pack/plugins/canvas/server/plugin.ts +++ b/x-pack/plugins/canvas/server/plugin.ts @@ -14,6 +14,7 @@ import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { HomeServerPluginSetup } from 'src/plugins/home/server'; +import { EmbeddableSetup } from 'src/plugins/embeddable/server'; import { ESSQL_SEARCH_STRATEGY } from '../common/lib/constants'; import { ReportingSetup } from '../../reporting/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; @@ -30,6 +31,7 @@ import { CanvasRouteHandlerContext, createWorkpadRouteContext } from './workpad_ interface PluginsSetup { expressions: ExpressionsServerSetup; + embeddable: EmbeddableSetup; features: FeaturesPluginSetup; home: HomeServerPluginSetup; bfetch: BfetchServerSetup; @@ -82,7 +84,12 @@ export class CanvasPlugin implements Plugin { const kibanaIndex = coreSetup.savedObjects.getKibanaIndex(); registerCanvasUsageCollector(plugins.usageCollection, kibanaIndex); - setupInterpreter(expressionsFork); + setupInterpreter(expressionsFork, { + embeddablePersistableStateService: { + extract: plugins.embeddable.extract, + inject: plugins.embeddable.inject, + }, + }); coreSetup.getStartServices().then(([_, depsStart]) => { const strategy = essqlSearchStrategyProvider(); diff --git a/x-pack/plugins/canvas/server/setup_interpreter.ts b/x-pack/plugins/canvas/server/setup_interpreter.ts index 2fe23eb86c086fe..849ad79717056cd 100644 --- a/x-pack/plugins/canvas/server/setup_interpreter.ts +++ b/x-pack/plugins/canvas/server/setup_interpreter.ts @@ -7,9 +7,15 @@ import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { functions } from '../canvas_plugin_src/functions/server'; -import { functions as externalFunctions } from '../canvas_plugin_src/functions/external'; +import { + initFunctions as initExternalFunctions, + InitializeArguments, +} from '../canvas_plugin_src/functions/external'; -export function setupInterpreter(expressions: ExpressionsServerSetup) { +export function setupInterpreter( + expressions: ExpressionsServerSetup, + dependencies: InitializeArguments +) { functions.forEach((f) => expressions.registerFunction(f)); - externalFunctions.forEach((f) => expressions.registerFunction(f)); + initExternalFunctions(dependencies).forEach((f) => expressions.registerFunction(f)); } diff --git a/x-pack/plugins/canvas/types/functions.ts b/x-pack/plugins/canvas/types/functions.ts index 2569e0b10685b71..c80102915ed9536 100644 --- a/x-pack/plugins/canvas/types/functions.ts +++ b/x-pack/plugins/canvas/types/functions.ts @@ -10,8 +10,8 @@ import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; import { functions as commonFunctions } from '../canvas_plugin_src/functions/common'; import { functions as browserFunctions } from '../canvas_plugin_src/functions/browser'; import { functions as serverFunctions } from '../canvas_plugin_src/functions/server'; -import { functions as externalFunctions } from '../canvas_plugin_src/functions/external'; -import { initFunctions } from '../public/functions'; +import { initFunctions as initExternalFunctions } from '../canvas_plugin_src/functions/external'; +import { initFunctions as initClientFunctions } from '../public/functions'; /** * A `ExpressionFunctionFactory` is a powerful type used for any function that produces @@ -90,9 +90,11 @@ export type FunctionFactory = type CommonFunction = FunctionFactory; type BrowserFunction = FunctionFactory; type ServerFunction = FunctionFactory; -type ExternalFunction = FunctionFactory; +type ExternalFunction = FunctionFactory< + ReturnType extends Array ? U : never +>; type ClientFunctions = FunctionFactory< - ReturnType extends Array ? U : never + ReturnType extends Array ? U : never >; /**