From 7c6da2262a13a9e3ff08dc3b33aa49f44a4b16bb Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 15 Mar 2019 12:11:40 -0700 Subject: [PATCH] docs(demo): Add TS support + CORS demo (#118) * docs: [demo] add custom webpack config to resolve TS files * docs: [demo] add @babel/polyfill * docs: [demo][connection] add ConfigureCORS story for testing CORS * docs: [demo][ConfigureCORS] better instructions * fix: [demo] install an existing version of @superset-ui/connection * docs: better CORS story, update webpack for @babel/polyfill --- .../packages/superset-ui-demo/package.json | 2 + .../storybook-config/webpack.config.js | 19 +++ .../shared/components/ErrorMessage.tsx | 16 +++ .../shared/components/Expandable.tsx | 42 +++++++ .../shared/components/VerifyCORS.tsx | 116 ++++++++++++++++++ .../storybook/stories/index.js | 3 +- .../storybook/stories/mocks/formData.js | 35 ++++++ .../ConnectionStories.jsx | 46 +++++++ .../stories/superset-ui-connection/index.js | 5 + 9 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook-config/webpack.config.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/ErrorMessage.tsx create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/Expandable.tsx create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/VerifyCORS.tsx create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/mocks/formData.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/superset-ui-connection/ConnectionStories.jsx create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/superset-ui-connection/index.js diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/package.json b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/package.json index 9a064bb8fa39..72eb975c845e 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/package.json +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/package.json @@ -30,10 +30,12 @@ "homepage": "https://github.com/apache-superset/superset-ui#readme", "dependencies": { "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.2.5", "@storybook/addon-knobs": "^4.0.2", "@storybook/addon-options": "^4.0.3", "@storybook/react": "^4.0.2", "@superset-ui/color": "^0.10.1", + "@superset-ui/connection": "^0.10.2", "@superset-ui/number-format": "^0.10.1", "@superset-ui/time-format": "^0.10.1", "babel-loader": "^8.0.4", diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook-config/webpack.config.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook-config/webpack.config.js new file mode 100644 index 000000000000..187361534ae9 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook-config/webpack.config.js @@ -0,0 +1,19 @@ +module.exports = (baseConfig, env, config) => { + const customConfig = config; + + customConfig.module.rules.push({ + loader: require.resolve('babel-loader'), + options: { + presets: [ + ['@babel/preset-env', { useBuiltIns: 'entry' }], + '@babel/preset-react', + '@babel/preset-typescript', + ], + }, + test: /\.tsx?$/, + }); + + customConfig.resolve.extensions.push('.ts', '.tsx'); + + return customConfig; +}; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/ErrorMessage.tsx b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/ErrorMessage.tsx new file mode 100644 index 000000000000..78b38d7fa6ab --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/ErrorMessage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +export type Props = { + error: Error; +}; + +export default function ErrorMessage({ error }: Props) { + return ( +
+ {error.stack || error.message} + {!error.message && + !error.stack && + (typeof error === 'object' ? JSON.stringify(error) : String(error))} +
+ ); +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/Expandable.tsx b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/Expandable.tsx new file mode 100644 index 000000000000..dde54dc6c636 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/Expandable.tsx @@ -0,0 +1,42 @@ +import React, { ReactNode } from 'react'; + +export type Props = { + children: ReactNode; + expandableWhat?: string; +}; + +type State = { + open: boolean; +}; + +export default class Expandable extends React.Component { + constructor(props: Props) { + super(props); + this.state = { open: false }; + this.handleToggle = this.handleToggle.bind(this); + } + + handleToggle() { + this.setState(({ open }) => ({ open: !open })); + } + + render() { + const { open } = this.state; + const { children, expandableWhat } = this.props; + + return ( +
+ +
+
+ {open ? children : null} +
+ ); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/VerifyCORS.tsx b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/VerifyCORS.tsx new file mode 100644 index 000000000000..f4cbf2cb8564 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/shared/components/VerifyCORS.tsx @@ -0,0 +1,116 @@ +import React, { ReactNode } from 'react'; +import { SupersetClient } from '@superset-ui/connection'; +import ErrorMessage from './ErrorMessage'; + +export type Props = { + children: ({ payload }: { payload: object }) => ReactNode; + endpoint?: string; + host: string; + method?: 'POST' | 'GET'; + postPayload?: string; +}; + +type State = { + didVerify: boolean; + error?: Error; + payload?: object; +}; + +export const renderError = error => ( +
+ The following error occurred, make sure you have
+ 1) configured CORS in Superset to receive requests from this domain.
+ 2) set the Superset host correctly below.
+ 3) debug the CORS configuration under the `@superset-ui/connection` stories. +
+
+ +
+); + +export default class VerifyCORS extends React.Component { + constructor(props: Props) { + super(props); + this.state = { didVerify: false }; + this.handleVerify = this.handleVerify.bind(this); + } + + componentDidUpdate(prevProps) { + const { endpoint, host, postPayload, method } = this.props; + if ( + (this.state.didVerify || this.state.error) && + (prevProps.endpoint !== endpoint || + prevProps.host !== host || + prevProps.postPayload !== postPayload || + prevProps.method !== method) + ) { + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ didVerify: false, error: undefined }); + } + } + + handleVerify() { + const { endpoint, host, postPayload, method } = this.props; + + SupersetClient.reset(); + + SupersetClient.configure({ + credentials: 'include', + host, + mode: 'cors', + }) + .init() + .then(() => + // Test an endpoint if specified + endpoint + ? SupersetClient.request({ + endpoint, + method, + postPayload: postPayload ? JSON.parse(postPayload) : '', + }) + : Promise.resolve({}), + ) + .then(response => this.setState({ didVerify: true, error: undefined, payload: response })) + .catch((error: Response) => { + const { status, statusText = error } = error; + this.setState({ error: Error(`${status || ''}${status ? ':' : ''} ${statusText}`) }); + }); + } + + render() { + const { didVerify, error, payload } = this.state; + const { children } = this.props; + + return didVerify ? ( + children({ payload }) + ) : ( +
+
+ This example requires CORS requests from this domain.
+
+ 1) enable CORS requests in your Superset App from {`${window.location.origin}`} +
+ 2) configure your Superset App host name below
+ 3) click below to verify authentication. You may debug CORS further using the + `@superset-ui/connection` story.
+
+ +
+
+
+ + {error && ( +
+ +
+ )} +
+ ); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/index.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/index.js index 37ce71217703..25480a59dad1 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/index.js +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/index.js @@ -1,3 +1,4 @@ +import '@babel/polyfill'; import { setAddon, storiesOf } from '@storybook/react'; import { withKnobs } from '@storybook/addon-knobs'; import JSXAddon from 'storybook-addon-jsx'; @@ -36,7 +37,7 @@ requireContext.keys().forEach(packageName => { storiesOf(storyPath, module) .addParameters({ options }) - .addDecorator(withKnobs) + .addDecorator(withKnobs({ escapeHTML: false })) .addWithJSX(storyName, renderStory); }); } diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/mocks/formData.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/mocks/formData.js new file mode 100644 index 000000000000..59070cdf7886 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/mocks/formData.js @@ -0,0 +1,35 @@ +/* eslint sort-keys: 'off' */ +/** The form data defined here is based on default visualizations packaged with Apache Superset */ + +export const bigNumberFormData = { + datasource: '3__table', + viz_type: 'big_number', + slice_id: 54, + granularity_sqla: 'ds', + time_grain_sqla: 'P1D', + time_range: '100 years ago : now', + metric: 'sum__num', + adhoc_filters: [], + compare_lag: '5', + compare_suffix: 'over 5Y', + y_axis_format: '.3s', + show_trend_line: true, + start_y_axis_at_zero: true, +}; + +export const wordCloudFormData = { + datasource: '3__table', + viz_type: 'word_cloud', + slice_id: 60, + url_params: {}, + granularity_sqla: 'ds', + time_grain_sqla: 'P1D', + time_range: '100 years ago : now', + series: 'name', + metric: 'sum__num', + adhoc_filters: [], + row_limit: 50, + size_from: 10, + size_to: 70, + rotation: 'square', +}; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/superset-ui-connection/ConnectionStories.jsx b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/superset-ui-connection/ConnectionStories.jsx new file mode 100644 index 000000000000..cb3e9eea6212 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/superset-ui-connection/ConnectionStories.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { select, text } from '@storybook/addon-knobs'; + +import VerifyCORS from '../../shared/components/VerifyCORS'; +import Expandable from '../../shared/components/Expandable'; +import { bigNumberFormData } from '../mocks/formData'; + +const REQUEST_METHODS = ['GET', 'POST']; + +export default [ + { + renderStory: () => { + const host = text('Superset App host for CORS request', 'localhost:9000'); + const endpoint = text('Endpoint to test (blank to test auth only)', undefined); + const method = endpoint ? select('Request method', REQUEST_METHODS, 'POST') : undefined; + const postPayload = + endpoint && method === 'POST' + ? text('Optional POST payload', JSON.stringify({ form_data: bigNumberFormData })) + : undefined; + + return ( +
+ + {({ payload }) => ( + <> +
Success! Update knobs below to try again
+
+ +
+
{JSON.stringify(payload, null, 2)}
+
+ + )} +
+
+ ); + }, + storyName: 'Configure CORS', + storyPath: '@superset-ui/connection', + }, +]; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/superset-ui-connection/index.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/superset-ui-connection/index.js new file mode 100644 index 000000000000..ad530c0e3709 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/superset-ui-connection/index.js @@ -0,0 +1,5 @@ +import ConnectionStories from './ConnectionStories'; + +export default { + examples: [...ConnectionStories], +};