Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EPMRPP-91756 || Remove old plugins mechanism #3925

Merged
merged 4 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
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 }) {
Expand All @@ -45,7 +44,7 @@
if (loaded) {
sendRpContext();
}
}, [loaded, userInfo, projectInfo]);

Check warning on line 47 in app/src/components/extensionLoader/standaloneExtensionLoader/standaloneExtensionLoader.jsx

View workflow job for this annotation

GitHub Actions / build (20)

React Hook useEffect has a missing dependency: 'sendRpContext'. Either include it or remove the dependency array

Check warning on line 47 in app/src/components/extensionLoader/standaloneExtensionLoader/standaloneExtensionLoader.jsx

View workflow job for this annotation

GitHub Actions / build (20)

React Hook useEffect has a missing dependency: 'sendRpContext'. Either include it or remove the dependency array

return (
<iframe
Expand Down
24 changes: 21 additions & 3 deletions app/src/components/extensionLoader/utils.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
/*
* 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 { URLS } from 'common/urls';
import { MAIN_FILE_KEY } from 'controllers/plugins/uiExtensions/constants';

const DEFAULT_EXTENSION_FILE_NAME = 'remoteEntity.js';

export const getExtensionUrl = (extension) => {
const isDev = process.env.NODE_ENV === 'development';
const { pluginName, url: defaultUrl } = extension;
const { pluginName, url: defaultUrl, binaryData } = extension;
const fileName = binaryData[MAIN_FILE_KEY] || DEFAULT_EXTENSION_FILE_NAME;

if (isDev && defaultUrl) {
return `${defaultUrl}/${DEFAULT_EXTENSION_FILE_NAME}`;
return `${defaultUrl}/${fileName}`;
}

return URLS.pluginPublicFile(pluginName, DEFAULT_EXTENSION_FILE_NAME);
return URLS.pluginPublicFile(pluginName, fileName);
};
3 changes: 2 additions & 1 deletion app/src/components/integrations/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019 EPAM Systems
* 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.
Expand All @@ -24,6 +24,7 @@ import LdapIcon from 'common/img/plugins/ldap.png';
import DefaultPluginIcon from 'common/img/plugins/default-plugin-icon.svg';
import ActiveDirectoryIcon from 'common/img/plugins/activeDirectory.png';

// Constants for plugins that do not provide UI extensions in their bundles
export const PLUGIN_NAME_TITLES = {
[JIRA]: 'JIRA Server',
[RALLY]: 'RALLY',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 EPAM Systems
* 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.
Expand All @@ -15,78 +15,39 @@
*/

import React from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { URLS } from 'common/urls';
import { activeProjectSelector } from 'controllers/user';
import { COMMAND_GET_FILE } from 'controllers/plugins/uiExtensions/constants';
import { globalIntegrationsSelector } from 'controllers/plugins/selectors';
import {
filterIntegrationsByName,
isPluginSupportsAllowedCommand,
isPluginSupportsCommonCommand,
} from 'controllers/plugins/utils';
import { ICON_FILE_KEY } from 'controllers/plugins/uiExtensions/constants';
import { PLUGIN_DEFAULT_IMAGE, PLUGIN_IMAGES_MAP } from 'components/integrations/constants';
import { Image } from 'components/main/image';

export const PluginIcon = ({ pluginData, className, ...rest }) => {
const { details, name, enabled } = pluginData;
const isDynamicIconAvailable = details?.binaryData?.icon;
const projectId = useSelector(activeProjectSelector);
const globalIntegrations = useSelector(globalIntegrationsSelector);
const { details, name } = pluginData;
const isDynamicIconAvailable = details?.binaryData?.[ICON_FILE_KEY];

const calculateIconParams = () => {
const commandParams = { method: 'PUT', data: { fileKey: 'icon' } };

if (isDynamicIconAvailable && enabled) {
const isCommonCommandSupported = isPluginSupportsCommonCommand(pluginData, COMMAND_GET_FILE);
const isAllowedCommandSupported = isPluginSupportsAllowedCommand(
pluginData,
COMMAND_GET_FILE,
);

if (isCommonCommandSupported) {
return {
url: URLS.pluginCommandCommon(projectId, name, COMMAND_GET_FILE),
requestParams: commandParams,
};
}

const integration = filterIntegrationsByName(globalIntegrations, name)[0];
if (integration && isAllowedCommandSupported) {
return {
url: URLS.projectIntegrationByIdCommand(projectId, integration.id, COMMAND_GET_FILE),
requestParams: commandParams,
};
}

return {
url: URLS.pluginPublicFile(name, details.binaryData.icon),
};
const calculateIconUrl = () => {
if (isDynamicIconAvailable) {
return URLS.pluginPublicFile(name, details.binaryData[ICON_FILE_KEY]);
}

return {
url: PLUGIN_IMAGES_MAP[name] || PLUGIN_DEFAULT_IMAGE,
};
return PLUGIN_IMAGES_MAP[name] || PLUGIN_DEFAULT_IMAGE;
};

const { url, requestParams } = calculateIconParams();
const url = calculateIconUrl();

return (
<div className={className}>
<Image
src={url}
fallback={PLUGIN_DEFAULT_IMAGE}
isStatic={!isDynamicIconAvailable}
requestParams={requestParams}
preloaderColor="charcoal"
className={className}
{...rest}
/>
</div>
);
};

PluginIcon.propTypes = {
pluginData: PropTypes.object,
className: PropTypes.string,
Expand Down
3 changes: 1 addition & 2 deletions app/src/controllers/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export {
isPostIssueActionAvailable,
isAuthorizationPlugin,
isPluginSwitchable,
isPluginSupportsCommonCommand,
} from './utils';
export { isPluginSupportsCommonCommand } from './uiExtensions/utils';
export {
pluginsSelector,
pluginByNameSelector,
Expand All @@ -62,7 +62,6 @@ export { pluginSagas } from './sagas';
export {
uiExtensionSettingsTabsSelector,
uiExtensionAdminPagesSelector,
extensionsLoadedSelector,
uiExtensionSidebarComponentsSelector,
uiExtensionLaunchItemComponentsSelector,
uiExtensionIntegrationSettingsSelector,
Expand Down
3 changes: 2 additions & 1 deletion app/src/controllers/plugins/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,10 @@ export const integrationsReducer = (state = {}, { type = '', payload = {} }) =>
}
};

// TODO: store remote plugins separately
export const pluginsReducer = combineReducers({
plugins: queueReducers(fetchReducer(NAMESPACE), updatePluginLocallyReducer),
publicPlugins: fetchReducer(PUBLIC_PLUGINS),
publicPlugins: queueReducers(fetchReducer(PUBLIC_PLUGINS), updatePluginLocallyReducer),
integrations: integrationsReducer,
uiExtensions: uiExtensionsReducer,
pluginsLoading: loadingReducer(NAMESPACE),
Expand Down
21 changes: 8 additions & 13 deletions app/src/controllers/plugins/sagas.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019 EPAM Systems
* 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.
Expand Down Expand Up @@ -38,7 +38,6 @@ import {
REMOVE_INTEGRATION,
FETCH_GLOBAL_INTEGRATIONS,
SECRET_FIELDS_KEY,
FETCH_GLOBAL_INTEGRATIONS_SUCCESS,
UPDATE_PLUGIN_SUCCESS,
PUBLIC_PLUGINS,
} from './constants';
Expand All @@ -56,7 +55,7 @@ import {
fetchGlobalIntegrationsSuccessAction,
removeGlobalIntegrationsByTypeSuccessAction,
} from './actionCreators';
import { fetchUiExtensions, fetchExtensionsMetadata } from './uiExtensions';
import { fetchExtensionManifests, fetchExtensionManifest } from './uiExtensions';

function* addIntegration({ payload: { data, isGlobal, pluginName, callback }, meta }) {
yield put(showScreenLockAction());
Expand Down Expand Up @@ -255,19 +254,15 @@ function* watchRemovePlugin() {
yield takeEvery(REMOVE_PLUGIN, removePlugin);
}

// TODO: in the future plugins with js parts should not depend on integrations, only on plugins.
function* watchPluginChange() {
function* watchPluginListFetch() {
yield takeEvery(
[createFetchPredicate(NAMESPACE), FETCH_GLOBAL_INTEGRATIONS_SUCCESS, UPDATE_PLUGIN_SUCCESS],
fetchUiExtensions,
[createFetchPredicate(NAMESPACE), createFetchPredicate(PUBLIC_PLUGINS)],
fetchExtensionManifests,
);
}

function* watchPublicPluginChange() {
yield takeEvery(
[createFetchPredicate(PUBLIC_PLUGINS), UPDATE_PLUGIN_SUCCESS],
fetchExtensionsMetadata,
);
function* watchPluginChange() {
yield takeEvery(UPDATE_PLUGIN_SUCCESS, fetchExtensionManifest);
}

export function* pluginSagas() {
Expand All @@ -281,6 +276,6 @@ export function* pluginSagas() {
watchFetchPublicPlugins(),
watchRemovePlugin(),
watchPluginChange(),
watchPublicPluginChange(),
watchPluginListFetch(),
]);
}
28 changes: 8 additions & 20 deletions app/src/controllers/plugins/uiExtensions/actions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 EPAM Systems
* 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.
Expand All @@ -14,25 +14,13 @@
* limitations under the License.
*/

import {
EXTENSION_LOAD_FINISH,
EXTENSION_LOAD_START,
FETCH_EXTENSIONS_METADATA_SUCCESS,
UPDATE_EXTENSION_METADATA,
} from './constants';
import { FETCH_EXTENSION_MANIFESTS_SUCCESS, UPDATE_EXTENSION_MANIFEST } from './constants';

export const extensionLoadStartAction = () => ({
type: EXTENSION_LOAD_START,
export const fetchExtensionManifestsSuccessAction = (extensionManifests) => ({
type: FETCH_EXTENSION_MANIFESTS_SUCCESS,
payload: extensionManifests,
});
export const extensionLoadFinishAction = () => ({
type: EXTENSION_LOAD_FINISH,
});

export const fetchExtensionsMetadataSuccessAction = (extensionsMetadata) => ({
type: FETCH_EXTENSIONS_METADATA_SUCCESS,
payload: extensionsMetadata,
});
export const updateExtensionMetadataAction = (extensionMetadata) => ({
type: UPDATE_EXTENSION_METADATA,
payload: extensionMetadata,
export const updateExtensionManifestAction = (extensionManifest) => ({
type: UPDATE_EXTENSION_MANIFEST,
payload: extensionManifest,
});
34 changes: 24 additions & 10 deletions app/src/controllers/plugins/uiExtensions/constants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
/*
* 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.
*/

// extension types
export const EXTENSION_TYPE_SETTINGS_TAB = 'uiExtension:settingsTab';
export const EXTENSION_TYPE_ADMIN_SIDEBAR_COMPONENT = 'uiExtension:adminSidebarComponent';
export const EXTENSION_TYPE_ADMIN_PAGE = 'uiExtension:adminPage';
export const EXTENSION_TYPE_MODAL = 'uiExtension:modal';
export const EXTENSION_TYPE_SIDEBAR_COMPONENT = 'uiExtension:sidebarComponent';
export const EXTENSION_TYPE_LAUNCH_ITEM_COMPONENT = 'uiExtension:launchItemComponent';
export const EXTENSION_TYPE_INTEGRATION_FORM_FIELDS = 'uiExtension:integrationFormFields';
Expand All @@ -22,22 +38,20 @@ export const EXTENSION_TYPE_LOG_STACKTRACE_ADDON = 'uiExtension:logStacktraceAdd
export const EXTENSION_TYPE_TEST_ITEM_DETAILS_ADDON = 'uiExtension:testItemDetailsAddon';
export const EXTENSION_TYPE_PROJECT_PAGE = 'uiExtension:projectPage';

export const COMMAND_GET_FILE = 'getFile';
// plugin commands
export const COMMAND_GET_ISSUE_TYPES = 'getIssueTypes';
export const COMMAND_GET_ISSUE_FIELDS = 'getIssueFields';
export const COMMAND_POST_ISSUE = 'postTicket';
export const COMMAND_GET_ISSUE = 'getIssue';
export const COMMAND_GET_CLUSTERS = 'getClusters';

export const EXTENSION_LOAD_START = 'extensionLoadStart';
export const EXTENSION_LOAD_FINISH = 'extensionLoadFinish';

/* New plugins mechanism related code below */

export const METADATA_FILE_KEY = 'metadata';
// core files keys
export const MANIFEST_FILE_KEY = 'metadata';
export const MAIN_FILE_KEY = 'main';
export const ICON_FILE_KEY = 'icon';

export const FETCH_EXTENSIONS_METADATA_SUCCESS = 'fetchExtensionsMetadataSuccess';
export const UPDATE_EXTENSION_METADATA = 'updateExtensionMetadata';
// redux actions
export const FETCH_EXTENSION_MANIFESTS_SUCCESS = 'fetchExtensionManifestsSuccess';
export const UPDATE_EXTENSION_MANIFEST = 'updateExtensionManifest';

export const PLUGIN_TYPE_REMOTE = 'remote';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 EPAM Systems
* 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.
Expand Down Expand Up @@ -225,7 +225,7 @@ const INPUTS = {
WithAsyncLoading,
};

// TODO: in the future these components and other stuff will be shared via WMF
// TODO: share these components and other stuff via WMF and ui-kit library
export const createImportProps = (pluginName) => ({
lib: {
React,
Expand Down
5 changes: 4 additions & 1 deletion app/src/controllers/plugins/uiExtensions/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export const useActivePluginPageExtension = (extensionsSelector) => {
const activePluginPage = useSelector(pluginPageSelector);

const extension = React.useMemo(
() => extensions.find((ex) => ex.internalRoute === activePluginPage),
() =>
extensions.find(
(ex) => activePluginPage === ex.internalRoute || activePluginPage === ex.name,
),
[extensions, activePluginPage],
);

Expand Down
3 changes: 1 addition & 2 deletions app/src/controllers/plugins/uiExtensions/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
export { fetchUiExtensions, fetchExtensionsMetadata } from './sagas';
export { fetchExtensionManifests, fetchExtensionManifest } from './sagas';
export {
uiExtensionSettingsTabsSelector,
uiExtensionAdminPagesSelector,
extensionsLoadedSelector,
uiExtensionSidebarComponentsSelector,
uiExtensionAdminSidebarComponentsSelector,
uiExtensionLaunchItemComponentsSelector,
Expand Down
18 changes: 12 additions & 6 deletions app/src/controllers/plugins/uiExtensions/overrideExtension.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { updateExtensionMetadataAction } from './actions';
import { METADATA_FILE_KEY } from './constants';
import { pluginByNameSelector } from 'controllers/plugins';
import { updateExtensionManifestAction } from './actions';
import { MANIFEST_FILE_KEY } from './constants';

// TODO: restrict access to this function (f.e. only for admins)
export const createExtensionOverrider = (store) => async (pluginName, url) => {
const response = await fetch(`${url}/${METADATA_FILE_KEY}.json`, {
const plugin = pluginByNameSelector(store);

const manifestFileName =
plugin.details?.binaryData?.[MANIFEST_FILE_KEY] || `${MANIFEST_FILE_KEY}.json`;

const response = await fetch(`${url}/${manifestFileName}`, {
contentType: 'application/json',
});
const metadata = await response.json();
const manifest = await response.json();

store.dispatch(updateExtensionMetadataAction({ ...metadata, pluginName, url }));
store.dispatch(updateExtensionManifestAction({ ...manifest, pluginName, url }));

return metadata;
return manifest;
};
Loading
Loading