From a2cc73466c862c6a31ac1e1673c5c313c82734dc Mon Sep 17 00:00:00 2001 From: Harsh Patel Date: Mon, 24 May 2021 14:32:37 +0000 Subject: [PATCH] feat: allow esm JS modules as custom ui extensions to support external deps --- ui/package.json | 14 ++-- .../main/webapp/components/BaseFormView.jsx | 70 ++++++++++++++----- .../main/webapp/components/CustomControl.jsx | 28 +++++--- ui/src/main/webapp/components/CustomMenu.jsx | 26 ++++--- .../components/table/CustomTableControl.jsx | 30 ++++++-- .../components/table/CustomTableRow.jsx | 1 + ui/src/main/webapp/pages/Input/InputPage.jsx | 1 + ui/webpack.config.js | 2 +- 8 files changed, 122 insertions(+), 50 deletions(-) diff --git a/ui/package.json b/ui/package.json index 82114b6d4..1433299a8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -24,12 +24,12 @@ "@splunk/splunk-utils": "^2.0.0", "@splunk/themes": "^0.7.0", "axios": "^0.21.1", - "jsonschema": "^1.4.0", "immutability-helper": "^3.1.1", + "jsonschema": "^1.4.0", "react": "^16.12.0", - "styled-components": "^5.1.1", - "react-router-dom": "^5.2.0", "react-dom": "^16.12.0", + "react-router-dom": "^5.2.0", + "styled-components": "^5.1.1", "uuid": "^8.3.2" }, "devDependencies": { @@ -41,13 +41,13 @@ "@splunk/babel-preset": "^3.0.0", "@splunk/eslint-config": "^4.0.0", "@splunk/stylelint-config": "^4.0.0", - "postcss": "^8.2.10", "@splunk/webpack-configs": "^5.0.0", "babel-eslint": "^10.1.0", "babel-loader": "^8.0.4", "chai": "^3.5.0", "copy-webpack-plugin": "^4.5.2", "cross-env": "^7.0.3", + "css-loader": "^5.2.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", "eslint": "^7.14.0", @@ -59,14 +59,14 @@ "eslint-plugin-react": "^7.21.5", "eslint-plugin-react-hooks": "^4.2.0", "html-webpack-plugin": "^3.2.0", + "postcss": "^8.2.10", "prettier": "^2.0.5", "semantic-release": "^17.4.2", + "style-loader": "^2.0.0", "stylelint": "^13.0.0", "webpack": "^4.16.2", "webpack-cli": "^3.1.0", - "webpack-merge": "^4.1.3", - "css-loader": "^5.2.0", - "style-loader": "^2.0.0" + "webpack-merge": "^4.1.3" }, "engines": { "node": ">=6" diff --git a/ui/src/main/webapp/components/BaseFormView.jsx b/ui/src/main/webapp/components/BaseFormView.jsx index 0b3379bf5..346089af8 100644 --- a/ui/src/main/webapp/components/BaseFormView.jsx +++ b/ui/src/main/webapp/components/BaseFormView.jsx @@ -94,7 +94,11 @@ class BaseFormView extends PureComponent { this.updateEntitiesForGroup(service); this.options = service.options; if (service.hook) { - this.hookDeferred = this.loadHook(service.hook.src, globalConfig); + this.hookDeferred = this.loadHook( + service.hook.src, + service.hook.type, + globalConfig + ); } if (props.mode === MODE_EDIT || props.mode === MODE_CLONE) { this.currentInput = context.rowData[props.serviceName][props.stanzaName]; @@ -110,7 +114,11 @@ class BaseFormView extends PureComponent { this.entities = tab.entity; this.options = tab.options; if (tab.hook) { - this.hookDeferred = this.loadHook(tab.hook.src, globalConfig); + this.hookDeferred = this.loadHook( + tab.hook.src, + tab.hook.type, + globalConfig + ); } if (tab.table && (props.mode === MODE_EDIT || props.mode === MODE_CLONE)) { this.currentInput = context.rowData[props.serviceName][props.stanzaName]; @@ -333,6 +341,7 @@ class BaseFormView extends PureComponent { if (this.hookDeferred) { this.hookDeferred.then(() => { if (typeof this.hook.onCreate === 'function') { + // TODO: try catch to stop UI break this.hook.onCreate(); } }); @@ -734,21 +743,37 @@ class BaseFormView extends PureComponent { }; // generatesubmitMessage - loadHook = (module, globalConfig) => { + loadHook = (module, type, globalConfig) => { const myPromise = new Promise((resolve) => { - import(/* webpackIgnore: true */ `${getBuildDirPath()}/custom/${module}.js`).then( - (external) => { - const Hook = external.default; - this.hook = new Hook( - globalConfig, - this.props.serviceName, - this.state, - this.props.mode, - this.util - ); - resolve(Hook); - } - ); + if (type === 'external') { + import(/* webpackIgnore: true */ `${getBuildDirPath()}/custom/${module}.js`).then( + (external) => { + const Hook = external.default; + this.hook = new Hook( + globalConfig, + this.props.serviceName, + this.state, + this.props.mode, + this.util + ); + resolve(Hook); + } + ); + } else { + __non_webpack_require__( + [`app/${this.appName}/js/build/custom/${module}`], + (Hook) => { + this.hook = new Hook( + globalConfig, + this.props.serviceName, + this.state, + this.props.mode, + this.util + ); + resolve(Hook); + } + ); + } }); return myPromise; }; @@ -927,7 +952,11 @@ class BaseFormView extends PureComponent { if (this.hookDeferred) { this.hookDeferred.then(() => { if (typeof this.hook.onRender === 'function') { - this.hook.onRender(); + try { + this.hook.onRender(); + } catch (err) { + console.error(err); + } } }); } @@ -936,7 +965,11 @@ class BaseFormView extends PureComponent { if (this.hookDeferred) { this.hookDeferred.then(() => { if (typeof this.hook.onEditLoad === 'function') { - this.hook.onEditLoad(); + try { + this.hook.onEditLoad(); + } catch (err) { + console.error(err); + } } }); } @@ -957,6 +990,7 @@ class BaseFormView extends PureComponent { const temState = this.state.data[e.field]; if (temState.placeholder) { + // eslint-disable-next-line no-param-reassign e = { ...e, options: { ...e.options, placeholder: temState.placeholder }, diff --git a/ui/src/main/webapp/components/CustomControl.jsx b/ui/src/main/webapp/components/CustomControl.jsx index 0e571df37..a04ed1f4e 100644 --- a/ui/src/main/webapp/components/CustomControl.jsx +++ b/ui/src/main/webapp/components/CustomControl.jsx @@ -16,8 +16,13 @@ class CustomControl extends Component { componentDidMount() { const globalConfig = getUnifiedConfigs(); + const appName = globalConfig.meta.name; - this.loadCustomControl(this.props.controlOptions.src).then((Control) => { + this.loadCustomControl( + this.props.controlOptions.src, + this.props.controlOptions.type, + appName + ).then((Control) => { const customControl = new Control( globalConfig, this.el, @@ -42,16 +47,21 @@ class CustomControl extends Component { return false; } - loadCustomControl = (module) => { - const myPromise = new Promise((resolve) => { - import(/* webpackIgnore: true */ `${getBuildDirPath()}/custom/${module}.js`).then( - (external) => { - const Control = external.default; + loadCustomControl = (module, type, appName) => { + return new Promise((resolve) => { + if (type === 'external') { + import(/* webpackIgnore: true */ `${getBuildDirPath()}/custom/${module}.js`).then( + (external) => { + const Control = external.default; + resolve(Control); + } + ); + } else { + __non_webpack_require__([`app/${appName}/js/build/custom/${module}`], (Control) => { resolve(Control); - } - ); + }); + } }); - return myPromise; }; setValue = (newValue) => { diff --git a/ui/src/main/webapp/components/CustomMenu.jsx b/ui/src/main/webapp/components/CustomMenu.jsx index b5af8b7d1..79d17b663 100644 --- a/ui/src/main/webapp/components/CustomMenu.jsx +++ b/ui/src/main/webapp/components/CustomMenu.jsx @@ -36,15 +36,24 @@ class CustomMenu extends Component { }; loadCustomMenu = () => { - const globalConfig = getUnifiedConfigs(); - const appName = globalConfig.meta.name; return new Promise((resolve) => { - import( - /* webpackIgnore: true */ `${getBuildDirPath()}/custom/${this.props.fileName}.js` - ).then((external) => { - const Control = external.default; - resolve(Control); - }); + if (this.props.type === 'external') { + import( + /* webpackIgnore: true */ `${getBuildDirPath()}/custom/${ + this.props.fileName + }.js` + ).then((external) => { + const Control = external.default; + resolve(Control); + }); + } else { + const globalConfig = getUnifiedConfigs(); + const appName = globalConfig.meta.name; + __non_webpack_require__( + [`app/${appName}/js/build/custom/${this.props.fileName}`], + (Control) => resolve(Control) + ); + } }); }; @@ -67,6 +76,7 @@ class CustomMenu extends Component { CustomMenu.propTypes = { fileName: PropTypes.string.isRequired, + type: PropTypes.string, handleChange: PropTypes.func, }; diff --git a/ui/src/main/webapp/components/table/CustomTableControl.jsx b/ui/src/main/webapp/components/table/CustomTableControl.jsx index dd9e65734..b327cff5d 100644 --- a/ui/src/main/webapp/components/table/CustomTableControl.jsx +++ b/ui/src/main/webapp/components/table/CustomTableControl.jsx @@ -42,18 +42,33 @@ class CustomTableControl extends Component { loadCustomControl = () => { return new Promise((resolve) => { - import( - /* webpackIgnore: true */ `${getBuildDirPath()}/custom/${this.props.fileName}.js` - ).then((external) => { - const Control = external.default; - resolve(Control); - }); + if (this.props.type === 'external') { + import( + /* webpackIgnore: true */ `${getBuildDirPath()}/custom/${ + this.props.fileName + }.js` + ).then((external) => { + const Control = external.default; + resolve(Control); + }); + } else { + const globalConfig = getUnifiedConfigs(); + const appName = globalConfig.meta.name; + __non_webpack_require__( + [`app/${appName}/js/build/custom/${this.props.fileName}`], + (Control) => resolve(Control) + ); + } }); }; render() { if (!this.state.loading) { - this.customControl.render(this.props.row, this.props.field); + try { + this.customControl.render(this.props.row, this.props.field); + } catch (err) { + console.error(err); + } } return ( <> @@ -76,6 +91,7 @@ CustomTableControl.propTypes = { row: PropTypes.object.isRequired, field: PropTypes.string, fileName: PropTypes.string.isRequired, + type: PropTypes.string, }; export default CustomTableControl; diff --git a/ui/src/main/webapp/components/table/CustomTableRow.jsx b/ui/src/main/webapp/components/table/CustomTableRow.jsx index 851a993ab..6531c3c90 100644 --- a/ui/src/main/webapp/components/table/CustomTableRow.jsx +++ b/ui/src/main/webapp/components/table/CustomTableRow.jsx @@ -45,6 +45,7 @@ function CustomTableRow(props) { field: header.field, row: customRow, fileName: header.customCell.src, + type: header.customCell.type, }); }; diff --git a/ui/src/main/webapp/pages/Input/InputPage.jsx b/ui/src/main/webapp/pages/Input/InputPage.jsx index d7aa082c3..68d8d9c4f 100755 --- a/ui/src/main/webapp/pages/Input/InputPage.jsx +++ b/ui/src/main/webapp/pages/Input/InputPage.jsx @@ -230,6 +230,7 @@ function InputPage() { {React.createElement(CustomMenu, { fileName: customMenuField.src, + type: customMenuField.type, handleChange: changeRoute, })} diff --git a/ui/webpack.config.js b/ui/webpack.config.js index e20c0fdf8..3d0cf2f4f 100644 --- a/ui/webpack.config.js +++ b/ui/webpack.config.js @@ -28,5 +28,5 @@ module.exports = webpackMerge(baseConfig, { }, ]), ], - devtool: 'eval-source-map', + devtool: 'inline-source-map', });