diff --git a/x-pack/package.json b/x-pack/package.json
index 477f8eed37b2f..77ce03b079ec9 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -75,6 +75,7 @@
"@types/hoist-non-react-statics": "^3.3.1",
"@types/history": "^4.7.3",
"@types/jest": "^25.2.3",
+ "@types/jest-specific-snapshot": "^0.5.4",
"@types/joi": "^13.4.2",
"@types/js-search": "^1.4.0",
"@types/js-yaml": "^3.11.1",
@@ -119,6 +120,7 @@
"@types/xml2js": "^0.4.5",
"@types/stats-lite": "^2.2.0",
"@types/pretty-ms": "^5.0.0",
+ "@types/webpack-env": "^1.15.2",
"@welldone-software/why-did-you-render": "^4.0.0",
"abab": "^1.0.4",
"autoprefixer": "^9.7.4",
@@ -158,6 +160,7 @@
"jest-cli": "^25.5.4",
"jest-styled-components": "^7.0.2",
"jsdom": "13.1.0",
+ "jsondiffpatch": "0.4.1",
"loader-utils": "^1.2.3",
"madge": "3.4.4",
"marge": "^1.0.1",
@@ -395,4 +398,4 @@
"cypress-multi-reporters"
]
}
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.stories.tsx b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.stories.tsx
new file mode 100644
index 0000000000000..0b99bbce50288
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.stories.tsx
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { action } from '@storybook/addon-actions';
+import { storiesOf } from '@storybook/react';
+import React from 'react';
+import { reduxDecorator, getAddonPanelParameters } from '../../../../storybook';
+import { Asset, AssetComponent } from '../';
+import { AIRPLANE, MARKER, assets } from './assets';
+
+storiesOf('components/Assets/Asset', module)
+ .addDecorator((story) =>
{story()}
)
+ .addDecorator(reduxDecorator({ assets }))
+ .addParameters(getAddonPanelParameters())
+ .add('redux: Asset', () => {
+ return ;
+ })
+ .add('airplane', () => (
+
+ ))
+ .add('marker', () => (
+
+ ));
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.stories.tsx b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.stories.tsx
index 1434ef60cf0d8..673c66734b39a 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.stories.tsx
+++ b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.stories.tsx
@@ -4,35 +4,29 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import React from 'react';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
-import React from 'react';
+import { reduxDecorator, getAddonPanelParameters } from '../../../../storybook';
import { AssetManager, AssetManagerComponent } from '../';
-
-import { Provider, AIRPLANE, MARKER } from './provider';
+import { assets } from './assets';
storiesOf('components/Assets/AssetManager', module)
- .add('redux: AssetManager', () => (
-
-
-
- ))
+ .addDecorator(reduxDecorator({ assets }))
+ .addParameters(getAddonPanelParameters())
+ .add('redux: AssetManager', () => )
.add('no assets', () => (
-
-
-
+
))
.add('two assets', () => (
-
-
-
+
));
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/assets.ts b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/assets.ts
new file mode 100644
index 0000000000000..3b5576667ed26
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/assets.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { AssetType } from '../../../../types';
+
+export const AIRPLANE: AssetType = {
+ '@created': '2018-10-13T16:44:44.648Z',
+ id: 'airplane',
+ type: 'dataurl',
+ value:
+ 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1Ni4zMSA1Ni4zMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMDc4YTA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlBsYW5lIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDkuNTEsNDguOTMsNDEuMjYsMjIuNTIsNTMuNzYsMTBhNS4yOSw1LjI5LDAsMCwwLTcuNDgtNy40N2wtMTIuNSwxMi41TDcuMzgsNi43OUEuNy43LDAsMCwwLDYuNjksN0wxLjIsMTIuNDVhLjcuNywwLDAsMCwwLDFMMTkuODUsMjlsLTcuMjQsNy4yNC03Ljc0LS42YS43MS43MSwwLDAsMC0uNTMuMkwxLjIxLDM5YS42Ny42NywwLDAsMCwuMDgsMUw5LjQ1LDQ2bC4wNywwYy4xMS4xMy4yMi4yNi4zNC4zOHMuMjUuMjMuMzguMzRhLjM2LjM2LDAsMCwwLDAsLjA3TDE2LjMzLDU1YS42OC42OCwwLDAsMCwxLC4wN0wyMC40OSw1MmEuNjcuNjcsMCwwLDAsLjE5LS41NGwtLjU5LTcuNzQsNy4yNC03LjI0TDQyLjg1LDU1LjA2YS42OC42OCwwLDAsMCwxLDBsNS41LTUuNUEuNjYuNjYsMCwwLDAsNDkuNTEsNDguOTNaIi8+PC9nPjwvZz48L3N2Zz4=',
+};
+
+export const MARKER: AssetType = {
+ '@created': '2018-10-13T16:44:44.648Z',
+ id: 'marker',
+ type: 'dataurl',
+ value:
+ 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOC4zOSA1Ny41NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMTliOGY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkxvY2F0aW9uIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTkuMTksMUExOC4xOSwxOC4xOSwwLDAsMCwyLjk0LDI3LjM2aDBhMTkuNTEsMTkuNTEsMCwwLDAsMSwxLjc4TDE5LjE5LDU1LjU3LDM0LjM4LDI5LjIxQTE4LjE5LDE4LjE5LDAsMCwwLDE5LjE5LDFabTAsMjMuMjlhNS41Myw1LjUzLDAsMSwxLDUuNTMtNS41M0E1LjUzLDUuNTMsMCwwLDEsMTkuMTksMjQuMjlaIi8+PC9nPjwvZz48L3N2Zz4=',
+};
+
+export const assets = [AIRPLANE, MARKER];
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/provider.tsx b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/provider.tsx
deleted file mode 100644
index 1cd7562b59c47..0000000000000
--- a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/provider.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-/* eslint-disable no-console */
-
-/*
- This Provider is temporary. See https://github.com/elastic/kibana/pull/69357
-*/
-
-import React, { FC } from 'react';
-import { applyMiddleware, createStore, Dispatch, Store } from 'redux';
-import thunkMiddleware from 'redux-thunk';
-import { Provider as ReduxProvider } from 'react-redux';
-
-// @ts-expect-error untyped local
-import { appReady } from '../../../../public/state/middleware/app_ready';
-// @ts-expect-error untyped local
-import { resolvedArgs } from '../../../../public/state/middleware/resolved_args';
-
-// @ts-expect-error untyped local
-import { getRootReducer } from '../../../../public/state/reducers';
-
-// @ts-expect-error Untyped local
-import { getDefaultWorkpad } from '../../../../public/state/defaults';
-import { State, AssetType } from '../../../../types';
-
-export const AIRPLANE: AssetType = {
- '@created': '2018-10-13T16:44:44.648Z',
- id: 'airplane',
- type: 'dataurl',
- value:
- 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1Ni4zMSA1Ni4zMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMDc4YTA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlBsYW5lIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDkuNTEsNDguOTMsNDEuMjYsMjIuNTIsNTMuNzYsMTBhNS4yOSw1LjI5LDAsMCwwLTcuNDgtNy40N2wtMTIuNSwxMi41TDcuMzgsNi43OUEuNy43LDAsMCwwLDYuNjksN0wxLjIsMTIuNDVhLjcuNywwLDAsMCwwLDFMMTkuODUsMjlsLTcuMjQsNy4yNC03Ljc0LS42YS43MS43MSwwLDAsMC0uNTMuMkwxLjIxLDM5YS42Ny42NywwLDAsMCwuMDgsMUw5LjQ1LDQ2bC4wNywwYy4xMS4xMy4yMi4yNi4zNC4zOHMuMjUuMjMuMzguMzRhLjM2LjM2LDAsMCwwLDAsLjA3TDE2LjMzLDU1YS42OC42OCwwLDAsMCwxLC4wN0wyMC40OSw1MmEuNjcuNjcsMCwwLDAsLjE5LS41NGwtLjU5LTcuNzQsNy4yNC03LjI0TDQyLjg1LDU1LjA2YS42OC42OCwwLDAsMCwxLDBsNS41LTUuNUEuNjYuNjYsMCwwLDAsNDkuNTEsNDguOTNaIi8+PC9nPjwvZz48L3N2Zz4=',
-};
-
-export const MARKER: AssetType = {
- '@created': '2018-10-13T16:44:44.648Z',
- id: 'marker',
- type: 'dataurl',
- value:
- 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOC4zOSA1Ny41NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMTliOGY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkxvY2F0aW9uIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTkuMTksMUExOC4xOSwxOC4xOSwwLDAsMCwyLjk0LDI3LjM2aDBhMTkuNTEsMTkuNTEsMCwwLDAsMSwxLjc4TDE5LjE5LDU1LjU3LDM0LjM4LDI5LjIxQTE4LjE5LDE4LjE5LDAsMCwwLDE5LjE5LDFabTAsMjMuMjlhNS41Myw1LjUzLDAsMSwxLDUuNTMtNS41M0E1LjUzLDUuNTMsMCwwLDEsMTkuMTksMjQuMjlaIi8+PC9nPjwvZz48L3N2Zz4=',
-};
-
-export const state: State = {
- app: {
- basePath: '/',
- ready: true,
- serverFunctions: [],
- },
- assets: {
- AIRPLANE,
- MARKER,
- },
- transient: {
- canUserWrite: true,
- zoomScale: 1,
- elementStats: {
- total: 0,
- ready: 0,
- pending: 0,
- error: 0,
- },
- inFlight: false,
- fullScreen: false,
- selectedTopLevelNodes: [],
- resolvedArgs: {},
- refresh: {
- interval: 0,
- },
- autoplay: {
- enabled: false,
- interval: 10000,
- },
- },
- persistent: {
- schemaVersion: 2,
- workpad: getDefaultWorkpad(),
- },
-};
-
-// @ts-expect-error untyped local
-import { elementsRegistry } from '../../../lib/elements_registry';
-import { image } from '../../../../canvas_plugin_src/elements/image';
-elementsRegistry.register(image);
-
-export const patchDispatch: (store: Store, dispatch: Dispatch) => Dispatch = (store, dispatch) => (
- action
-) => {
- const previousState = store.getState();
- const returnValue = dispatch(action);
- const newState = store.getState();
-
- console.group(action.type || '(thunk)');
- console.log('Previous State', previousState);
- console.log('New State', newState);
- console.groupEnd();
-
- return returnValue;
-};
-
-export const Provider: FC = ({ children }) => {
- const middleware = applyMiddleware(thunkMiddleware);
- const reducer = getRootReducer(state);
- const store = createStore(reducer, state, middleware);
- store.dispatch = patchDispatch(store, store.dispatch);
-
- return {children};
-};
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset.component.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset.component.tsx
index a04d37cf7f9fc..ed000741bc542 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/asset.component.tsx
+++ b/x-pack/plugins/canvas/public/components/asset_manager/asset.component.tsx
@@ -17,7 +17,7 @@ import {
EuiToolTip,
} from '@elastic/eui';
-import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
+import { useNotifyService } from '../../services';
import { ConfirmModal } from '../confirm_modal';
import { Clipboard } from '../clipboard';
@@ -38,11 +38,10 @@ interface Props {
}
export const Asset: FC = ({ asset, onCreate, onDelete }) => {
- const { services } = useKibana();
+ const { success } = useNotifyService();
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);
- const onCopy = (result: boolean) =>
- result && services.canvas.notify.success(`Copied '${asset.id}' to clipboard`);
+ const onCopy = (result: boolean) => result && success(`Copied '${asset.id}' to clipboard`);
const confirmModal = (
new CanvasPlugin();
+export const plugin = (_initializerContext: PluginInitializerContext) => new CanvasPlugin();
diff --git a/x-pack/plugins/canvas/public/services/context.tsx b/x-pack/plugins/canvas/public/services/context.tsx
index 9bd86ef98f1e3..9f79e81369b6b 100644
--- a/x-pack/plugins/canvas/public/services/context.tsx
+++ b/x-pack/plugins/canvas/public/services/context.tsx
@@ -12,7 +12,7 @@ import React, {
FC,
ReactElement,
} from 'react';
-import { CanvasServices, CanvasServiceProviders } from '.';
+import { CanvasServices, CanvasServiceProviders, services } from '.';
export interface WithServicesProps {
services: CanvasServices;
@@ -36,23 +36,22 @@ export const useNotifyService = () => useServices().notify;
export const useNavLinkService = () => useServices().navLink;
export const withServices = (type: ComponentType) => {
- const EnhancedType: FC = (props) => {
- const services = useServices();
- return createElement(type, { ...props, services });
- };
+ const EnhancedType: FC = (props) =>
+ createElement(type, { ...props, services: useServices() });
return EnhancedType;
};
export const ServicesProvider: FC<{
- providers: CanvasServiceProviders;
+ providers?: Partial;
children: ReactElement;
-}> = ({ providers, children }) => {
+}> = ({ providers = {}, children }) => {
+ const specifiedProviders: CanvasServiceProviders = { ...services, ...providers };
const value = {
- embeddables: providers.embeddables.getService(),
- expressions: providers.expressions.getService(),
- notify: providers.notify.getService(),
- platform: providers.platform.getService(),
- navLink: providers.navLink.getService(),
+ embeddables: specifiedProviders.embeddables.getService(),
+ expressions: specifiedProviders.expressions.getService(),
+ notify: specifiedProviders.notify.getService(),
+ platform: specifiedProviders.platform.getService(),
+ navLink: specifiedProviders.navLink.getService(),
};
return {children};
};
diff --git a/x-pack/plugins/canvas/scripts/storybook.js b/x-pack/plugins/canvas/scripts/storybook.js
index beea1814b54d2..671de53d74407 100644
--- a/x-pack/plugins/canvas/scripts/storybook.js
+++ b/x-pack/plugins/canvas/scripts/storybook.js
@@ -24,7 +24,7 @@ const storybookOptions = {
run(
({ log, flags }) => {
- const { dll, clean, stats, site } = flags;
+ const { addon, dll, clean, stats, site } = flags;
// Delete the existing DLL if we're cleaning or building.
if (clean || dll) {
@@ -81,13 +81,20 @@ run(
return;
}
+ // Build the addon
+ execa.sync('node', ['scripts/build'], {
+ cwd: path.resolve(__dirname, '../storybook/addon'),
+ stdio: ['ignore', 'inherit', 'inherit'],
+ buffer: false,
+ });
+
// Build site and exit
if (site) {
log.success('storybook: Generating Storybook site');
storybook({
...storybookOptions,
mode: 'static',
- outputDir: path.resolve(__dirname, './../storybook'),
+ outputDir: path.resolve(__dirname, './../storybook/build'),
});
return;
}
@@ -100,6 +107,14 @@ run(
...options,
});
+ if (addon) {
+ execa('node', ['scripts/build', '--watch'], {
+ cwd: path.resolve(__dirname, '../storybook/addon'),
+ stdio: ['ignore', 'inherit', 'inherit'],
+ buffer: false,
+ });
+ }
+
storybook({
...storybookOptions,
port: 9001,
@@ -110,8 +125,9 @@ run(
Storybook runner for Canvas.
`,
flags: {
- boolean: ['dll', 'clean', 'stats', 'site'],
+ boolean: ['addon', 'dll', 'clean', 'stats', 'site'],
help: `
+ --addon Watch the addon source code for changes.
--clean Forces a clean of the Storybook DLL and exits.
--dll Cleans and builds the Storybook dependency DLL and exits.
--stats Produces a Webpack stats file.
diff --git a/x-pack/plugins/canvas/storybook/addon/babel.config.js b/x-pack/plugins/canvas/storybook/addon/babel.config.js
new file mode 100644
index 0000000000000..5081cf455906f
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/babel.config.js
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+module.exports = {
+ presets: ['@kbn/babel-preset/webpack_preset'],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+};
diff --git a/x-pack/plugins/canvas/storybook/addon/scripts/build.js b/x-pack/plugins/canvas/storybook/addon/scripts/build.js
new file mode 100644
index 0000000000000..b3525244fad25
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/scripts/build.js
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+const { resolve } = require('path');
+
+const del = require('del');
+const supportsColor = require('supports-color');
+const { run, withProcRunner } = require('@kbn/dev-utils');
+
+const ROOT_DIR = resolve(__dirname, '..');
+const BUILD_DIR = resolve(ROOT_DIR, 'target');
+
+const padRight = (width, str) =>
+ str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`;
+
+run(
+ async ({ log, flags }) => {
+ await withProcRunner(log, async (proc) => {
+ if (!flags.watch) {
+ log.info('Deleting old output');
+ await del(BUILD_DIR);
+ }
+
+ const cwd = ROOT_DIR;
+ const env = { process };
+
+ if (supportsColor.stdout) {
+ env.FORCE_COLOR = 'true';
+ }
+
+ log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`);
+ await proc.run(padRight(10, `babel`), {
+ cmd: 'babel',
+ args: [
+ 'src',
+ '--config-file',
+ require.resolve('../babel.config.js'),
+ '--out-dir',
+ BUILD_DIR,
+ '--extensions',
+ '.ts,.js,.tsx',
+ '--copy-files',
+ ...(flags.watch ? ['--watch'] : ['--quiet']),
+ ],
+ wait: true,
+ env,
+ cwd,
+ });
+
+ log.success('Complete');
+ });
+ },
+ {
+ description: 'Simple build tool for Canvas Storybook addon',
+ flags: {
+ boolean: ['watch'],
+ help: `
+ --watch Run in watch mode
+ `,
+ },
+ }
+);
diff --git a/x-pack/plugins/canvas/storybook/addon/src/components/action_list.tsx b/x-pack/plugins/canvas/storybook/addon/src/components/action_list.tsx
new file mode 100644
index 0000000000000..9c29a44a67318
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/components/action_list.tsx
@@ -0,0 +1,77 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, useEffect, useState } from 'react';
+import { EuiSelectable, EuiSelectableOption } from '@elastic/eui';
+import addons from '@storybook/addons';
+import uuid from 'uuid/v4';
+
+import { EVENTS } from '../constants';
+import { RecordedAction, RecordedPayload } from '../types';
+
+export const ActionList: FC<{
+ onSelect: (action: RecordedAction | null) => void;
+}> = ({ onSelect }) => {
+ const [recordedActions, setRecordedActions] = useState>({});
+ const [selectedAction, setSelectedAction] = useState(null);
+
+ useEffect(() => {
+ onSelect(selectedAction);
+ }, [onSelect, selectedAction]);
+
+ useEffect(() => {
+ const actionListener = (newAction: RecordedPayload) => {
+ const id = uuid();
+ setRecordedActions({ ...recordedActions, [id]: { ...newAction, id } });
+ };
+
+ const resetListener = () => {
+ setSelectedAction(null);
+ setRecordedActions({});
+ };
+
+ const channel = addons.getChannel();
+ channel.addListener(EVENTS.ACTION, actionListener);
+ channel.addListener(EVENTS.RESET, resetListener);
+
+ return () => {
+ channel.removeListener(EVENTS.ACTION, actionListener);
+ channel.removeListener(EVENTS.RESET, resetListener);
+ };
+ });
+
+ useEffect(() => {
+ const values = Object.values(recordedActions);
+ if (values.length > 0) {
+ setSelectedAction(values[values.length - 1]);
+ }
+ }, [recordedActions]);
+
+ const options: EuiSelectableOption[] = Object.values(recordedActions).map((recordedAction) => ({
+ id: recordedAction.id,
+ key: recordedAction.id,
+ label: recordedAction.action.type,
+ checked: recordedAction.id === selectedAction?.id ? 'on' : undefined,
+ }));
+
+ const onChange: (selectedOptions: EuiSelectableOption[]) => void = (selectedOptions) => {
+ selectedOptions.forEach((option) => {
+ if (option && option.checked && option.id) {
+ const selected = recordedActions[option.id];
+
+ if (selected) {
+ setSelectedAction(selected);
+ }
+ }
+ });
+ };
+
+ return (
+
+ {(list) => list}
+
+ );
+};
diff --git a/x-pack/plugins/canvas/storybook/addon/src/components/action_tree.tsx b/x-pack/plugins/canvas/storybook/addon/src/components/action_tree.tsx
new file mode 100644
index 0000000000000..351b94edb351f
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/components/action_tree.tsx
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import { isObject, isDate } from 'lodash';
+import uuid from 'uuid/v4';
+import { EuiTreeView } from '@elastic/eui';
+
+import { Node } from '@elastic/eui/src/components/tree_view/tree_view';
+
+import { RecordedAction } from '../types';
+
+const actionToTree = (recordedAction: RecordedAction) => {
+ const { action, newState, previousState } = recordedAction;
+
+ return [
+ {
+ label: 'Action',
+ id: uuid(),
+ children: jsonToTree(action),
+ },
+ {
+ label: 'Previous State',
+ id: uuid(),
+ children: jsonToTree(previousState),
+ },
+ {
+ label: 'Current State',
+ id: uuid(),
+ children: jsonToTree(newState),
+ },
+ ];
+};
+
+const jsonToTree: (obj: Record) => Node[] = (obj) => {
+ const keys = Object.keys(obj);
+
+ const values = keys.map((label) => {
+ const value = obj[label];
+
+ if (!value) {
+ return null;
+ }
+
+ const id = uuid();
+
+ if (isDate(value)) {
+ return { label: `${label}: ${(value as Date).toDateString()}` };
+ }
+
+ if (isObject(value)) {
+ const children = jsonToTree(value);
+
+ if (children !== null && Object.keys(children).length > 0) {
+ return { label, id, children };
+ } else {
+ return { label, id };
+ }
+ }
+
+ return { label: `${label}: ${value.toString().slice(0, 100)}`, id };
+ });
+
+ return values.filter((value) => value !== null) as Node[];
+};
+
+export const ActionTree: FC<{ action: RecordedAction | null }> = ({ action }) => {
+ const items = action ? actionToTree(action) : null;
+ let tree = <>>;
+
+ if (action && items) {
+ tree = (
+
+ );
+ } else if (action) {
+ tree = No change
;
+ }
+
+ return tree;
+};
diff --git a/x-pack/plugins/canvas/storybook/addons.js b/x-pack/plugins/canvas/storybook/addon/src/components/index.ts
similarity index 66%
rename from x-pack/plugins/canvas/storybook/addons.js
rename to x-pack/plugins/canvas/storybook/addon/src/components/index.ts
index 75bbe620c9e7b..5acb1acf3b459 100644
--- a/x-pack/plugins/canvas/storybook/addons.js
+++ b/x-pack/plugins/canvas/storybook/addon/src/components/index.ts
@@ -4,6 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import '@storybook/addon-actions/register';
-import '@storybook/addon-knobs/register';
-import '@storybook/addon-console';
+export { ActionList } from './action_list';
+export { ActionTree } from './action_tree';
diff --git a/x-pack/plugins/canvas/storybook/addon/src/components/state_change.tsx b/x-pack/plugins/canvas/storybook/addon/src/components/state_change.tsx
new file mode 100644
index 0000000000000..4db3c23c93843
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/components/state_change.tsx
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import { EuiAccordion } from '@elastic/eui';
+import { formatters } from 'jsondiffpatch';
+
+import { RecordedAction } from '../types';
+
+interface Props {
+ action: RecordedAction | null;
+}
+
+export const StateChange: FC = ({ action }) => {
+ if (!action) {
+ return null;
+ }
+
+ const { change, previousState } = action;
+ const html = formatters.html.format(change, previousState);
+ formatters.html.hideUnchanged();
+
+ return (
+
+ {/* eslint-disable-next-line react/no-danger */}
+
+
+ );
+};
diff --git a/x-pack/plugins/canvas/storybook/addon/src/constants.ts b/x-pack/plugins/canvas/storybook/addon/src/constants.ts
new file mode 100644
index 0000000000000..fb2646ef3ba8f
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/constants.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const ADDON_ID = 'kbn-canvas/redux-actions';
+export const ACTIONS_PANEL_ID = `${ADDON_ID}/panel`;
+
+const RESULT = `${ADDON_ID}/result`;
+const REQUEST = `${ADDON_ID}/request`;
+const ACTION = `${ADDON_ID}/action`;
+const RESET = `${ADDON_ID}/reset`;
+
+export const EVENTS = { ACTION, RESULT, REQUEST, RESET };
diff --git a/x-pack/plugins/canvas/storybook/addon/src/panel.css b/x-pack/plugins/canvas/storybook/addon/src/panel.css
new file mode 100644
index 0000000000000..b2b6591343b5f
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/panel.css
@@ -0,0 +1,171 @@
+.panel__tree {
+ font-family: monospace;
+ font-size: 85%;
+}
+
+.panel__tree .euiTreeView {
+ padding-left: 12px;
+ font-size: 85%;
+}
+
+.panel__resizeableContainer {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+}
+
+.panel__stateChange .euiAccordion__button {
+ font-size: 12px;
+ font-family: monospace;
+}
+
+.panel__stateChange .euiAccordion__iconWrapper {
+ transform: scale(.80);
+ transform-origin: top left;
+ margin: 8px 0px 4px 7px;
+}
+
+.jsondiffpatch-delta {
+ font-family: monospace;
+ font-size: 12px;
+ line-height: 20px;
+ margin: 0;
+ padding: 0 0 0 12px;
+ display: inline-block;
+}
+.jsondiffpatch-delta pre {
+ font-size: 12px;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+}
+ul.jsondiffpatch-delta {
+ list-style-type: none;
+ padding: 0 0 0 20px;
+ margin: 0;
+}
+.jsondiffpatch-delta ul {
+ list-style-type: none;
+ padding: 0 0 0 20px;
+ margin: 0;
+}
+
+.jsondiffpatch-added .jsondiffpatch-property-name,
+.jsondiffpatch-added .jsondiffpatch-value pre,
+.jsondiffpatch-modified .jsondiffpatch-right-value pre,
+.jsondiffpatch-textdiff-added {
+ background: #bbffbb;
+}
+
+.jsondiffpatch-deleted .jsondiffpatch-property-name,
+.jsondiffpatch-deleted pre,
+.jsondiffpatch-modified .jsondiffpatch-left-value pre,
+.jsondiffpatch-textdiff-deleted {
+ background: #ffbbbb;
+ text-decoration: line-through;
+}
+
+.jsondiffpatch-unchanged { display: none; }
+
+.jsondiffpatch-value {
+ display: inline-block;
+}
+
+.jsondiffpatch-property-name {
+ display: inline-block;
+ padding-right: 5px;
+ vertical-align: top;
+}
+
+.jsondiffpatch-property-name:after {
+ content: ': ';
+}
+
+.jsondiffpatch-child-node-type-array > .jsondiffpatch-property-name:after {
+ content: ': [';
+}
+
+.jsondiffpatch-child-node-type-array:after {
+ content: '],';
+}
+
+div.jsondiffpatch-child-node-type-array:before {
+ content: '[';
+}
+
+div.jsondiffpatch-child-node-type-array:after {
+ content: ']';
+}
+
+.jsondiffpatch-child-node-type-object > .jsondiffpatch-property-name:after {
+ content: ': {';
+}
+
+.jsondiffpatch-child-node-type-object:after {
+ content: '},';
+}
+
+div.jsondiffpatch-child-node-type-object:before {
+ content: '{';
+}
+
+div.jsondiffpatch-child-node-type-object:after {
+ content: '}';
+}
+
+.jsondiffpatch-value pre:after {
+ content: ',';
+}
+
+li:last-child > .jsondiffpatch-value pre:after,
+.jsondiffpatch-modified > .jsondiffpatch-left-value pre:after {
+ content: '';
+}
+
+.jsondiffpatch-modified .jsondiffpatch-value {
+ display: inline-block;
+}
+
+.jsondiffpatch-modified .jsondiffpatch-right-value {
+ margin-left: 5px;
+}
+
+.jsondiffpatch-moved .jsondiffpatch-value {
+ display: none;
+}
+
+.jsondiffpatch-moved .jsondiffpatch-moved-destination {
+ display: inline-block;
+ background: #ffffbb;
+ color: #888;
+}
+
+.jsondiffpatch-moved .jsondiffpatch-moved-destination:before {
+ content: ' => ';
+}
+
+ul.jsondiffpatch-textdiff {
+ padding: 0;
+}
+
+.jsondiffpatch-textdiff-location {
+ color: #bbb;
+ display: inline-block;
+ min-width: 60px;
+}
+
+.jsondiffpatch-textdiff-line {
+ display: inline-block;
+}
+
+.jsondiffpatch-textdiff-line-number:after {
+ content: ',';
+}
+
+.jsondiffpatch-error {
+ background: red;
+ color: white;
+ font-weight: bold;
+}
diff --git a/x-pack/plugins/canvas/storybook/addon/src/panel.tsx b/x-pack/plugins/canvas/storybook/addon/src/panel.tsx
new file mode 100644
index 0000000000000..adf6e8555c00a
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/panel.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState } from 'react';
+import { EuiResizableContainer } from '@elastic/eui';
+import { StateChange } from './components/state_change';
+
+import '@elastic/eui/dist/eui_theme_light.css';
+import './panel.css';
+
+import { RecordedAction } from './types';
+import { ActionList, ActionTree } from './components';
+
+export const Panel = () => {
+ const [selectedAction, setSelectedAction] = useState(null);
+
+ return (
+
+ {(EuiResizablePanel, EuiResizableButton) => (
+ <>
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/canvas/storybook/addon/src/register.tsx b/x-pack/plugins/canvas/storybook/addon/src/register.tsx
new file mode 100644
index 0000000000000..3a5c4a6818ac1
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/register.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/* eslint-disable import/no-extraneous-dependencies */
+
+import React from 'react';
+import { addons, types } from '@storybook/addons';
+import { AddonPanel } from '@storybook/components';
+import { STORY_CHANGED } from '@storybook/core-events';
+
+import { ADDON_ID, EVENTS, ACTIONS_PANEL_ID } from './constants';
+import { Panel } from './panel';
+
+addons.register(ADDON_ID, (api) => {
+ const channel = addons.getChannel();
+
+ api.on(STORY_CHANGED, (storyId) => {
+ channel.emit(EVENTS.RESET, storyId);
+ });
+
+ addons.add(ACTIONS_PANEL_ID, {
+ title: 'Redux Actions',
+ type: types.PANEL,
+ render: ({ active, key }) => {
+ return (
+
+
+
+ );
+ },
+ });
+});
diff --git a/x-pack/plugins/canvas/storybook/addon/src/state.ts b/x-pack/plugins/canvas/storybook/addon/src/state.ts
new file mode 100644
index 0000000000000..6d601fff7184a
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/state.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/* es-lint-disable import/no-extraneous-dependencies */
+import { applyMiddleware, Dispatch, Store } from 'redux';
+import thunkMiddleware from 'redux-thunk';
+import addons from '@storybook/addons';
+import { diff } from 'jsondiffpatch';
+import { isFunction } from 'lodash';
+
+import { EVENTS } from './constants';
+
+// @ts-expect-error untyped local
+import { appReady } from '../../../public/state/middleware/app_ready';
+// @ts-expect-error untyped local
+import { resolvedArgs } from '../../../public/state/middleware/resolved_args';
+
+// @ts-expect-error untyped local
+import { getRootReducer } from '../../../public/state/reducers';
+
+// @ts-expect-error Untyped local
+import { getDefaultWorkpad } from '../../../public/state/defaults';
+// @ts-expect-error Untyped local
+import { getInitialState as getState } from '../../../public/state/initial_state';
+import { State } from '../../../types';
+
+export const getInitialState: () => State = () => getState();
+export const getMiddleware = () => applyMiddleware(thunkMiddleware);
+export const getReducer = () => getRootReducer(getInitialState());
+
+export const patchDispatch: (store: Store, dispatch: Dispatch) => Dispatch = (store, dispatch) => (
+ action
+) => {
+ const channel = addons.getChannel();
+
+ const previousState = store.getState();
+ const returnValue = dispatch(action);
+ const newState = store.getState();
+ const change = diff(previousState, newState) || {};
+
+ channel.emit(EVENTS.ACTION, {
+ previousState,
+ newState,
+ change,
+ action: isFunction(action) ? { type: '(thunk)' } : action,
+ });
+
+ return returnValue;
+};
diff --git a/x-pack/plugins/canvas/storybook/addon/src/types.ts b/x-pack/plugins/canvas/storybook/addon/src/types.ts
new file mode 100644
index 0000000000000..e8a2cb70c89ff
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/src/types.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Action } from 'redux';
+import { State } from '../../../types';
+
+export interface RecordedPayload {
+ previousState: State;
+ newState: State;
+ change: Partial;
+ action: Action;
+}
+
+export interface RecordedAction extends RecordedPayload {
+ id: string;
+}
diff --git a/x-pack/plugins/canvas/storybook/addon/tsconfig.json b/x-pack/plugins/canvas/storybook/addon/tsconfig.json
new file mode 100644
index 0000000000000..9cab0af235f2e
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/addon/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../../../tsconfig.json",
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx"
+ ],
+ "exclude": [
+ "target"
+ ],
+ "compilerOptions": {
+ "declaration": false,
+ }
+}
diff --git a/x-pack/plugins/canvas/storybook/config.js b/x-pack/plugins/canvas/storybook/config.js
deleted file mode 100644
index dc16d6c46084d..0000000000000
--- a/x-pack/plugins/canvas/storybook/config.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { configure, addDecorator, addParameters } from '@storybook/react';
-import { withInfo } from '@storybook/addon-info';
-import { create } from '@storybook/theming';
-
-import { startServices } from '../public/services/stubs';
-import { addDecorators } from './decorators';
-
-// If we're running Storyshots, be sure to register the require context hook.
-// Otherwise, add the other decorators.
-if (process.env.NODE_ENV === 'test') {
- require('babel-plugin-require-context-hook/register')();
-} else {
- // Customize the info for each story.
- addDecorator(
- withInfo({
- inline: true,
- styles: {
- infoBody: {
- margin: 20,
- },
- infoStory: {
- margin: '40px 60px',
- },
- },
- })
- );
-}
-
-addDecorators();
-startServices();
-
-function loadStories() {
- require('./dll_contexts');
-
- // Only gather and require CSS files related to Canvas. The other CSS files
- // are built into the DLL.
- const css = require.context(
- '../../../../built_assets/css',
- true,
- /plugins\/(?=canvas).*light\.css/
- );
- css.keys().forEach((filename) => css(filename));
-
- // Find all files ending in *.stories.tsx
- const req = require.context('./..', true, /.(stories).tsx$/);
- req.keys().forEach((filename) => req(filename));
-
- // Import Canvas CSS
- require('../public/style/index.scss');
-}
-
-// Set up the Storybook environment with custom settings.
-addParameters({
- options: {
- theme: create({
- base: 'light',
- brandTitle: 'Canvas Storybook',
- brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas',
- }),
- showPanel: true,
- isFullscreen: false,
- panelPosition: 'bottom',
- isToolshown: true,
- },
-});
-
-configure(loadStories, module);
diff --git a/x-pack/plugins/canvas/storybook/decorators/index.ts b/x-pack/plugins/canvas/storybook/decorators/index.ts
index aa1e958a410f5..8cd716cf7e3f1 100644
--- a/x-pack/plugins/canvas/storybook/decorators/index.ts
+++ b/x-pack/plugins/canvas/storybook/decorators/index.ts
@@ -5,15 +5,43 @@
*/
import { addDecorator } from '@storybook/react';
-import { withKnobs } from '@storybook/addon-knobs';
// @ts-expect-error
import { withInfo } from '@storybook/addon-info';
+import { Provider as ReduxProvider } from 'react-redux';
+
+import { ServicesProvider } from '../../public/services';
+import { RouterContext } from '../../public/components/router';
+import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { routerContextDecorator } from './router_decorator';
import { kibanaContextDecorator } from './kibana_decorator';
+import { servicesContextDecorator } from './services_decorator';
+
+export { reduxDecorator } from './redux_decorator';
export const addDecorators = () => {
- addDecorator(withKnobs);
+ if (process.env.NODE_ENV === 'test') {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ require('babel-plugin-require-context-hook/register')();
+ } else {
+ // Customize the info for each story.
+ addDecorator(
+ withInfo({
+ inline: true,
+ styles: {
+ infoBody: {
+ margin: 20,
+ },
+ infoStory: {
+ margin: '40px 60px',
+ },
+ },
+ propTablesExclude: [ReduxProvider, ServicesProvider, RouterContext, KibanaContextProvider],
+ })
+ );
+ }
+
addDecorator(kibanaContextDecorator);
addDecorator(routerContextDecorator);
+ addDecorator(servicesContextDecorator);
};
diff --git a/x-pack/plugins/canvas/storybook/decorators/redux_decorator.tsx b/x-pack/plugins/canvas/storybook/decorators/redux_decorator.tsx
new file mode 100644
index 0000000000000..e35b065a61764
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/decorators/redux_decorator.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/* es-lint-disable import/no-extraneous-dependencies */
+
+import React from 'react';
+import { createStore } from 'redux';
+import { Provider as ReduxProvider } from 'react-redux';
+import { cloneDeep } from 'lodash';
+import { set } from '@elastic/safer-lodash-set';
+
+// @ts-expect-error Untyped local
+import { getDefaultWorkpad } from '../../public/state/defaults';
+import { CanvasWorkpad, CanvasElement, CanvasAsset } from '../../types';
+
+// @ts-expect-error untyped local
+import { elementsRegistry } from '../../public/lib/elements_registry';
+import { image } from '../../canvas_plugin_src/elements/image';
+elementsRegistry.register(image);
+
+import { getInitialState, getReducer, getMiddleware, patchDispatch } from '../addon/src/state';
+export { ADDON_ID, ACTIONS_PANEL_ID } from '../addon/src/constants';
+
+interface Params {
+ workpad?: CanvasWorkpad;
+ elements?: CanvasElement[];
+ assets?: CanvasAsset[];
+}
+
+export const reduxDecorator = (params: Params = {}) => {
+ const state = cloneDeep(getInitialState());
+ const { workpad, elements, assets } = params;
+
+ if (workpad) {
+ set(state, 'persistent.workpad', workpad);
+ }
+
+ if (elements) {
+ set(state, 'persistent.workpad.pages.0.elements', elements);
+ }
+
+ if (assets) {
+ set(
+ state,
+ 'assets',
+ assets.reduce((obj: Record, item) => {
+ obj[item.id] = item;
+ return obj;
+ }, {})
+ );
+ }
+
+ return (story: Function) => {
+ const store = createStore(getReducer(), state, getMiddleware());
+ store.dispatch = patchDispatch(store, store.dispatch);
+ return {story()};
+ };
+};
diff --git a/x-pack/plugins/canvas/storybook/decorators/router_decorator.tsx b/x-pack/plugins/canvas/storybook/decorators/router_decorator.tsx
index db775b697d248..464577b1f7c1e 100644
--- a/x-pack/plugins/canvas/storybook/decorators/router_decorator.tsx
+++ b/x-pack/plugins/canvas/storybook/decorators/router_decorator.tsx
@@ -5,32 +5,9 @@
*/
import React from 'react';
-import PropTypes from 'prop-types';
-import { RouterContext } from '../../public/components/router';
-
-const context = {
- router: {
- getFullPath: () => 'path',
- create: () => '',
- },
- navigateTo: () => {},
-};
-
-class RouterProvider extends React.Component {
- static childContextTypes = {
- router: PropTypes.object.isRequired,
- navigateTo: PropTypes.func,
- };
- getChildContext() {
- return context;
- }
-
- render() {
- return {this.props.children};
- }
-}
+import { RouterContext } from '../../public/components/router';
-export function routerContextDecorator(story: Function) {
- return {story()};
-}
+export const routerContextDecorator = (story: Function) => (
+ {} }}>{story()}
+);
diff --git a/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx b/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx
new file mode 100644
index 0000000000000..918eaffb47d77
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { ServicesProvider } from '../../public/services';
+
+export const servicesContextDecorator = (story: Function) => (
+ {story()}
+);
diff --git a/x-pack/plugins/canvas/storybook/index.ts b/x-pack/plugins/canvas/storybook/index.ts
new file mode 100644
index 0000000000000..5cad89eb614e5
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/index.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ACTIONS_PANEL_ID } from './addon/src/constants';
+
+export * from './decorators';
+export { ACTIONS_PANEL_ID } from './addon/src/constants';
+export const getAddonPanelParameters = () => ({ options: { selectedPanel: ACTIONS_PANEL_ID } });
diff --git a/x-pack/plugins/canvas/storybook/main.ts b/x-pack/plugins/canvas/storybook/main.ts
new file mode 100644
index 0000000000000..ad6d10f9bc75f
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/main.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+module.exports = {
+ stories: ['../**/*.stories.tsx'],
+ addons: [
+ '@storybook/addon-actions',
+ '@storybook/addon-knobs',
+ './storybook/addon/target/register',
+ ],
+};
diff --git a/x-pack/plugins/canvas/storybook/manager.ts b/x-pack/plugins/canvas/storybook/manager.ts
new file mode 100644
index 0000000000000..6727040c9b27f
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/manager.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { addons } from '@storybook/addons';
+import { create } from '@storybook/theming';
+import { PANEL_ID } from '@storybook/addon-actions';
+
+addons.setConfig({
+ theme: create({
+ base: 'light',
+ brandTitle: 'Canvas Storybook',
+ brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas',
+ }),
+ showPanel: true,
+ isFullscreen: false,
+ panelPosition: 'bottom',
+ isToolshown: true,
+ selectedPanel: PANEL_ID,
+});
diff --git a/x-pack/plugins/canvas/storybook/middleware.js b/x-pack/plugins/canvas/storybook/middleware.ts
similarity index 74%
rename from x-pack/plugins/canvas/storybook/middleware.js
rename to x-pack/plugins/canvas/storybook/middleware.ts
index baa524aefa709..d319a6918a02a 100644
--- a/x-pack/plugins/canvas/storybook/middleware.js
+++ b/x-pack/plugins/canvas/storybook/middleware.ts
@@ -4,11 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-const path = require('path');
-const serve = require('serve-static');
+import path from 'path';
+// @ts-expect-error
+import serve from 'serve-static';
// Extend the Storybook Middleware to include a route to access Legacy UI assets
-module.exports = function (router) {
+module.exports = function (router: { get: (...args: any[]) => void }) {
router.get(
'/ui',
serve(path.resolve(__dirname, '../../../../../src/core/server/core_app/assets'))
diff --git a/x-pack/plugins/canvas/storybook/preview.ts b/x-pack/plugins/canvas/storybook/preview.ts
new file mode 100644
index 0000000000000..fc194664c84b8
--- /dev/null
+++ b/x-pack/plugins/canvas/storybook/preview.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { action } from '@storybook/addon-actions';
+
+import { startServices } from '../public/services/stubs';
+import { addDecorators } from './decorators';
+
+// Import the modules from the DLL.
+import './dll_contexts';
+
+// Import Canvas CSS
+import '../public/style/index.scss';
+
+startServices({
+ notify: {
+ success: (message) => action(`success: ${message}`)(),
+ error: (message) => action(`error: ${message}`)(),
+ info: (message) => action(`info: ${message}`)(),
+ warning: (message) => action(`warning: ${message}`)(),
+ },
+});
+
+addDecorators();
+
+// Only gather and require CSS files related to Canvas. The other CSS files
+// are built into the DLL.
+const css = require.context(
+ '../../../../built_assets/css',
+ true,
+ /plugins\/(?=canvas).*light\.css/
+);
+css.keys().forEach((filename) => css(filename));
diff --git a/x-pack/plugins/canvas/storybook/storyshots.test.js b/x-pack/plugins/canvas/storybook/storyshots.test.tsx
similarity index 85%
rename from x-pack/plugins/canvas/storybook/storyshots.test.js
rename to x-pack/plugins/canvas/storybook/storyshots.test.tsx
index dbcbbff6398b5..c66be4a011f8c 100644
--- a/x-pack/plugins/canvas/storybook/storyshots.test.js
+++ b/x-pack/plugins/canvas/storybook/storyshots.test.tsx
@@ -4,18 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { ReactChildren } from 'react';
import path from 'path';
import moment from 'moment';
import 'moment-timezone';
import ReactDOM from 'react-dom';
import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots';
+// @ts-expect-error untyped library
import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer';
import { addSerializer } from 'jest-specific-snapshot';
// Several of the renderers, used by the runtime, use jQuery.
import jquery from 'jquery';
+// @ts-expect-error jQuery global
global.$ = jquery;
+// @ts-expect-error jQuery global
global.jQuery = jquery;
// Set our default timezone to UTC for tests so we can generate predictable snapshots
@@ -23,7 +27,7 @@ moment.tz.setDefault('UTC');
// Freeze time for the tests for predictable snapshots
const testTime = new Date(Date.UTC(2019, 5, 1)); // June 1 2019
-Date.now = jest.fn(() => testTime);
+Date.now = jest.fn(() => testTime.getTime());
// Mock telemetry service
jest.mock('../public/lib/ui_metric', () => ({ trackCanvasUiMetric: () => {} }));
@@ -53,10 +57,10 @@ jest.mock('@elastic/eui/packages/react-datepicker', () => {
});
// Mock React Portal for components that use modals, tooltips, etc
-ReactDOM.createPortal = jest.fn((element) => {
- return element;
-});
+// @ts-expect-error Portal mocks are notoriously difficult to type
+ReactDOM.createPortal = jest.fn((element) => element);
+// Mock the EUI HTML ID Generator so elements have a predictable ID in snapshots
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
return {
htmlIdGenerator: () => () => `generated-id`,
@@ -67,7 +71,7 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
// https://github.com/elastic/eui/issues/3712
jest.mock('@elastic/eui/lib/components/overlay_mask/overlay_mask', () => {
return {
- EuiOverlayMask: ({ children }) => children,
+ EuiOverlayMask: ({ children }: { children: ReactChildren }) => children,
};
});
@@ -79,6 +83,7 @@ jest.mock(
}
);
+// @ts-expect-error untyped library
import { EuiObserver } from '@elastic/eui/test-env/components/observer/observer';
jest.mock('@elastic/eui/test-env/components/observer/observer');
EuiObserver.mockImplementation(() => 'EuiObserver');
@@ -86,6 +91,7 @@ EuiObserver.mockImplementation(() => 'EuiObserver');
// This element uses a `ref` and cannot be rendered by Jest snapshots.
import { RenderedElement } from '../shareable_runtime/components/rendered_element';
jest.mock('../shareable_runtime/components/rendered_element');
+// @ts-expect-error
RenderedElement.mockImplementation(() => 'RenderedElement');
addSerializer(styleSheetSerializer);
@@ -94,5 +100,6 @@ addSerializer(styleSheetSerializer);
initStoryshots({
configPath: path.resolve(__dirname, './../storybook'),
test: multiSnapshotWithOptions({}),
+ // Don't snapshot tests that start with 'redux'
storyNameRegex: /^((?!.*?redux).)*$/,
});
diff --git a/x-pack/plugins/canvas/storybook/webpack.config.js b/x-pack/plugins/canvas/storybook/webpack.config.js
index 982185a731b14..1321ade30bbde 100644
--- a/x-pack/plugins/canvas/storybook/webpack.config.js
+++ b/x-pack/plugins/canvas/storybook/webpack.config.js
@@ -6,236 +6,198 @@
const path = require('path');
const webpack = require('webpack');
+const webpackMerge = require('webpack-merge');
const { stringifyRequest } = require('loader-utils');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { DLL_OUTPUT, KIBANA_ROOT } = require('./constants');
// Extend the Storybook Webpack config with some customizations
-module.exports = async ({ config }) => {
- // Find and alter the CSS rule to replace the Kibana public path string with a path
- // to the route we've added in middleware.js
- const cssRule = config.module.rules.find((rule) => rule.test.source.includes('.css$'));
- cssRule.use.push({
- loader: 'string-replace-loader',
- options: {
- search: '__REPLACE_WITH_PUBLIC_PATH__',
- replace: '/',
- flags: 'g',
- },
- });
-
- // Include the React preset from Kibana for Storybook JS files.
- config.module.rules.push({
- test: /\.js$/,
- exclude: /node_modules/,
- loaders: 'babel-loader',
- options: {
- presets: [require.resolve('@kbn/babel-preset/webpack_preset')],
- },
- });
-
- // Handle Typescript files
- config.module.rules.push({
- test: /\.tsx?$/,
- use: [
- {
- loader: 'babel-loader',
- options: {
- presets: [require.resolve('@kbn/babel-preset/webpack_preset')],
- },
- },
- ],
- });
-
- config.module.rules.push({
- test: /\.mjs$/,
- include: /node_modules/,
- type: 'javascript/auto',
- });
-
- // Parse props data for .tsx files
- // This is notoriously slow, and is making Storybook unusable. Disabling for now.
- // See: https://github.com/storybookjs/storybook/issues/7998
- //
- // config.module.rules.push({
- // test: /\.tsx$/,
- // // Exclude example files, as we don't display props info for them
- // exclude: /\.examples.tsx$/,
- // use: [
- // // Parse TS comments to create Props tables in the UI
- // require.resolve('react-docgen-typescript-loader'),
- // ],
- // });
-
- // Enable SASS, but exclude CSS Modules in Storybook
- config.module.rules.push({
- test: /\.scss$/,
- exclude: /\.module.(s(a|c)ss)$/,
- use: [
- { loader: 'style-loader' },
- { loader: 'css-loader', options: { importLoaders: 2 } },
- {
- loader: 'postcss-loader',
- options: {
- config: {
- path: require.resolve('@kbn/optimizer/postcss.config.js'),
- },
- },
- },
- {
- loader: 'sass-loader',
- options: {
- prependData(loaderContext) {
- return `@import ${stringifyRequest(
- loaderContext,
- path.resolve(KIBANA_ROOT, 'src/legacy/ui/public/styles/_globals_v7light.scss')
- )};\n`;
- },
- sassOptions: {
- includePaths: [path.resolve(KIBANA_ROOT, 'node_modules')],
+module.exports = async ({ config: storybookConfig }) => {
+ const config = {
+ module: {
+ rules: [
+ // Include the React preset from Kibana for JS(X) and TS(X)
+ {
+ test: /\.(j|t)sx?$/,
+ exclude: /node_modules/,
+ loaders: 'babel-loader',
+ options: {
+ presets: [require.resolve('@kbn/babel-preset/webpack_preset')],
},
},
- },
- ],
- });
-
- // Enable CSS Modules in Storybook
- config.module.rules.push({
- test: /\.module\.s(a|c)ss$/,
- loader: [
- 'style-loader',
- {
- loader: 'css-loader',
- options: {
- importLoaders: 2,
- modules: {
- localIdentName: '[name]__[local]___[hash:base64:5]',
- },
+ // Parse props data for .tsx files
+ // This is notoriously slow, and is making Storybook unusable. Disabling for now.
+ // See: https://github.com/storybookjs/storybook/issues/7998
+ //
+ // {
+ // test: /\.tsx$/,
+ // // Exclude example files, as we don't display props info for them
+ // exclude: /\.examples.tsx$/,
+ // use: [
+ // // Parse TS comments to create Props tables in the UI
+ // require.resolve('react-docgen-typescript-loader'),
+ // ],
+ // },
+ // Enable SASS, but exclude CSS Modules in Storybook
+ {
+ test: /\.scss$/,
+ exclude: /\.module.(s(a|c)ss)$/,
+ use: [
+ { loader: 'style-loader' },
+ { loader: 'css-loader', options: { importLoaders: 2 } },
+ {
+ loader: 'postcss-loader',
+ options: {
+ path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'),
+ },
+ },
+ {
+ loader: 'sass-loader',
+ options: {
+ prependData(loaderContext) {
+ return `@import ${stringifyRequest(
+ loaderContext,
+ path.resolve(KIBANA_ROOT, 'src/legacy/ui/public/styles/_globals_v7light.scss')
+ )};\n`;
+ },
+ sassOptions: {
+ includePaths: [path.resolve(KIBANA_ROOT, 'node_modules')],
+ },
+ },
+ },
+ ],
},
- },
- {
- loader: 'postcss-loader',
- options: {
- config: {
- path: require.resolve('@kbn/optimizer/postcss.config.js'),
- },
+ // Enable CSS Modules in Storybook (Shareable Runtime)
+ {
+ test: /\.module\.s(a|c)ss$/,
+ loader: [
+ 'style-loader',
+ {
+ loader: 'css-loader',
+ options: {
+ importLoaders: 2,
+ modules: {
+ localIdentName: '[name]__[local]___[hash:base64:5]',
+ },
+ },
+ },
+ {
+ loader: 'postcss-loader',
+ options: {
+ path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'),
+ },
+ },
+ {
+ loader: 'sass-loader',
+ },
+ ],
},
- },
- {
- loader: 'sass-loader',
- },
- ],
- });
-
- // Exclude large-dependency modules that need not be included in Storybook.
- config.module.rules.push({
- test: [
- path.resolve(__dirname, '../public/components/embeddable_flyout'),
- path.resolve(__dirname, '../../reporting/public'),
- ],
- use: 'null-loader',
- });
-
- // Ensure jQuery is global for Storybook, specifically for the runtime.
- config.plugins.push(
- new webpack.ProvidePlugin({
- $: 'jquery',
- jQuery: 'jquery',
- })
- );
-
- // Reference the built DLL file of static(ish) dependencies, which are removed
- // during kbn:bootstrap and rebuilt if missing.
- config.plugins.push(
- new webpack.DllReferencePlugin({
- manifest: path.resolve(DLL_OUTPUT, 'manifest.json'),
- context: KIBANA_ROOT,
- })
- );
-
- // Copy the DLL files to the Webpack build for use in the Storybook UI
- config.plugins.push(
- new CopyWebpackPlugin({
- patterns: [
{
- from: path.resolve(DLL_OUTPUT, 'dll.js'),
- to: 'dll.js',
+ test: /\.mjs$/,
+ include: /node_modules/,
+ type: 'javascript/auto',
},
+ // Exclude large-dependency, troublesome or irrelevant modules.
{
- from: path.resolve(DLL_OUTPUT, 'dll.css'),
- to: 'dll.css',
+ test: [
+ path.resolve(__dirname, '../public/components/embeddable_flyout'),
+ path.resolve(__dirname, '../../reporting/public'),
+ path.resolve(__dirname, '../../../../src/plugins/kibana_legacy/public/angular'),
+ path.resolve(__dirname, '../../../../src/plugins/kibana_legacy/public/paginate'),
+ ],
+ use: 'null-loader',
},
],
- })
- );
-
- config.plugins.push(
- // replace imports for `uiExports/*` modules with a synthetic module
- // created by create_ui_exports_module.js
- new webpack.NormalModuleReplacementPlugin(/^uiExports\//, (resource) => {
- // uiExports used by Canvas
- const extensions = {
- hacks: [],
- chromeNavControls: [],
- };
-
- // everything following the first / in the request is
- // treated as a type of appExtension
- const type = resource.request.slice(resource.request.indexOf('/') + 1);
-
- resource.request = [
- // the "val-loader" is used to execute create_ui_exports_module
- // and use its return value as the source for the module in the
- // bundle. This allows us to bypass writing to the file system
- require.resolve('val-loader'),
- '!',
- require.resolve(KIBANA_ROOT + '/src/optimize/create_ui_exports_module'),
- '?',
- // this JSON is parsed by create_ui_exports_module and determines
- // what require() calls it will execute within the bundle
- JSON.stringify({ type, modules: extensions[type] || [] }),
- ].join('');
- }),
-
- // Mock out libs used by a few componets to avoid loading in kibana_legacy and platform
- new webpack.NormalModuleReplacementPlugin(
- /(lib)?\/notify/,
- path.resolve(__dirname, '../tasks/mocks/uiNotify')
- ),
- new webpack.NormalModuleReplacementPlugin(
- /lib\/download_workpad/,
- path.resolve(__dirname, '../tasks/mocks/downloadWorkpad')
- ),
- new webpack.NormalModuleReplacementPlugin(
- /(lib)?\/custom_element_service/,
- path.resolve(__dirname, '../tasks/mocks/customElementService')
- ),
- new webpack.NormalModuleReplacementPlugin(
- /(lib)?\/ui_metric/,
- path.resolve(__dirname, '../tasks/mocks/uiMetric')
- )
- );
-
- // Tell Webpack about relevant extensions
- config.resolve.extensions.push('.ts', '.tsx', '.scss');
-
- // Alias imports to either a mock or the proper module or directory.
- // NOTE: order is important here - `ui/notify` will override `ui/notify/foo` if it
- // is added first.
- config.resolve.alias['ui/notify/lib/format_msg'] = path.resolve(
- __dirname,
- '../tasks/mocks/uiNotifyFormatMsg'
- );
- config.resolve.alias['ui/notify'] = path.resolve(__dirname, '../tasks/mocks/uiNotify');
- config.resolve.alias['ui/url/absolute_to_parsed_url'] = path.resolve(
- __dirname,
- '../tasks/mocks/uiAbsoluteToParsedUrl'
- );
- config.resolve.alias['ui/chrome'] = path.resolve(__dirname, '../tasks/mocks/uiChrome');
- config.resolve.alias.ui = path.resolve(KIBANA_ROOT, 'src/legacy/ui/public');
- config.resolve.alias.ng_mock$ = path.resolve(KIBANA_ROOT, 'src/test_utils/public/ng_mock');
+ },
+ plugins: [
+ // Reference the built DLL file of static(ish) dependencies, which are removed
+ // during kbn:bootstrap and rebuilt if missing.
+ new webpack.DllReferencePlugin({
+ manifest: path.resolve(DLL_OUTPUT, 'manifest.json'),
+ context: KIBANA_ROOT,
+ }),
+ // Ensure jQuery is global for Storybook, specifically for the runtime.
+ new webpack.ProvidePlugin({
+ $: 'jquery',
+ jQuery: 'jquery',
+ }),
+ // Copy the DLL files to the Webpack build for use in the Storybook UI
+ new CopyWebpackPlugin({
+ patterns: [
+ {
+ from: path.resolve(DLL_OUTPUT, 'dll.js'),
+ to: 'dll.js',
+ },
+ {
+ from: path.resolve(DLL_OUTPUT, 'dll.css'),
+ to: 'dll.css',
+ },
+ ],
+ }),
+ // replace imports for `uiExports/*` modules with a synthetic module
+ // created by create_ui_exports_module.js
+ new webpack.NormalModuleReplacementPlugin(/^uiExports\//, (resource) => {
+ // uiExports used by Canvas
+ const extensions = {
+ hacks: [],
+ chromeNavControls: [],
+ };
+
+ // everything following the first / in the request is
+ // treated as a type of appExtension
+ const type = resource.request.slice(resource.request.indexOf('/') + 1);
+
+ resource.request = [
+ // the "val-loader" is used to execute create_ui_exports_module
+ // and use its return value as the source for the module in the
+ // bundle. This allows us to bypass writing to the file system
+ require.resolve('val-loader'),
+ '!',
+ require.resolve(KIBANA_ROOT + '/src/optimize/create_ui_exports_module'),
+ '?',
+ // this JSON is parsed by create_ui_exports_module and determines
+ // what require() calls it will execute within the bundle
+ JSON.stringify({ type, modules: extensions[type] || [] }),
+ ].join('');
+ }),
+
+ new webpack.NormalModuleReplacementPlugin(
+ /lib\/download_workpad/,
+ path.resolve(__dirname, '../tasks/mocks/downloadWorkpad')
+ ),
+ new webpack.NormalModuleReplacementPlugin(
+ /(lib)?\/custom_element_service/,
+ path.resolve(__dirname, '../tasks/mocks/customElementService')
+ ),
+ new webpack.NormalModuleReplacementPlugin(
+ /(lib)?\/ui_metric/,
+ path.resolve(__dirname, '../tasks/mocks/uiMetric')
+ ),
+ ],
+ resolve: {
+ extensions: ['.ts', '.tsx', '.scss', '.mjs', '.html'],
+ alias: {
+ 'ui/url/absolute_to_parsed_url': path.resolve(
+ __dirname,
+ '../tasks/mocks/uiAbsoluteToParsedUrl'
+ ),
+ ui: path.resolve(KIBANA_ROOT, 'src/legacy/ui/public'),
+ ng_mock$: path.resolve(KIBANA_ROOT, 'src/test_utils/public/ng_mock'),
+ },
+ },
+ };
- config.resolve.extensions.push('.mjs');
+ // Find and alter the CSS rule to replace the Kibana public path string with a path
+ // to the route we've added in middleware.js
+ const cssRule = storybookConfig.module.rules.find((rule) => rule.test.source.includes('.css$'));
+ cssRule.use.push({
+ loader: 'string-replace-loader',
+ options: {
+ search: '__REPLACE_WITH_PUBLIC_PATH__',
+ replace: '/',
+ flags: 'g',
+ },
+ });
- return config;
+ return webpackMerge(storybookConfig, config);
};
diff --git a/x-pack/plugins/canvas/storybook/webpack.dll.config.js b/x-pack/plugins/canvas/storybook/webpack.dll.config.js
index 81d19c035075f..4e54750f08eea 100644
--- a/x-pack/plugins/canvas/storybook/webpack.dll.config.js
+++ b/x-pack/plugins/canvas/storybook/webpack.dll.config.js
@@ -25,9 +25,6 @@ module.exports = {
'@elastic/eui/dist/eui_theme_light.css',
'@kbn/ui-framework/dist/kui_light.css',
'@storybook/addon-actions/register',
- '@storybook/addon-knobs',
- '@storybook/addon-knobs/react',
- '@storybook/addon-knobs/register',
'@storybook/core',
'@storybook/core/dist/server/common/polyfills.js',
'@storybook/react',
@@ -38,6 +35,7 @@ module.exports = {
'chroma-js',
'highlight.js',
'html-entities',
+ 'jsondiffpatch',
'jquery',
'lodash',
'markdown-it',
diff --git a/yarn.lock b/yarn.lock
index 83091a5e70467..277c23b3a0838 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4890,7 +4890,7 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
-"@types/jest-specific-snapshot@^0.5.3":
+"@types/jest-specific-snapshot@^0.5.3", "@types/jest-specific-snapshot@^0.5.4":
version "0.5.4"
resolved "https://registry.yarnpkg.com/@types/jest-specific-snapshot/-/jest-specific-snapshot-0.5.4.tgz#997364c39a59ddeff0ee790a19415e79dd061d1e"
integrity sha512-1qISn4fH8wkOOPFEx+uWRRjw6m/pP/It3OHLm8Ee1KQpO7Z9ZGYDtWPU5AgK05UXsNTAgOK+dPQvJKGdy9E/1g==
@@ -5808,7 +5808,7 @@
"@types/node" "*"
chokidar "^2.1.2"
-"@types/webpack-env@^1.15.0":
+"@types/webpack-env@^1.15.0", "@types/webpack-env@^1.15.2":
version "1.15.2"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a"
integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ==
@@ -12026,6 +12026,11 @@ diagnostics@^1.1.1:
enabled "1.0.x"
kuler "1.0.x"
+diff-match-patch@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
+ integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
+
diff-match-patch@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
@@ -19376,6 +19381,14 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
+jsondiffpatch@0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz#9fb085036767f03534ebd46dcd841df6070c5773"
+ integrity sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==
+ dependencies:
+ chalk "^2.3.0"
+ diff-match-patch "^1.0.0"
+
jsonfile@^2.1.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"