diff --git a/frontend/packages/console-app/console-extensions.json b/frontend/packages/console-app/console-extensions.json index f6c0b88c476..6b42d774ec4 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 747218fc672..de4661f89ff 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 30f3cdc7da1..00000000000 --- 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 5c52d971977..a90236d5c78 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 65b4acdf69d..9b9218d5941 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 2114eab003c..00000000000 --- 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 c6c588b7fce..d5022bcf6da 100644 --- a/frontend/packages/kubevirt-plugin/console-extensions.json +++ b/frontend/packages/kubevirt-plugin/console-extensions.json @@ -16,6 +16,9 @@ "exact": false, "path": "/k8s/ns/:ns/virtualmachineinstances/:name/standaloneconsole", "component": { "$codeRef": "standaloneConsole" } + }, + "flags": { + "required": ["KUBEVIRT"] } }, { @@ -60,7 +63,6 @@ "required": ["KUBEVIRT"] } }, - { "type": "console.catalog/item-filter", "properties": { @@ -72,6 +74,16 @@ "required": ["KUBEVIRT"] } }, + { + "type": "console.redux-reducer", + "properties": { + "scope": "kubevirt", + "reducer": { "$codeRef": "reduxReducer" } + }, + "flags": { + "required": ["KUBEVIRT"] + } + }, { "type": "console.context-provider", "properties": { diff --git a/frontend/packages/kubevirt-plugin/package.json b/frontend/packages/kubevirt-plugin/package.json index b76f919f7e7..33745fc81d8 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 6659f863abe..6c4720ca75c 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 fe51488c706..e44235fe44c 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 6e5414a3f5e..7b9405daa0b 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 ae168effe08..0cc10ddf2a1 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 0d493026398..3d388b656e8 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/components/app.jsx b/frontend/public/components/app.jsx index 370165aab3f..065ed4ef0af 100644 --- a/frontend/public/components/app.jsx +++ b/frontend/public/components/app.jsx @@ -7,7 +7,7 @@ import { Provider, useSelector } from 'react-redux'; import { Route, Router, Switch } from 'react-router-dom'; // AbortController is not supported in some older browser versions import 'abort-controller/polyfill'; -import store from '../redux'; +import store, { applyReduxExtensions } from '../redux'; import { withTranslation } from 'react-i18next'; import { detectFeatures } from '../actions/features'; @@ -30,6 +30,7 @@ import { useExtensions } from '@console/plugin-sdk'; import { useResolvedExtensions, isContextProvider, + isReduxReducer, isStandaloneRoutePage, } from '@console/dynamic-plugin-sdk'; import { initConsolePlugins } from '@console/dynamic-plugin-sdk/src/runtime/plugin-init'; @@ -212,9 +213,15 @@ class App_ extends React.PureComponent { } const AppWithExtensions = withTranslation()((props) => { - const [contextProviderExtensions, resolved] = useResolvedExtensions(isContextProvider); + const [reduxReducerExtensions, reducersResolved] = useResolvedExtensions(isReduxReducer); + const [contextProviderExtensions, providersResolved] = useResolvedExtensions(isContextProvider); - return resolved && ; + if (reducersResolved && providersResolved) { + applyReduxExtensions(reduxReducerExtensions); + return ; + } + + return ; }); initConsolePlugins(pluginStore, store); diff --git a/frontend/public/redux.ts b/frontend/public/redux.ts index c13dff671db..b540947b9fd 100644 --- a/frontend/public/redux.ts +++ b/frontend/public/redux.ts @@ -1,13 +1,10 @@ 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 { ResolvedExtension, ReduxReducer } from '@console/dynamic-plugin-sdk'; 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,42 +50,20 @@ 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(); - - store.subscribe(() => { - const currentFlagsObject = getReduxFlagsObject(); - - if (JSON.stringify(flagsObject) !== JSON.stringify(currentFlagsObject)) { - flagsObject = currentFlagsObject; +export const applyReduxExtensions = (reducerExtensions: ResolvedExtension[]) => { + const pluginReducers: ReducersMapObject = {}; - const pluginReducerExtensions = reducerExtensions.filter((e) => - isExtensionInUse(e, flagsObject), - ); - - const pluginReducers: ReducersMapObject = pluginReducerExtensions.reduce((map, e) => { - map[e.properties.namespace] = e.properties.reducer; - return map; - }, {}); + reducerExtensions.forEach(({ properties: { scope, reducer } }) => { + pluginReducers[scope] = reducer; + }); - const nextReducers: ReducersMapObject = _.isEmpty(pluginReducers) - ? baseReducers - : { plugins: combineReducers(pluginReducers), ...baseReducers }; + const nextReducers: ReducersMapObject = _.isEmpty(pluginReducers) + ? baseReducers + : { plugins: combineReducers(pluginReducers), ...baseReducers }; - store.replaceReducer(combineReducers(nextReducers)); - } - }); + store.replaceReducer(combineReducers(nextReducers)); }; -addPluginListener(); - if (process.env.NODE_ENV !== 'production') { // Expose Redux store for debugging window.store = store;