diff --git a/frontend/packages/console-app/console-extensions.json b/frontend/packages/console-app/console-extensions.json index f6c0b88c4762..6b42d774ec41 100644 --- a/frontend/packages/console-app/console-extensions.json +++ b/frontend/packages/console-app/console-extensions.json @@ -30,5 +30,12 @@ "provider": { "$codeRef": "fileUploadContext.FileUploadContextProvider" }, "useValueHook": { "$codeRef": "fileUploadContext.useValuesFileUploadContext" } } + }, + { + "type": "console.redux-reducer", + "properties": { + "scope": "console", + "reducer": { "$codeRef": "reduxReducer" } + } } ] diff --git a/frontend/packages/console-app/package.json b/frontend/packages/console-app/package.json index 747218fc672d..de4661f89ff1 100644 --- a/frontend/packages/console-app/package.json +++ b/frontend/packages/console-app/package.json @@ -33,7 +33,8 @@ "exposedModules": { "tourContext": "src/components/tour/tour-context.ts", "quickStartContext": "src/components/quick-starts/utils/quick-start-context.ts", - "fileUploadContext": "src/components/file-upload/file-upload-context.ts" + "fileUploadContext": "src/components/file-upload/file-upload-context.ts", + "reduxReducer": "src/redux/reducer.ts" } } } diff --git a/frontend/packages/console-app/src/__tests__/extension-checks/reducers.spec.ts b/frontend/packages/console-app/src/__tests__/extension-checks/reducers.spec.ts deleted file mode 100644 index 30f3cdc7da18..000000000000 --- a/frontend/packages/console-app/src/__tests__/extension-checks/reducers.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as _ from 'lodash'; -import { testedExtensions } from '../plugin-test-utils'; -import { isReduxReducer } from '@console/plugin-sdk'; - -describe('ReduxReducer', () => { - it('duplicate namespaces are not allowed', () => { - const reducers = testedExtensions.toArray().filter(isReduxReducer); - const dedupedReducers = _.uniqWith( - reducers, - (a, b) => a.properties.namespace === b.properties.namespace, - ); - const duplicateReducers = _.difference(reducers, dedupedReducers); - - expect(duplicateReducers).toEqual([]); - }); -}); diff --git a/frontend/packages/console-app/src/plugin.tsx b/frontend/packages/console-app/src/plugin.tsx index 5c52d9719772..a90236d5c787 100644 --- a/frontend/packages/console-app/src/plugin.tsx +++ b/frontend/packages/console-app/src/plugin.tsx @@ -12,7 +12,6 @@ import { DashboardsOverviewHealthPrometheusSubsystem, DashboardsOverviewInventoryItem, DashboardsOverviewHealthOperator, - ReduxReducer, ResourceDetailsPage, ResourceListPage, ResourceClusterNavItem, @@ -45,7 +44,6 @@ import { getClusterUpdateTimestamp, isClusterUpdateActivity, } from './components/dashboards-page/activity'; -import reducer from './redux/reducer'; import * as models from './models'; type ConsumedExtensions = @@ -57,7 +55,6 @@ type ConsumedExtensions = | DashboardsOverviewHealthPrometheusSubsystem | DashboardsOverviewInventoryItem | DashboardsOverviewHealthOperator - | ReduxReducer | ResourceListPage | ResourceDetailsPage | ResourceClusterNavItem @@ -211,13 +208,6 @@ const plugin: Plugin = [ ).default, }, }, - { - type: 'ReduxReducer', - properties: { - namespace: 'console', - reducer, - }, - }, { type: 'NavItem/ResourceCluster', properties: { diff --git a/frontend/packages/console-plugin-sdk/src/typings/index.ts b/frontend/packages/console-plugin-sdk/src/typings/index.ts index 65b4acdf69d7..9b9218d59411 100644 --- a/frontend/packages/console-plugin-sdk/src/typings/index.ts +++ b/frontend/packages/console-plugin-sdk/src/typings/index.ts @@ -13,7 +13,6 @@ export * from './perspectives'; export * from './yaml-templates'; export * from './global-configs'; export * from './clusterserviceversions'; -export * from './reducers'; export * from './horizontal-nav'; export * from './storage-class-params'; export * from './guided-tour'; diff --git a/frontend/packages/console-plugin-sdk/src/typings/reducers.ts b/frontend/packages/console-plugin-sdk/src/typings/reducers.ts deleted file mode 100644 index 2114eab003c1..000000000000 --- a/frontend/packages/console-plugin-sdk/src/typings/reducers.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Reducer } from 'redux'; -import { Extension } from './base'; - -namespace ExtensionProperties { - export interface ReduxReducer { - /** The key to represent reducer-managed substate within the Redux state object. */ - namespace: string; - /** The reducer function, operating on reducer-managed substate. */ - reducer: Reducer; - } -} - -export interface ReduxReducer extends Extension { - type: 'ReduxReducer'; -} - -export const isReduxReducer = (e: Extension): e is ReduxReducer => { - return e.type === 'ReduxReducer'; -}; diff --git a/frontend/packages/kubevirt-plugin/console-extensions.json b/frontend/packages/kubevirt-plugin/console-extensions.json index c6c588b7fceb..56b860e5746f 100644 --- a/frontend/packages/kubevirt-plugin/console-extensions.json +++ b/frontend/packages/kubevirt-plugin/console-extensions.json @@ -60,13 +60,19 @@ "required": ["KUBEVIRT"] } }, - { "type": "console.catalog/item-filter", "properties": { "catalogId": "dev-catalog", "type": "Template", "filter": { "$codeRef": "createVM.catalogVMTemplateFilter" } + } + }, + { + "type": "console.redux-reducer", + "properties": { + "scope": "kubevirt", + "reducer": { "$codeRef": "reduxReducer" } }, "flags": { "required": ["KUBEVIRT"] diff --git a/frontend/packages/kubevirt-plugin/package.json b/frontend/packages/kubevirt-plugin/package.json index b76f919f7e72..33745fc81d87 100644 --- a/frontend/packages/kubevirt-plugin/package.json +++ b/frontend/packages/kubevirt-plugin/package.json @@ -49,7 +49,8 @@ "pvcSelectors": "src/selectors/pvc/selectors.ts", "pvcAlert": "src/components/cdi-upload-provider/pvc-alert-extension.tsx", "pvcStatus": "src/components/cdi-upload-provider/upload-pvc-popover.tsx", - "pvcDelete": "src/components/cdi-upload-provider/pvc-delete-extension.tsx" + "pvcDelete": "src/components/cdi-upload-provider/pvc-delete-extension.tsx", + "reduxReducer": "src/redux/index.ts" } } } diff --git a/frontend/packages/kubevirt-plugin/src/plugin.tsx b/frontend/packages/kubevirt-plugin/src/plugin.tsx index 6659f863abe6..6c4720ca75c0 100644 --- a/frontend/packages/kubevirt-plugin/src/plugin.tsx +++ b/frontend/packages/kubevirt-plugin/src/plugin.tsx @@ -10,7 +10,6 @@ import { ModelDefinition, Plugin, ProjectDashboardInventoryItem, - ReduxReducer, ResourceDetailsPage, ResourceListPage, ResourceNSNavItem, @@ -26,7 +25,6 @@ import { import { diskImportKindMapping } from './components/dashboards-page/overview-dashboard/utils'; import * as models from './models'; import { VirtualMachineYAMLTemplates, VMTemplateYAMLTemplates } from './models/templates'; -import kubevirtReducer from './redux'; import { getTopologyPlugin, TopologyConsumedExtensions } from './topology/topology-plugin'; import '@console/internal/i18n.js'; @@ -43,7 +41,6 @@ type ConsumedExtensions = | DashboardsOverviewInventoryItem | DashboardsInventoryItemGroup | DashboardsStorageCapacityDropdownItem - | ReduxReducer | ProjectDashboardInventoryItem | DashboardsOverviewResourceActivity | TopologyConsumedExtensions; @@ -364,16 +361,6 @@ const plugin: Plugin = [ required: FLAG_KUBEVIRT, }, }, - { - type: 'ReduxReducer', - properties: { - namespace: 'kubevirt', - reducer: kubevirtReducer, - }, - flags: { - required: [FLAG_KUBEVIRT], - }, - }, { type: 'Project/Dashboard/Inventory/Item', properties: { diff --git a/frontend/packages/topology/console-extensions.json b/frontend/packages/topology/console-extensions.json index fe51488c7066..e44235fe44cd 100644 --- a/frontend/packages/topology/console-extensions.json +++ b/frontend/packages/topology/console-extensions.json @@ -1 +1,9 @@ -[] +[ + { + "type": "console.redux-reducer", + "properties": { + "scope": "devconsole", + "reducer": { "$codeRef": "reduxReducer" } + } + } +] diff --git a/frontend/packages/topology/package.json b/frontend/packages/topology/package.json index 6e5414a3f5eb..7b9405daa0b7 100644 --- a/frontend/packages/topology/package.json +++ b/frontend/packages/topology/package.json @@ -21,6 +21,9 @@ "topology": [ "integration-tests/**/*.scenario.ts" ] + }, + "exposedModules": { + "reduxReducer": "src/utils/reducer.ts" } } } diff --git a/frontend/packages/topology/src/plugin.tsx b/frontend/packages/topology/src/plugin.tsx index ae168effe08c..0cc10ddf2a1f 100755 --- a/frontend/packages/topology/src/plugin.tsx +++ b/frontend/packages/topology/src/plugin.tsx @@ -1,10 +1,9 @@ import * as _ from 'lodash'; -import { Plugin, ReduxReducer, ModelDefinition, ModelFeatureFlag } from '@console/plugin-sdk'; +import { Plugin, ModelDefinition, ModelFeatureFlag } from '@console/plugin-sdk'; import { OperatorsTopologyConsumedExtensions, operatorsTopologyPlugin, } from './operators/operatorsTopologyPlugin'; -import reducer from './utils/reducer'; import * as models from './models'; import { ServiceBindingModel } from './models/service-binding'; import { ALLOW_SERVICE_BINDING_FLAG } from './const'; @@ -14,7 +13,6 @@ import { TopologyDecoratorProvider } from './extensions'; type ConsumedExtensions = | ModelDefinition | ModelFeatureFlag - | ReduxReducer | TopologyDecoratorProvider | OperatorsTopologyConsumedExtensions; @@ -32,13 +30,6 @@ const plugin: Plugin = [ flag: ALLOW_SERVICE_BINDING_FLAG, }, }, - { - type: 'ReduxReducer', - properties: { - namespace: 'devconsole', - reducer, - }, - }, ...operatorsTopologyPlugin, ...defaultDecoratorsPlugin, ]; diff --git a/frontend/packages/topology/src/redux/reducer.ts b/frontend/packages/topology/src/redux/reducer.ts index 0d493026398f..3d388b656e83 100644 --- a/frontend/packages/topology/src/redux/reducer.ts +++ b/frontend/packages/topology/src/redux/reducer.ts @@ -2,7 +2,7 @@ import { Map } from 'immutable'; import { TopologyAction, Actions } from './action'; import { DEFAULT_TOPOLOGY_FILTERS } from '../filters/const'; -export type State = Map; +type State = Map; export default (state: State, action: TopologyAction) => { if (!state) { diff --git a/frontend/public/redux.ts b/frontend/public/redux.ts index c13dff671db7..212d147831be 100644 --- a/frontend/public/redux.ts +++ b/frontend/public/redux.ts @@ -1,13 +1,16 @@ import { applyMiddleware, combineReducers, createStore, compose, ReducersMapObject } from 'redux'; import * as _ from 'lodash-es'; - -import { isReduxReducer } from '@console/plugin-sdk/src/typings/reducers'; -import { isExtensionInUse, getGatingFlagNames } from '@console/plugin-sdk/src/store'; +import { ReduxReducer, isReduxReducer } from '@console/dynamic-plugin-sdk'; +import { + subscribeToExtensions, + extensionDiffListener, +} from '@console/plugin-sdk/src/api/subscribeToExtensions'; +import { resolveExtension } from '@console/dynamic-plugin-sdk/src/coderefs/coderef-resolver'; +import { unwrapPromiseSettledResults } from '@console/dynamic-plugin-sdk/src/utils/promise'; import { featureReducer, featureReducerName, FeatureState } from './reducers/features'; import k8sReducers, { K8sState } from './reducers/k8s'; import UIReducers, { UIState } from './reducers/ui'; import { dashboardsReducer, DashboardsState } from './reducers/dashboards'; -import { pluginStore } from './plugins'; const composeEnhancers = (process.env.NODE_ENV !== 'production' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; @@ -53,41 +56,35 @@ const store = createStore( composeEnhancers(applyMiddleware(thunk)), ); -const addPluginListener = () => { - const reducerExtensions = pluginStore.getAllExtensions().filter(isReduxReducer); - const getReduxFlagsObject = () => { - const gatingFlags = getGatingFlagNames(reducerExtensions); - const featureState = store.getState()[featureReducerName]; - return featureState ? _.pick(featureState.toObject(), gatingFlags) : null; - }; - - let flagsObject = getReduxFlagsObject(); +const pluginReducers: ReducersMapObject = {}; - store.subscribe(() => { - const currentFlagsObject = getReduxFlagsObject(); +subscribeToExtensions( + extensionDiffListener((added, removed) => { + removed.forEach(({ properties: { scope } }) => { + delete pluginReducers[scope]; + }); - if (JSON.stringify(flagsObject) !== JSON.stringify(currentFlagsObject)) { - flagsObject = currentFlagsObject; + Promise.allSettled(added.map(resolveExtension)).then((results) => { + const [fulfilledValues, rejectedReasons] = unwrapPromiseSettledResults(results); - const pluginReducerExtensions = reducerExtensions.filter((e) => - isExtensionInUse(e, flagsObject), - ); + fulfilledValues.forEach(({ properties: { scope, reducer } }) => { + pluginReducers[scope] = reducer; + }); - const pluginReducers: ReducersMapObject = pluginReducerExtensions.reduce((map, e) => { - map[e.properties.namespace] = e.properties.reducer; - return map; - }, {}); + if (rejectedReasons.length > 0) { + // eslint-disable-next-line no-console + console.error('Failed to resolve Redux reducer extensions', rejectedReasons); + } const nextReducers: ReducersMapObject = _.isEmpty(pluginReducers) ? baseReducers : { plugins: combineReducers(pluginReducers), ...baseReducers }; store.replaceReducer(combineReducers(nextReducers)); - } - }); -}; - -addPluginListener(); + }); + }), + isReduxReducer, +); if (process.env.NODE_ENV !== 'production') { // Expose Redux store for debugging