kibana/x-pack/plugins/canvas/shareable_runtime
Yaroslav Kuznietsov b352976b3b
[Canvas] Expression reveal image. (#101987)
* expression_reveal_image skeleton.

* expression_functions added.

* expression_renderers added.

* Backup of daily work.

* Fixed errors.

* Added legacy support. Added button for legacy.

* Added storybook.

* Removed revealImage from canvas.

* setState while rendering error fixed.

* tsconfig.json added.

* jest.config.js added.

* Demo doc added.

* Types fixed.

* added limits.

* Removed not used imports.

* i18n namespaces fixed.

* Fixed test suite error.

* Some errors fixed.

* Fixed eslint error.

* Removed more unused translations.

* Moved UI and elements, related to expressionRevealImage from canvas.

* Fixed unused translations errors.

* Moved type of element to types.

* Fixed types and added service for representing elements, ui and supported renderers to canvas.

* Added expression registration to canvas.

* Fixed

* Fixed mutiple call of the function.

* Removed support of a legacy lib for revealImage chart.

* Removed legacy presentation_utils plugin import.

* Doc error fixed.

* Removed useless translations and tried to fix error.

* One more fix.

* Small imports fix.

* Fixed translations.

* Made fixes based on nits.

* Removed useless params.

* fix.

* Fixed errors, related to jest and __mocks__.

* Removed useless type definition.

* Replaced RendererHandlers with IInterpreterRendererHandlers.

* fixed supported_shareable.

* Moved elements back to canvas.

* Moved views to canvas, removed expression service and imported renderer to canvas.

* Fixed translations.

* Types fix.

* Moved libs to presentation utils.

* Fixed one mistake.

* removed dataurl lib.

* Fixed jest files.

* elasticLogo removed.

* Removed elastic_outline.

* removed httpurl.

* Removed missing_asset.

* removed url.

* replaced mostly all tests.

* Fixed types.

* Fixed types and removed function_wrapper.ts

* Fixed types of test helpers.

* Changed limits of presentationUtil plugin.

* Fixed imports.

* One more fix.

* Fixed huge size of bundle.

* Reduced allow limit for presentationUtil

* Updated limits for presentationUtil.

* Fixed public API.

* fixed type errors.

* Moved css to component.

* Fixed spaces at element.

* Changed order of requiredPlugins.

* Updated limits.

* Removed unused plugin.

* Added rule for allowing import from __stories__ directory.

* removed useless comment.

* Changed readme.md

* Fixed docs error.

* A possible of smoke test.

* onResize changed to useResizeObserver.

* Remove useless events and `useEffect` block.

* Changed from passing handlers to separate functions.

* `function` moved to `server`.

* Fixed eslint error.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
2021-07-01 13:30:00 +03:00
..
__mocks__
api Upgrade EUI to v33.0.0 (#99382) 2021-06-01 10:53:07 -05:00
components Upgrade EUI to v34.5.1 (#103297) 2021-06-28 11:31:23 -05:00
context
test
constants.d.ts
constants.js
constants_static.d.ts
constants_static.js
css_modules.d.ts
index.html
index.ts
postcss.config.js
README.mdx [canvas] Refactor Storybook from bespoke to standard configuration (#101962) 2021-06-17 18:57:44 -04:00
supported_renderers.d.ts
supported_renderers.js [Canvas] Expression reveal image. (#101987) 2021-07-01 13:30:00 +03:00
template.html
types.ts
webpack.config.js [Lens] Formula editor (#99297) 2021-06-10 10:09:16 -04:00

---
id: canvasShareableWorkpads
slug: /playground/kibana/canvas-shareable-workpads
title: Share a Canvas Workpad on a Website
summary: How to share a static snapshot of a workpad on an external website.
date: 2021-02-18
tags: ['kibana', 'canvas', 'share']
related: []
---

The Canvas Shareable Runtime is designed to render Shareable Canvas Workpads outside of Kibana in a different website or application. It uses the intermediate, "transient" state of a workpad, which is a JSON-blob state after element expressions are initially evaluated against their data sources, but before the elements are rendered to the screen. This "transient" state, therefore, has no dependency or access to ES/Kibana data, making it lightweight and portable.

This directory contains the code necessary to build and test this runtime.

## Quick Start

- Load a workpad in Canvas.
- Click "Export" -> "Share on a website" -> "download a ZIP file"
- Extract and change to the extracted directory.
- On your local machine:
  - Start a web server, like: `python -m SimpleHTTPServer 9001`
  - Open a web browser to `http://localhost:9001`
- On a remote webserver:
  - Add `kbn_canvas.js` and your Shared Workpad file to your web page:
    ```
    <script src="kbn_canvas.js"></script>
    ```
  - Add the HTML snippet to your webpage:
    ```
    <div kbn-canvas-shareable="canvas" kbn-canvas-url="[WORKPAD URL]" />
    ```
  - Execute the JS method:
    ```
      <script type="text/javascript">
        KbnCanvas.share();
      </script>
    ```

## Using the Runtime

### Assumptions

- The runtime is added to a web page using a standard `<script>` tag.
- A Shared Workpad JSON file (see: [Testing](#Testing)) is available via some known URL.

### Restrictions

Not all elements from a workpad may render in the runtime. See [Supported Expressions](#supported-expressions) for more details.

### JS

The runtime is a global library with `KbnCanvas` as the namespace. When executed, the `share` method will interpret any and all nodes that match the API. This function can be called from anywhere, in a script block at the bottom of the page, or after any other initialization.

```html
<script type="text/javascript">
  KbnCanvas.share();
</script>
```

### HTML

The Canvas Shareable Runtime will scan the DOM of a given web page looking for any element with `kbn-canvas-shareable="canvas"` as an attribute. This DOM node will be the host in which the workpad will be rendered. The node will also be sized and manipulated as necessary, but all other attributes, (such as `id`) will remain unaltered. A class name, `kbnCanvas`, will be _added_ to the DOM node.

> Note: Any content within this DOM node will be replaced.

Options to configure the runtime are included on the DOM node. The only required attribute is `kbn-canvas-url`, the URL from which the shared workpad can be loaded.

> Note: the workpad is loaded by `fetch`, therefore the runtime cannot be initialized on the local file system. Relative URLs are allowed.

Each attribute on the node that is correctly parsed will be removed. For example:

```html
<!-- Markup added to the source file. -->
<div kbn-canvas-shareable="canvas" kbn-canvas-height="400" kbn-canvas-url="workpad.json" />

<!-- Markup in the DOM after runtime processes it. -->
<div class="kbnCanvas" />
```

A sure sign that there was an error, or that an attribute was included that is not recognized, would be any attributes remaining:

```html
<!-- Markup added to the source file. -->
<div kbn-canvas-shareable="canvas" kbn-canvas-hieght="400" kbn-canvas-url="workpad.json" />

<!-- Markup in the DOM after runtime processes it. -->
<div class="kbnCanvas" kbn-canvas-hieght="400" />
```

### Options

The [`api/shareable.tsx`]('./api/shareable') component file contains the base class with available options to configure the Shareable Workpad. Each of these would be prefixed with `kbn-canvas-`:

```typescript
  /**
   * The preferred height to scale the Shareable Canvas Workpad.  If only `height` is
   * specified, `width` will be calculated by the workpad ratio.  If both are
   * specified, the ratio will be overriden by an absolute size.
   */
  height?: number;

  /**
   * The preferred width to scale the Shareable Canvas Workpad.  If only `width` is
   * specified, `height` will be calculated by the workpad ratio.  If both are
   * specified, the ratio will be overriden by an absolute size.
   */
  width?: number;

  /**
   * The initial page to display.
   */
  page?: number;

  /**
   * Should the runtime automatically move through the pages of the workpad?
   * @default false
   */
  autoplay?: boolean;

  /**
   * The interval upon which the pages will advance in time format, (e.g. 2s, 1m)
   * @default '5s'
   * */
  interval?: string;

  /**
   * Should the toolbar be hidden?
   * @default false
   */
  toolbar?: boolean;
```

## Testing

You can test this functionality in a number of ways. The easiest would be:

### Download a ZIP from Canvas

- Load a workpad in Canvas.
- Click "Export" -> "Share on a website" -> "download a ZIP file"
- Extract and change to the extracted directory.
- Start a web server, like: `python -m SimpleHTTPServer 9001`
- Open a web browser to `http://localhost:9001`

### Test the Runtime Directly from Webpack

- Load a workpad in Canvas.
- Click "Export" -> "Share on a website" -> "Download Workpad"
- Copy the workpad to `canvas/shareable_runtime/test`.
- Edit `canvas/shareable_runtime/index.html` to include your workpad.
- From `/canvas`, run `node scripts/shareable_runtime --run`
- Open a web browser to `http://localhost:8080`

### Run the Canvas Storybook

From `/kibana`: `yarn storybook canvas`

### Run the Jest Tests

The Jest tests utilize Enzyme to test interactions within the runtime, as well.

From `/canvas`: `node scripts/jest --path shareable_runtime`

#### Gathering Test Coverage

From `/canvas`: `node scripts/jest --path shareable_runtime --coverage`

## Building

Run `node scripts/shareable_runtime`. The runtime will be built and stored `shareable_runtime/build`.

### Build Options

By default, `scripts/shareable_runtime` will build a production-ready JS library. This takes a bit longer and produces a single file.

There are a number of options for the build script:

- `--dev` - allow Webpack to chunk the runtime into several files. This is helpful when developing the runtime itself.
- `--run` - run the Webpack Dev Server to develop and test the runtime. It will use HMR to incorporate changes.
- `--clean` - clean the runtime from the build directory.
- `--stats` - output Webpack statistics for the runtime.

## Development

### Prerequisite

Before testing or running this PR locally, you **must** run `node scripts/shareable_runtime` from `/canvas` _after_ `yarn kbn bootstrap` and _before_ starting Kibana. It is only built automatically when Kibana is built to avoid slowing down other development activities.

### Webpack Dev Server

To start the `webpack-dev-server` and test a workpad, simply run:

`/canvas`: `node scripts/shareable_runtime --dev --run`

A browser window should automatically open. If not, open a browser to [`http://localhost:8080/`](http://localhost:8080).

The `index.html` file contains a call to the `CanvasShareable` runtime. Currently, you can share by object or by url:

```html
<script src="kbn_canvas.js"></script>
...
<div kbn-canvas-shareable="canvas" kbn-canvas-height="400" kbn-canvas-url="workpad.json"></div>
<script type="text/javascript">
  KbnCanvas.share();
</script>
```

There are three test workpads available, in `test/workpads`.

### Gathering Statistics

Webpack will output a `stats.json` file for analysis. This allows us to know how large the runtime is, where the largest dependencies are coming from, and how we might prune down its size. Two popular sites are:

- Official Webpack Analysis tool: http://webpack.github.io/analyse/
- Webpack Visualizer: https://chrisbateman.github.io/webpack-visualizer/

## Architecture

The Shareable Runtime is an independently-built artifact for use outside of Kibana. It consists of two parts: the Build and the App.

### The Build

A custom Webpack build is used to incorporate code from Canvas, Kibana and EUI into a single file for distribution. This code interprets the shared JSON workpad file and renders the pages of elements within the area provided.

#### Supported Expressions

Because Shareable Workpads are not connected to any data source in Kibana or otherwise, the runtime simply renders the transient state of the workpad at the time it was shared from within Canvas. So elements that are used to manipulate data, (e.g. filtering controls like `time_filter` or `dropdown_filter`) are not included in the runtime. This lowers the runtime size. Any element that uses an excluded renderer will render nothing in their place. Users are warned within Canvas as they download the Shared Workpad if their workpad contains any of these non-rendered controls.

> Note: Since the runtime is statically built with the Kibana release, renderers provided by plugins are not supported. Functions that use standard renderers, provided they are not data-manipulating, will still work as expected.

#### Expression Interpreter

Kibana and Canvas use an interpreter to register expressions and then eventually evaluate them at run time. Most of the code within the interpreter is not needed for the Shareable Runtime. As a result, a bespoke interpreter is used instead.

#### Build Size

At the moment, the resulting library is relatively large, (5.6M). This is due to the bundling of dependencies like EUI. By trading off file size, we're able to keep the library contained without a need to download other external dependencies, (like React). We're working to reduce that size through further tree-shaking or compression.

### The App

The App refers to the user interface that is embedded on the consuming web page and displays the workpad.

#### App State

To minimize the distribution size, we opted to avoid as many libraries as possible in the UI. So while Canvas uses Redux to maintain state, we opted for a React Context + Hooks-based approach with a custom reducer. This code can be found in `shareable_runtime/context`.

#### CSS

All CSS in the runtime UI uses CSS Modules to sandbox and obfuscate class names. In addition, the Webpack build uses `postcss-prefix-selector` to prefix all public class names from Kibana and EUI with `.kbnCanvas`. As a result, all class names should be sandboxed and not interfere with the host page in any way.