Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] Script-based visualizations #131321

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1438607
script-based visualization plugin
drewdaemon Apr 25, 2022
f4a068b
make script entry fill vertical space
drewdaemon Apr 25, 2022
ef7b52b
Add sandbox script renderer
drewdaemon Apr 25, 2022
7056d66
everything is working
drewdaemon Apr 25, 2022
9cb83fc
Add a default visualization
drewdaemon Apr 25, 2022
cfca2e4
update readme
drewdaemon Apr 25, 2022
fdb5bbb
promote script-based
drewdaemon Apr 25, 2022
0000ad9
wip add remote-ui/rpc
Dosant Apr 26, 2022
b2fe89d
[CI] Auto-commit changed files from 'node scripts/build_plugin_list_d…
kibanamachine Apr 26, 2022
3725767
Merge pull request #5 from Dosant/d/2022-04-26-script-based-visualiza…
drewdaemon Apr 26, 2022
5f58bfc
use Monaco instead of textarea
drewdaemon Apr 26, 2022
5f96932
allow execute searches
Dosant Apr 26, 2022
d143292
Merge branch 'd/2022-04-26-script-based-visualizations' of github.com…
Dosant Apr 26, 2022
992545b
don’t remove iframe on unmount
Dosant Apr 26, 2022
0efafe2
Merge pull request #6 from Dosant/d/2022-04-26-script-based-visualiza…
drewdaemon Apr 26, 2022
be1b99c
fix styling
drewdaemon Apr 26, 2022
d11fa94
basic autocomplete
drewdaemon Apr 26, 2022
e02b1a9
Merge branch 'add-syntax-highlighting' into script-based-visualizations
drewdaemon Apr 26, 2022
b259ac1
promise-based search interface
drewdaemon Apr 26, 2022
7de4481
some usability improvements
drewdaemon Apr 26, 2022
21e035c
shorten KIBANA_API to KIBANA
drewdaemon Apr 26, 2022
4b28c02
add size APIs
drewdaemon Apr 27, 2022
297c902
add dependency-urls settings
drewdaemon Apr 27, 2022
2912df2
load dependency scripts from URLs
drewdaemon Apr 27, 2022
7729ab5
validate package URLs
drewdaemon Apr 27, 2022
2830f30
make URLs subject to externalUrl.policy settings
drewdaemon Apr 27, 2022
72231b1
Merge branch 'main' of github.com:elastic/kibana into script-based-vi…
drewdaemon Apr 27, 2022
ff6ecac
add nonce to csp
flash1293 Apr 26, 2022
ca05dff
use nonce from core
drewdaemon Apr 27, 2022
d41f032
update default visualization to respect resize
drewdaemon Apr 27, 2022
2e45815
revert defer() workaround
Dosant Apr 28, 2022
410a14d
Merge pull request #7 from Dosant/d/2022-04-26-script-based-visualiza…
drewdaemon Apr 28, 2022
6477795
very basic runtime error handling
Dosant Apr 28, 2022
1939783
very basic error handling
Dosant Apr 28, 2022
affc827
Merge pull request #8 from Dosant/d/2022-04-26-script-based-visualiza…
drewdaemon Apr 28, 2022
dfa51d6
add sql api
Dosant Apr 28, 2022
c80b12d
Merge pull request #9 from Dosant/d/2022-04-26-script-based-visualiza…
drewdaemon Apr 28, 2022
f512ac6
Add filters and query bar
drewdaemon Apr 28, 2022
cb3fdcc
Add filter fns
drewdaemon Apr 28, 2022
d6ce3eb
Merge pull request #11 from andrewctate/onweek/add-filter-fns
drewdaemon Apr 29, 2022
2432cef
add styling dependencies
drewdaemon Apr 29, 2022
da5b5e4
make kibana context work
Dosant Apr 29, 2022
736fd6f
Merge pull request #12 from andrewctate/onweek/add-style-dependencies
drewdaemon Apr 29, 2022
a05bc39
Merge branch 'script-based-visualizations' of https://github.com/andr…
Dosant Apr 29, 2022
56782d6
Merge pull request #13 from Dosant/d/2022-04-26-script-based-visualiz…
drewdaemon Apr 29, 2022
722e26b
remove extra suggestions
drewdaemon Apr 29, 2022
d4fd6c6
Merge branch 'main' into script-based-visualizations
kibanamachine May 2, 2022
068fb33
Merge branch 'main' into script-based-visualizations
kibanamachine May 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ The plugin exposes the static DefaultEditorController class to consume.
|WARNING: Missing README.


|{kib-repo}blob/{branch}/src/plugins/vis_type_script/README.md[visTypeScript]
|This plugin defines the script-based visualization type. Users can use JavaScript and the popular D3 library to build script-based custom visualizations directly in Kibana.


|{kib-repo}blob/{branch}/src/plugins/vis_types/table/README.md[visTypeTable]
|Contains the data table visualization, that allows presenting data in a simple table format.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
"@mapbox/vector-tile": "1.3.1",
"@reduxjs/toolkit": "^1.6.1",
"@remote-ui/rpc": "1.3.0",
"@slack/webhook": "^5.0.4",
"@turf/along": "6.0.1",
"@turf/area": "6.0.1",
Expand Down
2 changes: 2 additions & 0 deletions src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { DeprecationsServiceStart } from './deprecations';
import type { ThemeServiceSetup, ThemeServiceStart } from './theme';
import { ExecutionContextSetup, ExecutionContextStart } from './execution_context';
import type { AnalyticsServiceSetup, AnalyticsServiceStart } from './analytics';
import type { InjectedMetadataSetup } from './injected_metadata';

export type {
PackageInfo,
Expand Down Expand Up @@ -251,6 +252,7 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
* */
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
getCsp: () => ReturnType<InjectedMetadataSetup['getCspConfig']>;
};
/** {@link ThemeServiceSetup} */
theme: ThemeServiceSetup;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export interface InjectedMetadataSetup {
getKibanaVersion: () => string;
getCspConfig: () => {
warnLegacyBrowsers: boolean;
nonce: string;
};
getExternalUrlConfig: () => {
policy: IExternalUrlPolicy[];
Expand Down
1 change: 1 addition & 0 deletions src/core/public/plugins/plugin_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export function createPluginSetupContext<
uiSettings: deps.uiSettings,
injectedMetadata: {
getInjectedVar: deps.injectedMetadata.getInjectedVar,
getCsp: deps.injectedMetadata.getCspConfig,
},
theme: deps.theme,
getStartServices: () => plugin.startDependencies,
Expand Down
9 changes: 8 additions & 1 deletion src/core/server/http_resources/http_resources_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { randomBytes } from 'crypto';
import { RequestHandlerContext } from '..';

import { CoreContext } from '../core_context';
Expand Down Expand Up @@ -93,18 +94,23 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
return {
async renderCoreApp(options: HttpResourcesRenderOptions = {}) {
const apmConfig = getApmConfig(request.url.pathname);
const nonce = randomBytes(16).toString('hex');
const { uiSettings } = await context.core;
const body = await deps.rendering.render(request, uiSettings.client, {
isAnonymousPage: false,
vars: {
apmConfig,
},
includeExposedConfigKeys: options.includeExposedConfigKeys,
nonce,
});

return response.ok({
body,
headers: { ...options.headers, 'content-security-policy': cspHeader },
headers: {
...options.headers,
'content-security-policy': `${cspHeader}; script-src '${nonce}';`,
},
});
},
async renderAnonymousCoreApp(options: HttpResourcesRenderOptions = {}) {
Expand All @@ -124,6 +130,7 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
});
},
renderHtml(options: HttpResourcesResponseOptions) {
// TODO do nonce to csp header
return response.ok({
body: options.body,
headers: {
Expand Down
4 changes: 2 additions & 2 deletions src/core/server/rendering/rendering_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class RenderingService {
{ http, uiPlugins, status }: RenderOptions,
request: KibanaRequest,
uiSettings: IUiSettingsClient,
{ isAnonymousPage = false, vars, includeExposedConfigKeys }: IRenderOptions = {}
{ isAnonymousPage = false, vars, includeExposedConfigKeys, nonce }: IRenderOptions = {}
) {
const env = {
mode: this.coreContext.env.mode,
Expand Down Expand Up @@ -131,7 +131,7 @@ export class RenderingService {
darkMode,
version: themeVersion,
},
csp: { warnLegacyBrowsers: http.csp.warnLegacyBrowsers },
csp: { warnLegacyBrowsers: http.csp.warnLegacyBrowsers, nonce },
externalUrl: http.externalUrl,
vars: vars ?? {},
uiPlugins: await Promise.all(
Expand Down
17 changes: 17 additions & 0 deletions src/plugins/vis_type_script/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Description

This plugin defines the script-based visualization type. Users can use JavaScript and the popular D3 library to build script-based custom visualizations directly in Kibana.

## Setup

To use this plugin, you need to relax Kibana's default content security policy via `kibana.yml`. (This will no longer be necessary once issue [101579](https://github.com/elastic/kibana/issues/101579) is resolved.)

Add the following to `kibana.yml` (**DO NOT USE IN PRODUCTION**):

```yml
csp.warnLegacyBrowsers: false
csp.strict: false
csp.script_src:
- 'unsafe-inline'
- 'https://unpkg.com/'
```
15 changes: 15 additions & 0 deletions src/plugins/vis_type_script/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 { schema, TypeOf } from '@kbn/config-schema';

export const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
});

export type ConfigSchema = TypeOf<typeof configSchema>;
16 changes: 16 additions & 0 deletions src/plugins/vis_type_script/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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>/src/plugins/vis_type_markdown'],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/src/plugins/vis_type_markdown',
coverageReporters: ['text', 'html'],
collectCoverageFrom: ['<rootDir>/src/plugins/vis_type_markdown/{public,server}/**/*.{ts,tsx}'],
};
13 changes: 13 additions & 0 deletions src/plugins/vis_type_script/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"id": "visTypeScript",
"owner": {
"name": "Kibana Presentation",
"githubTeam": "kibana-presentation"
},
"description": "Adds a script-based visualization type",
"version": "kibana",
"ui": true,
"server": false,
"requiredPlugins": ["expressions", "visualizations", "data"],
"requiredBundles": ["expressions", "visualizations", "visDefaultEditor", "kibanaReact"]
}
73 changes: 73 additions & 0 deletions src/plugins/vis_type_script/public/expression/fn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition, Render } from '@kbn/expressions-plugin/public';
import { ExpressionValueSearchContext } from '@kbn/data-plugin/common';
import type { Arguments } from '../types';
import type { RenderValue } from './renderer';

type ScriptVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
'scriptVis',
ExpressionValueSearchContext | null,
Arguments,
Render<RenderValue>
>;

export const createScriptVisFn = (): ScriptVisExpressionFunctionDefinition => ({
name: 'scriptVis',
type: 'render',
inputTypes: ['kibana_context', 'null'],
help: i18n.translate('visTypeMarkdown.function.help', {
defaultMessage: 'Script-based visualization',
}),
args: {
script: {
types: ['string'],
required: true,
help: i18n.translate('visTypeScript.function.markdown.help', {
defaultMessage: 'Visualization script',
}),
},
scriptDependencyUrls: {
types: ['string'],
multi: true,
required: true,
help: i18n.translate('visTypeScript.function.markdown.help', {
defaultMessage: 'List of script dependencies',
}),
},
styleDependencyUrls: {
types: ['string'],
multi: true,
required: true,
help: i18n.translate('visTypeScript.function.markdown.help', {
defaultMessage: 'List of script dependencies',
}),
},
},
fn(input, args) {
return {
type: 'render',
as: 'script_vis',
value: {
visType: 'script',
visParams: {
script: args.script,
scriptDependencyUrls: args.scriptDependencyUrls,
styleDependencyUrls: args.styleDependencyUrls,
},
visSearchContext: {
timeRange: input?.timeRange,
query: input?.query,
filters: input?.filters,
},
},
};
},
});
63 changes: 63 additions & 0 deletions src/plugins/vis_type_script/public/expression/renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { IExternalUrl, IUiSettingsClient } from '@kbn/core/public';
import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin';
import { VisualizationContainer } from '@kbn/visualizations-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { VisParams, VisSearchContext } from '../types';
import { ScriptRenderer } from '../renderer';
import { VisTypeScriptKibanaApi } from '../kibana_api';

export interface RenderValue {
visType: 'script';
visParams: VisParams;
visSearchContext: VisSearchContext;
}

export const scriptVisRenderer: (
// TODO: not sure if this is correct way of passing deps to vis renderer
getDeps: () => Promise<{
data: DataPublicPluginStart;
uiSettingsClient: IUiSettingsClient;
validateUrl: IExternalUrl['validateUrl'];
nonce: string;
}>
) => ExpressionRenderDefinition<RenderValue> = (getDeps) => ({
name: 'script_vis',
displayName: 'script-based visualization',
reuseDomNode: true,
render: async (domNode, { visParams, visSearchContext }, handlers) => {
const deps = await getDeps();
const visTypeScriptKibanaApi = new VisTypeScriptKibanaApi(deps, visSearchContext);

handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});

// hack to always force re-render the iframe to pick up the latest state and to react on "refresh"
const keyToForceRerenderScript = Date.now();

render(
<VisualizationContainer className="scriptVis" handlers={handlers}>
<ScriptRenderer
key={keyToForceRerenderScript}
script={visParams.script}
scriptDependencyUrls={visParams.scriptDependencyUrls}
styleDependencyUrls={visParams.styleDependencyUrls}
kibanaApi={visTypeScriptKibanaApi}
validateUrl={deps.validateUrl}
nonce={deps.nonce}
/>
</VisualizationContainer>,
domNode
);
},
});
32 changes: 32 additions & 0 deletions src/plugins/vis_type_script/public/expression/to_ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 { VisToExpressionAst } from '@kbn/visualizations-plugin/public';
import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/public';
import type { ExpressionFunctionDefinition, Render } from '@kbn/expressions-plugin/public';
import type { RenderValue } from './renderer';
import { VisParams } from '../types';

type ScriptVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
'scriptVis',
unknown,
{},
Render<RenderValue>
>;

export const toExpressionAst: VisToExpressionAst<VisParams> = (vis) => {
const scriptVis = buildExpressionFunction<ScriptVisExpressionFunctionDefinition>('scriptVis', {
script: vis.params.script,
scriptDependencyUrls: vis.params.scriptDependencyUrls,
styleDependencyUrls: vis.params.styleDependencyUrls,
});

const ast = buildExpression([scriptVis]);

return ast.toAst();
};
14 changes: 14 additions & 0 deletions src/plugins/vis_type_script/public/index.ts
Original file line number Diff line number Diff line change
@@ -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.
*/

import { PluginInitializerContext } from '@kbn/core/public';
import { ScriptVisPlugin as Plugin } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
return new Plugin(initializerContext);
}
9 changes: 9 additions & 0 deletions src/plugins/vis_type_script/public/kibana_api/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Loading