diff --git a/src/plugins/vis_type_script/kibana.json b/src/plugins/vis_type_script/kibana.json index d4a395baceb45..c28ed5217ed8e 100644 --- a/src/plugins/vis_type_script/kibana.json +++ b/src/plugins/vis_type_script/kibana.json @@ -8,6 +8,6 @@ "version": "kibana", "ui": true, "server": false, - "requiredPlugins": ["expressions", "visualizations"], + "requiredPlugins": ["expressions", "visualizations", "data"], "requiredBundles": ["expressions", "visualizations", "visDefaultEditor"] } diff --git a/src/plugins/vis_type_script/public/expression/fn.ts b/src/plugins/vis_type_script/public/expression/fn.ts index 6a81ef8dcc539..d935f4b68c4f3 100644 --- a/src/plugins/vis_type_script/public/expression/fn.ts +++ b/src/plugins/vis_type_script/public/expression/fn.ts @@ -10,10 +10,11 @@ import { i18n } from '@kbn/i18n'; import type { ExpressionFunctionDefinition, Render } from '../../../expressions/public'; import type { Arguments } from '../types'; import type { RenderValue } from './renderer'; +import { ExpressionValueSearchContext } from '../../../data/common'; type ScriptVisExpressionFunctionDefinition = ExpressionFunctionDefinition< 'scriptVis', - unknown, + ExpressionValueSearchContext | null, Arguments, Render >; @@ -21,7 +22,7 @@ type ScriptVisExpressionFunctionDefinition = ExpressionFunctionDefinition< export const createScriptVisFn = (): ScriptVisExpressionFunctionDefinition => ({ name: 'scriptVis', type: 'render', - inputTypes: [], + inputTypes: ['kibana_context', 'null'], help: i18n.translate('visTypeMarkdown.function.help', { defaultMessage: 'Script-based visualization', }), @@ -44,6 +45,11 @@ export const createScriptVisFn = (): ScriptVisExpressionFunctionDefinition => ({ visParams: { script: args.script, }, + visSearchContext: { + timeRange: input?.timeRange, + query: input?.query, + filter: input?.filters, + }, }, }; }, diff --git a/src/plugins/vis_type_script/public/expression/renderer.tsx b/src/plugins/vis_type_script/public/expression/renderer.tsx index b95796d5ba6c3..3cd56555a6adb 100644 --- a/src/plugins/vis_type_script/public/expression/renderer.tsx +++ b/src/plugins/vis_type_script/public/expression/renderer.tsx @@ -10,28 +10,36 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { ExpressionRenderDefinition } from '../../../expressions'; import { VisualizationContainer } from '../../../visualizations/public'; -import { VisParams } from '../types'; +import { VisParams, VisSearchContext } from '../types'; import { ScriptRenderer } from '../renderer'; +import { VisTypeScriptKibanaApi } from '../kibana_api'; +import { DataPublicPluginStart } from '../../../data/public'; export interface RenderValue { visType: 'script'; visParams: VisParams; + visSearchContext: VisSearchContext; } -export const scriptVisRenderer: ExpressionRenderDefinition = { +export const scriptVisRenderer: ( + getDeps: () => Promise<{ data: DataPublicPluginStart }> // TODO: not sure if this is correct way of passing deps to vis renderer +) => ExpressionRenderDefinition = (getDeps) => ({ name: 'script_vis', displayName: 'script-based visualization', reuseDomNode: true, - render: async (domNode, { visParams }, handlers) => { + render: async (domNode, { visParams, visSearchContext }, handlers) => { + const deps = await getDeps(); + const visTypeScriptKibanaApi = new VisTypeScriptKibanaApi(deps, visSearchContext); + handlers.onDestroy(() => { unmountComponentAtNode(domNode); }); render( - + , domNode ); }, -}; +}); diff --git a/src/plugins/vis_type_script/public/kibana_api/index.ts b/src/plugins/vis_type_script/public/kibana_api/index.ts new file mode 100644 index 0000000000000..877de71ecd62c --- /dev/null +++ b/src/plugins/vis_type_script/public/kibana_api/index.ts @@ -0,0 +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 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 './kibana_api'; diff --git a/src/plugins/vis_type_script/public/kibana_api/kibana_api.ts b/src/plugins/vis_type_script/public/kibana_api/kibana_api.ts new file mode 100644 index 0000000000000..633f7441828b9 --- /dev/null +++ b/src/plugins/vis_type_script/public/kibana_api/kibana_api.ts @@ -0,0 +1,37 @@ +/* + * 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 * as estypes from '@elastic/elasticsearch/lib/api/types'; +import { lastValueFrom } from 'rxjs'; +import { DataPublicPluginStart } from '../../../data/public'; +import { VisSearchContext } from '../types'; + +export interface VisTypeScriptKibanaApiDeps { + data: DataPublicPluginStart; +} + +export class VisTypeScriptKibanaApi { + constructor( + private readonly deps: VisTypeScriptKibanaApiDeps, + private readonly visSearchContext: VisSearchContext + ) {} + + async esSearch( + payload: estypes.SearchRequest, + { useKibanaContext = true } = { useKibanaContext: true } + ): Promise { + if (useKibanaContext) { + // TODO: adjust request based on this.visSearchContext + // eslint-disable-next-line no-console + console.log(this.visSearchContext); + } + + const response = await lastValueFrom(this.deps.data.search.search({ params: payload })); + return response.rawResponse; + } +} diff --git a/src/plugins/vis_type_script/public/plugin.ts b/src/plugins/vis_type_script/public/plugin.ts index e1dabdca79e81..349993489bb2c 100644 --- a/src/plugins/vis_type_script/public/plugin.ts +++ b/src/plugins/vis_type_script/public/plugin.ts @@ -14,24 +14,39 @@ import { scriptVisDefinition } from './vis_definition'; import { ConfigSchema } from '../config'; import { scriptVisRenderer } from './expression/renderer'; import { createScriptVisFn } from './expression/fn'; +import { DataPublicPluginStart } from '../../data/public'; /** @internal */ -export interface ScriptVisPluginDependencies { +export interface ScriptVisPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; } /** @internal */ -export class ScriptVisPlugin implements Plugin { +export interface ScriptVisPluginStartDependencies { + data: DataPublicPluginStart; +} + +/** @internal */ +export class ScriptVisPlugin + implements Plugin +{ initializerContext: PluginInitializerContext; constructor(initializerContext: PluginInitializerContext) { this.initializerContext = initializerContext; } - public setup(core: CoreSetup, { expressions, visualizations }: ScriptVisPluginDependencies) { + public setup( + core: CoreSetup, + { expressions, visualizations }: ScriptVisPluginSetupDependencies + ) { visualizations.createBaseVisualization(scriptVisDefinition); - expressions.registerRenderer(scriptVisRenderer); + expressions.registerRenderer( + scriptVisRenderer(() => + core.getStartServices().then(([coreStart, plugins]) => ({ data: plugins.data })) + ) + ); expressions.registerFunction(createScriptVisFn); } diff --git a/src/plugins/vis_type_script/public/renderer/index.tsx b/src/plugins/vis_type_script/public/renderer/index.tsx index 0cf0bdc272c96..5425e3491cf39 100644 --- a/src/plugins/vis_type_script/public/renderer/index.tsx +++ b/src/plugins/vis_type_script/public/renderer/index.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { createEndpoint, fromIframe } from '@remote-ui/rpc'; import './index.scss'; +import { VisTypeScriptKibanaApi } from '../kibana_api'; const getSandboxDocument = (script: string) => { // may be possible to remove this iframe-level nonce once we can use the top-level CSP @@ -30,19 +31,12 @@ const getSandboxDocument = (script: string) => { const endpoint = createEndpoint(fromInsideIframe()); const KIBANA_API = { - searchEs: () => { + esSearch: (payload, options) => { console.log('Calling search inside an iframe') - endpoint.call.searchEs(); + return endpoint.call.esSearch(payload, options); } } - endpoint.expose({ - onUpdate: () => { - console.log('Running an update from an iframe') - } - }) - - window.KIBANA_API = KIBANA_API; @@ -53,29 +47,33 @@ const getSandboxDocument = (script: string) => { `; }; -export const ScriptRenderer: React.FunctionComponent<{ script: string }> = ({ +export const ScriptRenderer: React.FunctionComponent<{ + script: string; + kibanaApi: VisTypeScriptKibanaApi; +}> = ({ script: visualizationScript, + kibanaApi, }: { script: string; + kibanaApi: VisTypeScriptKibanaApi; }) => { const iframeRef = React.useRef(null); React.useEffect(() => { if (!iframeRef.current) throw new Error('Iframe init error'); const iframeEl = iframeRef.current; - const endpoint = createEndpoint<{ onUpdate: () => void }>(fromIframe(iframeEl)); + const endpoint = createEndpoint(fromIframe(iframeEl, { terminate: false })); endpoint.expose({ - searchEs: () => { - // eslint-disable-next-line no-console - console.log('Running search from parent'); + esSearch: (payload, options) => { + return kibanaApi.esSearch(payload, options); }, }); - // eslint-disable-next-line no-console - console.log('Calling on update from parent'); - endpoint.call.onUpdate(); - }, []); + return () => { + endpoint.terminate(); + }; + }, [kibanaApi]); return (