From 53dea4abdcf00f13f6f4516a315c578d98beee59 Mon Sep 17 00:00:00 2001 From: Ilya Hancharyk Date: Tue, 7 May 2024 14:31:52 +0200 Subject: [PATCH] EPMRPP-90318 || Remote plugins support --- .../extensionLoader/extensionLoader.jsx | 47 ++++++----- .../extensionLoader/extensionTypes.js | 39 +++++++++- .../federatedExtensionLoader.jsx | 52 +++++++++++++ .../federatedExtensionLoader/index.js | 17 ++++ .../standaloneExtensionLoader/index.js | 17 ++++ .../standaloneExtensionLoader.jsx | 78 +++++++++++++++++++ app/src/controllers/pages/constants.js | 6 +- app/src/controllers/pages/index.js | 3 +- app/src/controllers/plugins/index.js | 3 +- .../plugins/uiExtensions/constants.js | 4 +- .../controllers/plugins/uiExtensions/hooks.js | 15 ++++ .../controllers/plugins/uiExtensions/index.js | 2 +- .../plugins/uiExtensions/selectors.js | 29 ++++++- .../adminUiExtensionPageLayout.jsx | 10 +-- .../index.js | 4 +- .../appLayout/appSidebar/appSidebar.jsx | 41 ++++++++-- app/src/layouts/common/layout/layout.jsx | 6 +- .../adminUiExtensionPage.jsx | 74 +++++++++++------- .../adminUiExtensionPage.scss} | 2 +- .../uiExtensionPage/uiExtensionPage.jsx | 77 ------------------ .../projectUiExtensionPage}/index.js | 4 +- .../projectUiExtensionPage.jsx | 31 ++++++++ app/src/routes/constants.js | 13 +++- app/src/routes/routesMap.js | 2 + 24 files changed, 419 insertions(+), 157 deletions(-) create mode 100644 app/src/components/extensionLoader/federatedExtensionLoader/federatedExtensionLoader.jsx create mode 100644 app/src/components/extensionLoader/federatedExtensionLoader/index.js create mode 100644 app/src/components/extensionLoader/standaloneExtensionLoader/index.js create mode 100644 app/src/components/extensionLoader/standaloneExtensionLoader/standaloneExtensionLoader.jsx create mode 100644 app/src/controllers/plugins/uiExtensions/hooks.js rename app/src/layouts/adminLayout/{adminUIExtensionLayout => adminUiExtensionPageLayout}/adminUiExtensionPageLayout.jsx (77%) rename app/src/layouts/adminLayout/{adminUIExtensionLayout => adminUiExtensionPageLayout}/index.js (84%) rename app/src/pages/{common/uiExtensionPage/uiExtensionPage.scss => admin/adminUiExtensionPage/adminUiExtensionPage.scss} (95%) delete mode 100644 app/src/pages/common/uiExtensionPage/uiExtensionPage.jsx rename app/src/pages/{common/uiExtensionPage => inside/projectUiExtensionPage}/index.js (85%) create mode 100644 app/src/pages/inside/projectUiExtensionPage/projectUiExtensionPage.jsx diff --git a/app/src/components/extensionLoader/extensionLoader.jsx b/app/src/components/extensionLoader/extensionLoader.jsx index 46dcc9f0cc..099c2db23d 100644 --- a/app/src/components/extensionLoader/extensionLoader.jsx +++ b/app/src/components/extensionLoader/extensionLoader.jsx @@ -1,30 +1,36 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import React from 'react'; import PropTypes from 'prop-types'; -import { BubblesLoader } from '@reportportal/ui-kit'; import { ErrorBoundary } from 'components/containers/errorBoundary'; -import { createImportProps } from 'controllers/plugins/uiExtensions/createImportProps'; import { ExtensionError } from './extensionError'; import { extensionType } from './extensionTypes'; -import { useFederatedComponent } from './hooks'; -import { getExtensionUrl } from './utils'; +import { FederatedExtensionLoader } from './federatedExtensionLoader'; +import { StandaloneExtensionLoader } from './standaloneExtensionLoader'; function ExtensionLoader({ extension, withPreloader, ...componentProps }) { - const { moduleName, scope, pluginName } = extension; - const url = getExtensionUrl(extension); - - const { failed, Component } = useFederatedComponent(scope, moduleName, url); - - if (failed) { - return

Failed to load extension: {moduleName}

; - } - - // TODO: remove legacy extensions when all existing plugins will be migrated to the new engine - const extensionImportProps = createImportProps(pluginName); - - return ( - : null}> - {Component ? : null} - + return extension.pluginType === 'remote' ? ( + + ) : ( + ); } ExtensionLoader.propTypes = { @@ -44,6 +50,7 @@ export function ExtensionLoaderWrapper({ }) { return ( }> + {/* TODO: remove legacy extensions when all existing plugins will be migrated to the new engine */} {extension.component ? ( ) : ( diff --git a/app/src/components/extensionLoader/extensionTypes.js b/app/src/components/extensionLoader/extensionTypes.js index de12e1159e..e92f4c239b 100644 --- a/app/src/components/extensionLoader/extensionTypes.js +++ b/app/src/components/extensionLoader/extensionTypes.js @@ -1,4 +1,21 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import PropTypes from 'prop-types'; +import { PLUGIN_TYPE_REMOTE } from 'controllers/plugins/uiExtensions/constants'; /* TODO: remove legacy extensions when all existing plugins will be migrated to the new engine and within next major version release */ @@ -11,13 +28,31 @@ const oldExtensionType = PropTypes.shape({ /* New plugins mechanism related code below */ -const newExtensionType = PropTypes.shape({ +const embeddedExtensionType = PropTypes.shape({ name: PropTypes.string.isRequired, title: PropTypes.string, + // TODO: describe this field more specifically type: PropTypes.string.isRequired, moduleName: PropTypes.string, scope: PropTypes.string, pluginName: PropTypes.string.isRequired, }); -export const extensionType = PropTypes.oneOfType([oldExtensionType, newExtensionType]); +const standaloneExtensionType = PropTypes.shape({ + pluginName: PropTypes.string.isRequired, + pluginType: PropTypes.oneOf([PLUGIN_TYPE_REMOTE]), + // TODO: describe this field more specifically + type: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + internalRoute: PropTypes.string, + icon: PropTypes.shape({ + url: PropTypes.string, + svg: PropTypes.string, + }), +}); + +export const extensionType = PropTypes.oneOfType([ + oldExtensionType, + embeddedExtensionType, + standaloneExtensionType, +]); diff --git a/app/src/components/extensionLoader/federatedExtensionLoader/federatedExtensionLoader.jsx b/app/src/components/extensionLoader/federatedExtensionLoader/federatedExtensionLoader.jsx new file mode 100644 index 0000000000..af637a7ed3 --- /dev/null +++ b/app/src/components/extensionLoader/federatedExtensionLoader/federatedExtensionLoader.jsx @@ -0,0 +1,52 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { BubblesLoader } from '@reportportal/ui-kit'; +import { createImportProps } from 'controllers/plugins/uiExtensions/createImportProps'; +import { getExtensionUrl } from '../utils'; +import { useFederatedComponent } from '../hooks'; +import { extensionType } from '../extensionTypes'; + +export function FederatedExtensionLoader({ extension, withPreloader, ...componentProps }) { + const { moduleName, scope, pluginName } = extension; + const url = getExtensionUrl(extension); + + const { failed, Component } = useFederatedComponent(scope, moduleName, url); + + // TODO: replace with proper failed state + if (failed) { + return

Failed to load extension: {moduleName}

; + } + + // TODO: Provide extensionImportProps via React Context + const extensionImportProps = createImportProps(pluginName); + + return ( + : null}> + {Component ? : null} + + ); +} +FederatedExtensionLoader.propTypes = { + extension: extensionType, + withPreloader: PropTypes.bool, +}; +FederatedExtensionLoader.defaultProps = { + extension: {}, + withPreloader: false, +}; diff --git a/app/src/components/extensionLoader/federatedExtensionLoader/index.js b/app/src/components/extensionLoader/federatedExtensionLoader/index.js new file mode 100644 index 0000000000..73fb756d3e --- /dev/null +++ b/app/src/components/extensionLoader/federatedExtensionLoader/index.js @@ -0,0 +1,17 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { FederatedExtensionLoader } from './federatedExtensionLoader'; diff --git a/app/src/components/extensionLoader/standaloneExtensionLoader/index.js b/app/src/components/extensionLoader/standaloneExtensionLoader/index.js new file mode 100644 index 0000000000..be040cfb9b --- /dev/null +++ b/app/src/components/extensionLoader/standaloneExtensionLoader/index.js @@ -0,0 +1,17 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { StandaloneExtensionLoader } from './standaloneExtensionLoader'; diff --git a/app/src/components/extensionLoader/standaloneExtensionLoader/standaloneExtensionLoader.jsx b/app/src/components/extensionLoader/standaloneExtensionLoader/standaloneExtensionLoader.jsx new file mode 100644 index 0000000000..c00ed6593a --- /dev/null +++ b/app/src/components/extensionLoader/standaloneExtensionLoader/standaloneExtensionLoader.jsx @@ -0,0 +1,78 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useRef, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { userInfoSelector } from 'controllers/user'; +import { projectInfoSelector } from 'controllers/project'; +import { extensionType } from '../extensionTypes'; + +// http://localhost:3000/#superadmin_personal/plugin/BrowserKube +// TODO: add loader while loading the iframe +// TODO: configure sandbox for iframe +function StandaloneExtensionLoader({ extension, userInfo, projectInfo }) { + const [loaded, setLoaded] = useState(false); + const ref = useRef(); + + const onLoad = () => { + setLoaded(true); + }; + + const sendRpContext = () => { + const consumerOrigin = new URL(extension.url).origin; + const data = { + user: userInfo, + project: projectInfo, + }; + ref?.current?.contentWindow.postMessage(data, consumerOrigin); + }; + + useEffect(() => { + if (loaded) { + sendRpContext(); + } + }, [loaded, userInfo, projectInfo]); + + return ( +