From 5e66d94c545cfe36f564c78d05004835f72b70c3 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Wed, 13 Dec 2023 08:12:30 -0500 Subject: [PATCH] Update Redux to use latest packages https://issues.redhat.com/browse/HCS-256 --- package-lock.json | 94 +++++++++++-------- package.json | 9 +- src/App.tsx | 3 +- .../components/dashboard/Dashboard.tsx | 32 ++++++- src/store/dashboard/dashboardCommon.ts | 11 ++- src/store/dashboard/dashboardWidgets.ts | 16 +--- src/store/mockStore.ts | 15 ++- src/store/store.ts | 37 ++++---- 8 files changed, 133 insertions(+), 84 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a63f492..4ebd7db7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", "@redhat-cloud-services/frontend-components-translations": "^3.2.7", "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", + "@reduxjs/toolkit": "^2.0.1", "@unleash/proxy-client-react": "^4.1.1", "axios": "^1.6.5", "classnames": "^2.5.1", @@ -28,12 +29,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-intl": "^6.5.5", - "react-redux": "^8.1.3", + "react-redux": "^9.0.4", "react-router-dom": "^6.21.1", - "redux": "^4.2.1", + "redux": "^5.0.1", "redux-logger": "^3.0.6", "redux-promise-middleware": "^6.2.0", - "redux-thunk": "^2.4.2", + "redux-thunk": "^3.1.0", "typesafe-actions": "^5.1.0", "victory-core": "^36.8.1" }, @@ -3022,6 +3023,29 @@ "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-0.0.24.tgz", "integrity": "sha512-P50stc+mnWLycID46/AKmD/760r5N1eoam//O6MUVriqVorUdht7xkUL78aJZU1vw8WW6xlrDHwz3F6BM148qg==" }, + "node_modules/@reduxjs/toolkit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.0.1.tgz", + "integrity": "sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.0", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", @@ -3913,7 +3937,7 @@ "version": "18.2.18", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", - "devOptional": true, + "dev": true, "dependencies": { "@types/react": "*" } @@ -11554,6 +11578,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", @@ -17453,35 +17486,23 @@ } }, "node_modules/react-redux": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", - "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz", + "integrity": "sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==", "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", "use-sync-external-store": "^1.0.0" }, "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4 || ^5.0.0-beta.0" + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, "react-native": { "optional": true }, @@ -17490,11 +17511,6 @@ } } }, - "node_modules/react-redux/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -17621,12 +17637,9 @@ } }, "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "node_modules/redux-logger": { "version": "3.0.6", @@ -17645,11 +17658,11 @@ } }, "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", "peerDependencies": { - "redux": "^4" + "redux": "^5.0.0" } }, "node_modules/reflect.getprototypeof": { @@ -17769,6 +17782,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resolve": { "version": "1.22.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", diff --git a/package.json b/package.json index f5914edb..2c7238d6 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", "@redhat-cloud-services/frontend-components-translations": "^3.2.7", "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", + "@reduxjs/toolkit": "^2.0.1", "@unleash/proxy-client-react": "^4.1.1", "axios": "^1.6.5", "classnames": "^2.5.1", @@ -64,12 +65,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-intl": "^6.5.5", - "react-redux": "^8.1.3", + "react-redux": "^9.0.4", "react-router-dom": "^6.21.1", - "redux": "^4.2.1", + "redux": "^5.0.1", "redux-logger": "^3.0.6", "redux-promise-middleware": "^6.2.0", - "redux-thunk": "^2.4.2", + "redux-thunk": "^3.1.0", "typesafe-actions": "^5.1.0", "victory-core": "^36.8.1" }, @@ -115,6 +116,8 @@ "webpack-bundle-analyzer": "^4.10.1" }, "overrides": { + "react-redux": "^9.0.4", + "redux": "^5.0.1" }, "insights": { "appname": "hybrid-committed-spend" diff --git a/src/App.tsx b/src/App.tsx index 8b11f9f6..eb8d7782 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,6 @@ import { notificationsReducer } from '@redhat-cloud-services/frontend-components import { getRegistry } from '@redhat-cloud-services/frontend-components-utilities/Registry'; import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; -import type { Reducer } from 'redux'; import pkg from '../package.json'; import { initApi } from './api'; @@ -34,7 +33,7 @@ const App = () => { useEffect(() => { const registry = getRegistry(); - registry.register({ notifications: notificationsReducer as Reducer }); + registry.register({ notifications: notificationsReducer as any }); // You can use directly the name of your app updateDocumentTitle(pkg.insights.appname); diff --git a/src/routes/overview/components/dashboard/Dashboard.tsx b/src/routes/overview/components/dashboard/Dashboard.tsx index 8c02b01e..ecb5ba63 100644 --- a/src/routes/overview/components/dashboard/Dashboard.tsx +++ b/src/routes/overview/components/dashboard/Dashboard.tsx @@ -1,9 +1,15 @@ import { Grid, GridItem } from '@patternfly/react-core'; -import React from 'react'; +import React, { lazy } from 'react'; import { useSelector } from 'react-redux'; import type { RootState } from 'store'; import type { DashboardWidget } from 'store/dashboard'; import { dashboardSelectors, DashboardSize } from 'store/dashboard'; +import { DashboardComponent } from 'store/dashboard/dashboardCommon'; + +const ActualSpend = lazy(() => import('routes/overview/components/actual-spend')); +const ActualSpendBreakdown = lazy(() => import('routes/overview/components/actual-spend-breakdown')); +const CommittedSpend = lazy(() => import('routes/overview/components/committed-spend')); +const CommittedSpendTrend = lazy(() => import('routes/overview/components/committed-spend-trend')); interface DashboardOwnProps { // TBD... @@ -23,13 +29,33 @@ const Dashboard: React.FC = () => { {currentWidgets.map(widgetId => { const widget: any = selectWidgets[widgetId]; + + let Component; + switch (widget.component) { + case DashboardComponent.ActualSpend: + Component = ActualSpend; + break; + case DashboardComponent.ActualSpendBreakdown: + Component = ActualSpendBreakdown; + break; + case DashboardComponent.CommittedSpend: + Component = CommittedSpend; + break; + case DashboardComponent.CommittedSpendTrend: + Component = CommittedSpendTrend; + break; + } + + if (!Component) { + return null; + } return widget.size === DashboardSize.half ? ( - + ) : ( - + ); })} diff --git a/src/store/dashboard/dashboardCommon.ts b/src/store/dashboard/dashboardCommon.ts index 53bc041e..5f59a2a1 100644 --- a/src/store/dashboard/dashboardCommon.ts +++ b/src/store/dashboard/dashboardCommon.ts @@ -2,7 +2,6 @@ import type { MessageDescriptor } from '@formatjs/intl/src/types'; import type { BillingFilters, BillingQuery } from 'api/queries'; import { getBillingQuery } from 'api/queries'; import type { ReportPathsType, ReportType } from 'api/reports/report'; -import type { LazyExoticComponent } from 'react'; export const dashboardStateKey = 'dashboard'; export const dashboardDefaultFilters: BillingFilters = { @@ -17,8 +16,16 @@ export const enum DashboardSize { half = 'half', } +// eslint-disable-next-line no-shadow +export const enum DashboardComponent { + ActualSpend, + ActualSpendBreakdown, + CommittedSpend, + CommittedSpendTrend, +} + export interface DashboardWidget { - component: LazyExoticComponent>; + component: DashboardComponent; chartName: string; filter?: any; id: number; diff --git a/src/store/dashboard/dashboardWidgets.ts b/src/store/dashboard/dashboardWidgets.ts index b1c5d10e..55eac646 100644 --- a/src/store/dashboard/dashboardWidgets.ts +++ b/src/store/dashboard/dashboardWidgets.ts @@ -1,21 +1,15 @@ import { ReportPathsType, ReportType } from 'api/reports/report'; import messages from 'locales/messages'; -import { lazy } from 'react'; import { routes } from 'Routes'; import type { DashboardWidget } from './dashboardCommon'; -import { DashboardSize } from './dashboardCommon'; - -const ActualSpend = lazy(() => import('routes/overview/components/actual-spend')); -const ActualSpendBreakdown = lazy(() => import('routes/overview/components/actual-spend-breakdown')); -const CommittedSpend = lazy(() => import('routes/overview/components/committed-spend')); -const CommittedSpendTrend = lazy(() => import('routes/overview/components/committed-spend-trend')); +import { DashboardComponent, DashboardSize } from './dashboardCommon'; let currrentId = 0; const getId = () => currrentId++; export const actualSpendWidget: DashboardWidget = { - component: ActualSpend, + component: DashboardComponent.ActualSpend, chartName: 'actualSpend', id: getId(), title: messages.dashboardActualSpendTitle, @@ -25,7 +19,7 @@ export const actualSpendWidget: DashboardWidget = { }; export const actualSpendBreakdownWidget: DashboardWidget = { - component: ActualSpendBreakdown, + component: DashboardComponent.ActualSpendBreakdown, chartName: 'actualSpendBreakdown', id: getId(), title: messages.dashboardActualSpendBreakdownTitle, @@ -35,7 +29,7 @@ export const actualSpendBreakdownWidget: DashboardWidget = { }; export const committedSpendWidget: DashboardWidget = { - component: CommittedSpend, + component: DashboardComponent.CommittedSpend, chartName: 'committedSpend', id: getId(), title: messages.dashboardCommitmentSpendTitle, @@ -45,7 +39,7 @@ export const committedSpendWidget: DashboardWidget = { }; export const committedSpendTrendWidget: DashboardWidget = { - component: CommittedSpendTrend, + component: DashboardComponent.CommittedSpendTrend, chartName: 'committedSpendTrend', id: getId(), title: messages.dashboardCommitmentSpendTrendTitle, diff --git a/src/store/mockStore.ts b/src/store/mockStore.ts index c9d004c9..72f75003 100644 --- a/src/store/mockStore.ts +++ b/src/store/mockStore.ts @@ -1,15 +1,20 @@ -import type { DeepPartial, Reducer, ReducersMapObject, Store } from 'redux'; -import { applyMiddleware, combineReducers, createStore } from 'redux'; +import { configureStore as createStore } from '@reduxjs/toolkit'; +import type { Reducer, ReducersMapObject, Store } from 'redux'; +import { combineReducers } from 'redux'; import type { RootState } from './rootReducer'; -import { middlewares } from './store'; +import { middleware } from './store'; type MockReducer = ReducersMapObject | Reducer; export function createMockStoreCreator(reducer: MockReducer) { const rootReducer = typeof reducer === 'object' ? combineReducers(reducer) : reducer; - return (initialState?: DeepPartial): Store => { - return createStore(rootReducer as any, initialState, applyMiddleware(...middlewares)); + return (initialState?: Partial): Store => { + return createStore({ + middleware, + preloadedState: initialState as any, + reducer: rootReducer as any, + }); }; } diff --git a/src/store/store.ts b/src/store/store.ts index 36e5d013..e8c76948 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -1,29 +1,26 @@ -import notificationsMiddleware from '@redhat-cloud-services/frontend-components-notifications/notificationsMiddleware'; +import { notificationsMiddleware } from '@redhat-cloud-services/frontend-components-notifications/notificationsMiddleware'; +import { configureStore as createStore } from '@reduxjs/toolkit'; import axios from 'axios'; -import type { DeepPartial } from 'redux'; -import { applyMiddleware, compose, createStore } from 'redux'; -import thunk from 'redux-thunk'; import type { RootState } from './rootReducer'; import { rootReducer } from './rootReducer'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -declare global { - interface Window { - __REDUX_DEVTOOLS_EXTENSION_COMPOSE__(options: any): typeof compose; - } -} - -const composeEnhancers = - typeof window === 'object' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ serialize: true }) - : compose; +// See https://redux-toolkit.js.org/api/serializabilityMiddleware +// and https://github.com/RedHatInsights/insights-advisor-frontend/blob/master/src/Store/index.js +export const middleware = getDefaultMiddleware => + getDefaultMiddleware({ + serializableCheck: { + ignoreActions: true, + ignoreState: true, + }, + }).concat(notificationsMiddleware()); -export const middlewares = [thunk, notificationsMiddleware()]; - -export function configureStore(initialState: DeepPartial) { - const enhancer = composeEnhancers(applyMiddleware(...middlewares)); - const store = createStore(rootReducer, initialState as any, enhancer); +export function configureStore(initialState: Partial) { + const store = createStore({ + middleware, + preloadedState: initialState as any, + reducer: rootReducer, + }); axios.interceptors.response.use(null, error => { return Promise.reject(error);