From 3e7138a7616a27d635adae97b494a1eec9b09a15 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Thu, 9 Feb 2023 18:29:37 -0500 Subject: [PATCH] chore(build): sw-625 review react router (#1052) * refactor(redux): sw-625 remove connectRouter (#1045) * refactor(config): sw-625 generate routes (#1045) * refactor(useRouter): sw-625 combine routerContext (#1045) * refactor(routerContext): sw-625 useRedirect hook --- config/build.plugins.js | 5 +- config/cspell.config.json | 1 + config/jest.setupTests.js | 23 +- package.json | 4 +- src/AppEntry.js | 15 +- src/app.js | 13 +- .../__snapshots__/authentication.test.js.snap | 281 +- .../authenticationContext.test.js.snap | 32 + .../__tests__/authenticationContext.test.js | 7 +- .../authentication/authentication.js | 25 +- .../authentication/authenticationContext.js | 16 +- .../__tests__/__snapshots__/i18n.test.js.snap | 6 +- src/components/i18n/i18n.js | 47 +- .../productViewContext.test.js.snap | 18 +- .../productViewMissing.test.js.snap | 109 +- .../__tests__/productViewMissing.test.js | 38 +- src/components/productView/productView.js | 114 +- .../productView/productViewMissing.js | 67 +- .../__snapshots__/redirect.test.js.snap | 453 ---- .../__snapshots__/router.test.js.snap | 2365 ++++++++++++++++- .../__snapshots__/routerContext.test.js.snap | 48 + .../__snapshots__/routerHelpers.test.js.snap | 245 +- .../router/__tests__/redirect.test.js | 64 - .../router/__tests__/router.test.js | 14 + .../router/__tests__/routerContext.test.js | 37 +- .../router/__tests__/routerHelpers.test.js | 126 +- src/components/router/index.js | 4 +- src/components/router/redirect.js | 66 - src/components/router/router.js | 118 +- src/components/router/routerContext.js | 546 +++- src/components/router/routerHelpers.js | 342 +-- .../__snapshots__/index.test.js.snap | 39 +- .../product.openshiftContainer.test.js.snap | 74 + .../__snapshots__/product.rhel.test.js.snap | 74 + .../product.satellite.test.js.snap | 126 +- .../__snapshots__/products.test.js.snap | 137 + .../__snapshots__/routes.test.js.snap | 21 + src/config/__tests__/index.test.js | 26 +- .../product.openshiftContainer.test.js | 10 + src/config/__tests__/product.rhel.test.js | 10 + .../__tests__/product.satellite.test.js | 2 +- src/config/__tests__/products.test.js | 37 + src/config/__tests__/routes.test.js | 7 + src/config/index.js | 20 +- src/config/product.openshiftContainer.js | 4 +- src/config/product.openshiftDedicated.js | 2 + src/config/product.openshiftMetrics.js | 6 +- src/config/product.rhacs.js | 2 + src/config/product.rhel.js | 12 +- src/config/product.rhods.js | 2 + src/config/product.rhosak.js | 2 + src/config/product.satellite.js | 12 +- src/config/products.js | 89 + src/config/routes.js | 118 +- .../__snapshots__/useRouter.test.js.snap | 51 - src/hooks/__tests__/useRouter.test.js | 42 - src/hooks/useRouter.js | 47 - src/index.appEntry.js | 18 + src/index.bootstrap.js | 7 + src/index.js | 7 +- src/redux/index.js | 13 +- src/redux/reducers/viewReducer.js | 23 +- src/redux/types/appTypes.js | 6 +- tests/__snapshots__/code.test.js.snap | 1 + tests/__snapshots__/dist.test.js.snap | 57 +- yarn.lock | 119 +- 66 files changed, 4293 insertions(+), 2179 deletions(-) delete mode 100644 src/components/router/__tests__/__snapshots__/redirect.test.js.snap delete mode 100644 src/components/router/__tests__/redirect.test.js delete mode 100644 src/components/router/redirect.js create mode 100644 src/config/__tests__/__snapshots__/products.test.js.snap create mode 100644 src/config/__tests__/__snapshots__/routes.test.js.snap create mode 100644 src/config/__tests__/products.test.js create mode 100644 src/config/__tests__/routes.test.js create mode 100644 src/config/products.js delete mode 100644 src/hooks/__tests__/__snapshots__/useRouter.test.js.snap delete mode 100644 src/hooks/__tests__/useRouter.test.js delete mode 100644 src/hooks/useRouter.js create mode 100644 src/index.appEntry.js create mode 100644 src/index.bootstrap.js diff --git a/config/build.plugins.js b/config/build.plugins.js index c19413022..263eff5a6 100644 --- a/config/build.plugins.js +++ b/config/build.plugins.js @@ -33,7 +33,10 @@ const setCommonPlugins = () => { }), fedModulePlugin({ root: RELATIVE_DIRNAME, - shared: [{ 'react-redux': { requiredVersion: dependencies['react-redux'] } }] + shared: [ + { 'react-router-dom': { singleton: true, requiredVersion: '*' } }, + { 'react-redux': { requiredVersion: dependencies['react-redux'] } } + ] }) ]; diff --git a/config/cspell.config.json b/config/cspell.config.json index c0631e649..47e36bb34 100644 --- a/config/cspell.config.json +++ b/config/cspell.config.json @@ -23,6 +23,7 @@ "hoverable", "ibmpower", "ibmz", + "iife", "ipsum", "keycloak", "kubernetes", diff --git a/config/jest.setupTests.js b/config/jest.setupTests.js index dd6cee87b..947ceb4fd 100644 --- a/config/jest.setupTests.js +++ b/config/jest.setupTests.js @@ -53,6 +53,14 @@ jest.mock('react-redux', () => ({ useSelector: jest.fn() })); +/** + * Emulate react router dom useLocation + */ +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => ({ hash: '', search: '' }) +})); + /** * Add the displayName property to function based components. Makes sure that snapshot tests have named components * instead of displaying a generic "". @@ -258,10 +266,7 @@ global.mockWindowLocation = async ( { url = 'https://ci.foo.redhat.com/subscriptions/rhel', location: locationProps = {} } = {} ) => { const updatedUrl = new URL(url); - const { location } = window; - delete window.location; - // mock - window.location = { + const updatedLocation = { href: updatedUrl.href, search: updatedUrl.search, hash: updatedUrl.hash, @@ -269,9 +274,13 @@ global.mockWindowLocation = async ( replace: Function.prototype, ...locationProps }; - await callback(window.location); - // restore - window.location = location; + + const { mockClear } = mockObjectProperty(window, 'location', updatedLocation); + await callback(updatedLocation); + + return { + mockClear + }; }; // FixMe: revisit squashing log and group messaging, redux leaks log messaging diff --git a/package.json b/package.json index cdfb8e98a..37e377997 100644 --- a/package.json +++ b/package.json @@ -97,8 +97,8 @@ "react-dom": "^17.0.2", "react-i18next": "^12.0.0", "react-redux": "^8.0.5", - "react-router": "5.3.3", - "react-router-dom": "5.3.0", + "react-router": "6.8.1", + "react-router-dom": "6.8.1", "react-use": "^17.4.0", "redux": "^4.2.0", "redux-logger": "^3.0.6", diff --git a/src/AppEntry.js b/src/AppEntry.js index 5263c7141..aeff9c274 100644 --- a/src/AppEntry.js +++ b/src/AppEntry.js @@ -1,18 +1,17 @@ import React from 'react'; import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import { routerHelpers } from './components/router'; import { store } from './redux'; import App from './app'; import './styles/index.scss'; import '@patternfly/react-styles/css/components/Select/select.css'; -const AppEntry = () => ( - - +const AppEntry = () => { + console.log('>>> APP ENTRY LOAD >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); + return ( + - - -); + + ); +}; export { AppEntry as default, AppEntry }; diff --git a/src/app.js b/src/app.js index 96764bac2..9ddd37786 100644 --- a/src/app.js +++ b/src/app.js @@ -25,9 +25,12 @@ const App = ({ getLocale, useDispatch: useAliasDispatch, useSelector: useAliasSe const dispatch = useAliasDispatch(); const { value: locale } = useAliasSelector(({ user }) => user?.locale?.data, {}); let platformNotifications = null; - + console.log('>>> APP load', locale); useMount(() => { - dispatch(getLocale()); + console.log('>>> APP mount comp', locale); + if (!locale) { + dispatch(getLocale()); + } }); if (!helpers.UI_DISABLED_NOTIFICATIONS) { @@ -35,10 +38,10 @@ const App = ({ getLocale, useDispatch: useAliasDispatch, useSelector: useAliasSe } return ( - + {platformNotifications} - - + + ); diff --git a/src/components/authentication/__tests__/__snapshots__/authentication.test.js.snap b/src/components/authentication/__tests__/__snapshots__/authentication.test.js.snap index cfd49a79a..2c5ecdd1d 100644 --- a/src/components/authentication/__tests__/__snapshots__/authentication.test.js.snap +++ b/src/components/authentication/__tests__/__snapshots__/authentication.test.js.snap @@ -52,6 +52,7 @@ exports[`Authentication Component should render a component authorized: authoriz isDisabled={false} t={[Function]} useGetAuthorization={[Function]} + useRedirect={[Function]} > - - +/> `; exports[`Authentication Component should return a redirect on a specific 403 error and error code: 403 error 1`] = ` @@ -421,98 +332,7 @@ exports[`Authentication Component should return a redirect on a specific 403 err "errorStatus": 403, } } -> - - +/> `; exports[`Authentication Component should return a redirect on a specific 403 error and error code: 403 redirect error 1`] = ` @@ -526,96 +346,5 @@ exports[`Authentication Component should return a redirect on a specific 403 err "errorStatus": 403, } } -> - - +/> `; diff --git a/src/components/authentication/__tests__/__snapshots__/authenticationContext.test.js.snap b/src/components/authentication/__tests__/__snapshots__/authenticationContext.test.js.snap index b65956c25..6187881c6 100644 --- a/src/components/authentication/__tests__/__snapshots__/authenticationContext.test.js.snap +++ b/src/components/authentication/__tests__/__snapshots__/authenticationContext.test.js.snap @@ -63,6 +63,38 @@ exports[`AuthenticationContext should apply a hook for retrieving auth data from } `; +exports[`AuthenticationContext should apply a hook for retrieving auth data from multiple selectors: success dispatch 1`] = ` +[ + [ + [Function], + ], + [ + [ + { + "payload": Promise {}, + "type": "PLATFORM_INIT", + }, + { + "meta": { + "data": { + "name": "subscriptions", + }, + }, + "payload": Promise {}, + "type": "PLATFORM_APP_NAME", + }, + { + "payload": Promise {}, + "type": "PLATFORM_GLOBAL_FILTER_HIDE", + }, + ], + ], + [ + [Function], + ], +] +`; + exports[`AuthenticationContext should apply a hook for retrieving auth data from multiple selectors: success response 1`] = ` { "data": { diff --git a/src/components/authentication/__tests__/authenticationContext.test.js b/src/components/authentication/__tests__/authenticationContext.test.js index c0e1eebc5..8881fb31b 100644 --- a/src/components/authentication/__tests__/authenticationContext.test.js +++ b/src/components/authentication/__tests__/authenticationContext.test.js @@ -6,7 +6,7 @@ describe('AuthenticationContext', () => { expect(context).toMatchSnapshot('specific properties'); }); - it('should apply a hook for retrieving auth data from multiple selectors', () => { + it('should apply a hook for retrieving auth data from multiple selectors', async () => { const { result: errorResponse } = shallowHook(() => useGetAuthorization({ useSelectorsResponse: () => ({ @@ -29,8 +29,10 @@ describe('AuthenticationContext', () => { expect(errorResponse).toMatchSnapshot('error response'); - const { result: successResponse } = shallowHook(() => + const mockDispatch = jest.fn(); + const { result: successResponse } = await mountHook(() => useGetAuthorization({ + useDispatch: () => mockDispatch, useSelectorsResponse: () => ({ fulfilled: true, data: { @@ -47,6 +49,7 @@ describe('AuthenticationContext', () => { }) ); + expect(mockDispatch.mock.calls).toMatchSnapshot('success dispatch'); expect(successResponse).toMatchSnapshot('success response'); const { result: mockStoreSuccessResponse } = shallowHook(() => useGetAuthorization(), { diff --git a/src/components/authentication/authentication.js b/src/components/authentication/authentication.js index ec33f8453..5a1f6f7e0 100644 --- a/src/components/authentication/authentication.js +++ b/src/components/authentication/authentication.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { BinocularsIcon } from '@patternfly/react-icons'; import { Maintenance } from '@redhat-cloud-services/frontend-components/Maintenance'; import { NotAuthorized } from '@redhat-cloud-services/frontend-components/NotAuthorized'; -import { routerHelpers, Redirect } from '../router'; +import { routerContext, routerHelpers } from '../router'; import { rhsmConstants } from '../../services/rhsm/rhsmConstants'; import { helpers } from '../../common'; import MessageView from '../messageView/messageView'; @@ -19,9 +19,18 @@ import { AuthenticationContext, useGetAuthorization } from './authenticationCont * @param {boolean} props.isDisabled * @param {Function} props.t * @param {Function} props.useGetAuthorization + * @param {Function} props.useRedirect * @returns {React.ReactNode} */ -const Authentication = ({ appName, children, isDisabled, t, useGetAuthorization: useAliasGetAuthorization }) => { +const Authentication = ({ + appName, + children, + isDisabled, + t, + useGetAuthorization: useAliasGetAuthorization, + useRedirect: useAliasRedirect +}) => { + const redirect = useAliasRedirect(); const { pending, data = {} } = useAliasGetAuthorization(); const { authorized = {}, errorCodes, errorStatus } = data; const { [appName]: isAuthorized } = authorized; @@ -47,7 +56,7 @@ const Authentication = ({ appName, children, isDisabled, t, useGetAuthorization: (errorCodes && errorCodes.includes(rhsmConstants.RHSM_API_RESPONSE_ERRORS_CODE_TYPES.OPTIN)) || errorStatus === 418 ) { - return ; + return redirect(routerHelpers.errorRoute.path); } return ( @@ -63,26 +72,28 @@ const Authentication = ({ appName, children, isDisabled, t, useGetAuthorization: /** * Prop types. * - * @type {{useGetAuthorization: Function, children: React.ReactNode, appName: string, isDisabled: boolean}} + * @type {{useGetAuthorization: Function, children: React.ReactNode, appName: string, useRedirect: Function, isDisabled: boolean}} */ Authentication.propTypes = { appName: PropTypes.string, children: PropTypes.node.isRequired, isDisabled: PropTypes.bool, t: PropTypes.func, - useGetAuthorization: PropTypes.func + useGetAuthorization: PropTypes.func, + useRedirect: PropTypes.func }; /** * Default props. * - * @type {{useGetAuthorization: Function, t: Function, appName: string, isDisabled: boolean}} + * @type {{useGetAuthorization: Function, t: Function, appName: string, useRedirect: Function, isDisabled: boolean}} */ Authentication.defaultProps = { appName: routerHelpers.appName, isDisabled: helpers.UI_DISABLED, t: translate, - useGetAuthorization + useGetAuthorization, + useRedirect: routerContext.useRedirect }; export { Authentication as default, Authentication }; diff --git a/src/components/authentication/authenticationContext.js b/src/components/authentication/authenticationContext.js index 252d8c0c9..803b7334c 100644 --- a/src/components/authentication/authenticationContext.js +++ b/src/components/authentication/authenticationContext.js @@ -1,7 +1,6 @@ -import React, { useContext, useState } from 'react'; -import { useMount, useUnmount } from 'react-use'; +import React, { useContext } from 'react'; +import { useMount } from 'react-use'; import { reduxActions, storeHooks } from '../../redux'; -import { routerHooks } from '../../hooks/useRouter'; import { helpers } from '../../common'; import { routerHelpers } from '../router'; @@ -29,10 +28,8 @@ const useAuthContext = () => useContext(AuthenticationContext); * @param {Function} options.authorizeUser * @param {Function} options.hideGlobalFilter * @param {Function} options.initializeChrome - * @param {Function} options.onNavigation * @param {Function} options.setAppName * @param {Function} options.useDispatch - * @param {Function} options.useHistory * @param {Function} options.useSelectorsResponse * @returns {{data: {errorCodes, errorStatus: *, locale}, pending: boolean, fulfilled: boolean, error: boolean}} */ @@ -41,14 +38,10 @@ const useGetAuthorization = ({ authorizeUser = reduxActions.platform.authorizeUser, hideGlobalFilter = reduxActions.platform.hideGlobalFilter, initializeChrome = reduxActions.platform.initializeChrome, - onNavigation = reduxActions.platform.onNavigation, setAppName = reduxActions.platform.setAppName, useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch, - useHistory: useAliasHistory = routerHooks.useHistory, useSelectorsResponse: useAliasSelectorsResponse = storeHooks.reactRedux.useSelectorsResponse } = {}) => { - const [unregister, setUnregister] = useState(() => helpers.noop); - const history = useAliasHistory(); const dispatch = useAliasDispatch(); const { data, error, fulfilled, pending, responses } = useAliasSelectorsResponse([ { id: 'auth', selector: ({ user }) => user?.auth }, @@ -62,11 +55,6 @@ const useGetAuthorization = ({ useMount(async () => { await dispatch(authorizeUser()); dispatch([initializeChrome(), setAppName(appName), hideGlobalFilter()]); - setUnregister(() => dispatch(onNavigation(event => history.push(event.navId)))); - }); - - useUnmount(() => { - unregister(); }); const [user = {}, app = {}] = (Array.isArray(data.auth) && data.auth) || []; diff --git a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap index 620c761fd..8c08fea02 100644 --- a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap +++ b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap @@ -277,7 +277,7 @@ exports[`I18n Component should generate a predictable locale key output snapshot }, { "key": "curiosity-view.title", - "match": "t(\`curiosity-view.title\`, { appName: helpers.UI_DISPLAY_NAME, context: routeProductLabel })", + "match": "t(\`curiosity-view.title\`, { appName: helpers.UI_DISPLAY_NAME, context: updatedRouteProductLabel })", }, ], }, @@ -290,11 +290,11 @@ exports[`I18n Component should generate a predictable locale key output snapshot }, { "key": "curiosity-view.title", - "match": "t('curiosity-view.title', { appName: helpers.UI_DISPLAY_NAME, context: (Array.isArray(product.pathParameter)", + "match": "t('curiosity-view.title', { appName: helpers.UI_DISPLAY_NAME, context: product.productId })", }, { "key": "curiosity-view.description", - "match": "t('curiosity-view.description', { appName: helpers.UI_DISPLAY_NAME, context: (Array.isArray(product.productParameter)", + "match": "t('curiosity-view.description', { appName: helpers.UI_DISPLAY_NAME, context: product.productId })", }, ], }, diff --git a/src/components/i18n/i18n.js b/src/components/i18n/i18n.js index 2bad0450d..9c52e6802 100644 --- a/src/components/i18n/i18n.js +++ b/src/components/i18n/i18n.js @@ -24,35 +24,38 @@ const I18n = ({ children, fallbackLng, loadPath, locale }) => { * Initialize i18next */ useMount(async () => { - try { - await i18next - .use(XHR) - .use(initReactI18next) - .init({ - backend: { - loadPath - }, - fallbackLng, - lng: undefined, - debug: !helpers.PROD_MODE, - ns: ['default'], - defaultNS: 'default', - react: { - useSuspense: false - } - }); - } catch (e) { - // - } + console.log('>>> mount i18n'); + if (!initialized) { + try { + await i18next + .use(XHR) + .use(initReactI18next) + .init({ + backend: { + loadPath + }, + fallbackLng, + lng: undefined, + debug: !helpers.PROD_MODE, + ns: ['default'], + defaultNS: 'default', + react: { + useSuspense: false + } + }); + } catch (e) { + // + } - setInitialized(true); + setInitialized(true); + } }); /** * Update locale. */ useEffect(() => { - if (initialized) { + if (initialized && locale) { try { i18next.changeLanguage(locale); } catch (e) { diff --git a/src/components/productView/__tests__/__snapshots__/productViewContext.test.js.snap b/src/components/productView/__tests__/__snapshots__/productViewContext.test.js.snap index b56a5b7b6..d08e8fd0b 100644 --- a/src/components/productView/__tests__/__snapshots__/productViewContext.test.js.snap +++ b/src/components/productView/__tests__/__snapshots__/productViewContext.test.js.snap @@ -24,6 +24,10 @@ exports[`ProductViewContext should apply a hook for retrieving product context: exports[`ProductViewContext should apply a hook for retrieving product context: product context, uom filtering cores 1`] = ` { + "aliases": [ + "OpenShift Container Platform", + "openshift", + ], "graphTallyQuery": { "granularity": "Daily", }, @@ -145,20 +149,25 @@ exports[`ProductViewContext should apply a hook for retrieving product context: "sort": "next_event_date", }, "productDisplay": "partial", - "productGroup": "OpenShift Container Platform", + "productGroup": "openshift-container", "productId": "OpenShift Container Platform", "productLabel": "OpenShift Container Platform", + "productPath": "openshift-container", "query": { "beginning": "2019-06-20T00:00:00.000Z", "ending": "2019-07-20T23:59:59.999Z", "uom": "cores", }, - "viewId": "viewOpenShift Container Platform", + "viewId": "viewopenshift-container", } `; exports[`ProductViewContext should apply a hook for retrieving product context: product context, uom filtering sockets 1`] = ` { + "aliases": [ + "OpenShift Container Platform", + "openshift", + ], "graphTallyQuery": { "granularity": "Daily", }, @@ -280,15 +289,16 @@ exports[`ProductViewContext should apply a hook for retrieving product context: "sort": "next_event_date", }, "productDisplay": "partial", - "productGroup": "OpenShift Container Platform", + "productGroup": "openshift-container", "productId": "OpenShift Container Platform", "productLabel": "OpenShift Container Platform", + "productPath": "openshift-container", "query": { "beginning": "2019-06-20T00:00:00.000Z", "ending": "2019-07-20T23:59:59.999Z", "uom": "cores", }, - "viewId": "viewOpenShift Container Platform", + "viewId": "viewopenshift-container", } `; diff --git a/src/components/productView/__tests__/__snapshots__/productViewMissing.test.js.snap b/src/components/productView/__tests__/__snapshots__/productViewMissing.test.js.snap index 70538a3cb..1ab5cd824 100644 --- a/src/components/productView/__tests__/__snapshots__/productViewMissing.test.js.snap +++ b/src/components/productView/__tests__/__snapshots__/productViewMissing.test.js.snap @@ -3,20 +3,12 @@ exports[`ProductViewMissing Component should redirect when there are limited product cards: redirect action 1`] = ` [ [ - { - "meta": { - "appName": undefined, - "id": "rhel", - "secondaryNav": undefined, - }, - "payload": Promise {}, - "type": "PLATFORM_SET_NAV", - }, + "openshift-container", ], ] `; -exports[`ProductViewMissing Component should render a non-connected component: non-connected 1`] = ` +exports[`ProductViewMissing Component should render a basic component: basic 1`] = ` @@ -34,43 +26,7 @@ exports[`ProductViewMissing Component should render a non-connected component: n > - - - t(curiosity-view.title, {"appName":"Subscriptions","context":"RHEL"}) - - - - t(curiosity-view.description, {"appName":"Subscriptions","context":"RHEL"}) - - - - - - @@ -106,7 +62,7 @@ exports[`ProductViewMissing Component should render a non-connected component: n @@ -142,7 +98,7 @@ exports[`ProductViewMissing Component should render a non-connected component: n @@ -150,13 +106,13 @@ exports[`ProductViewMissing Component should render a non-connected component: n headingLevel="h2" size="lg" > - t(curiosity-view.title, {"appName":"Subscriptions","context":"rhacs"}) + t(curiosity-view.title, {"appName":"Subscriptions","context":"OpenShift-metrics"}) - t(curiosity-view.description, {"appName":"Subscriptions","context":"rhacs"}) + t(curiosity-view.description, {"appName":"Subscriptions","context":"OpenShift-metrics"}) t(curiosity-inventory.label, {"context":"numberOfGuests","count":3}, [object Object]) , @@ -323,7 +417,7 @@ exports[`Product Satellite config should apply hosts inventory configuration: fi }, "inventory_id": { "title": "t(curiosity-inventory.header_inventory, {"context":"id"})", - "value": "lorem inventory id", + "value": "XXXX-XXXX-XXXXX-XXXXX", }, "last_seen": { "title": "t(curiosity-inventory.header_last, {"context":"seen"})", @@ -473,23 +567,23 @@ exports[`Product Satellite config should apply hosts inventory configuration: fi ], "columnHeaders": [ { - "title": "t(curiosity-inventory.header_display, {"context":"name"})", + "title": "t(curiosity-inventory.header_display_name, {"context":"Satellite"})", "transforms": [], }, { - "title": "t(curiosity-inventory.header_measurement, {"context":"type"})", + "title": "t(curiosity-inventory.header_measurement_type, {"context":"Satellite"})", "transforms": [ [Function], ], }, { - "title": "t(curiosity-inventory.header, {"context":"sockets"})", + "title": "t(curiosity-inventory.header_sockets, {"context":"Satellite"})", "transforms": [ [Function], ], }, { - "title": "t(curiosity-inventory.header_last, {"context":"seen"})", + "title": "t(curiosity-inventory.header_last_seen, {"context":"Satellite"})", "transforms": [ [Function], ], @@ -497,43 +591,43 @@ exports[`Product Satellite config should apply hosts inventory configuration: fi ], "data": { "cloud_provider": { - "title": "t(curiosity-inventory.header_cloud, {"context":"provider"})", + "title": "t(curiosity-inventory.header_cloud_provider, {"context":"Satellite"})", "value": "dolor sit", }, "cores": { - "title": "t(curiosity-inventory.header, {"context":"cores"})", + "title": "t(curiosity-inventory.header_cores, {"context":"Satellite"})", "value": 12, }, "display_name": { - "title": "t(curiosity-inventory.header_display, {"context":"name"})", + "title": "t(curiosity-inventory.header_display_name, {"context":"Satellite"})", "value": "lorem", }, "hardware_type": { - "title": "t(curiosity-inventory.header_hardware, {"context":"type"})", + "title": "t(curiosity-inventory.header_hardware_type, {"context":"Satellite"})", "value": "ipsum", }, "inventory_id": { - "title": "t(curiosity-inventory.header_inventory, {"context":"id"})", + "title": "t(curiosity-inventory.header_inventory_id, {"context":"Satellite"})", "value": null, }, "last_seen": { - "title": "t(curiosity-inventory.header_last, {"context":"seen"})", + "title": "t(curiosity-inventory.header_last_seen, {"context":"Satellite"})", "value": null, }, "loremIpsum": { - "title": "t(curiosity-inventory.header, {"context":"loremIpsum"})", + "title": "t(curiosity-inventory.header_loremIpsum, {"context":"Satellite"})", "value": "hello world", }, "measurement_type": { - "title": "t(curiosity-inventory.header_measurement, {"context":"type"})", + "title": "t(curiosity-inventory.header_measurement_type, {"context":"Satellite"})", "value": null, }, "number_of_guests": { - "title": "t(curiosity-inventory.header_number_of, {"context":"guests"})", + "title": "t(curiosity-inventory.header_number_of_guests, {"context":"Satellite"})", "value": 3, }, "sockets": { - "title": "t(curiosity-inventory.header, {"context":"sockets"})", + "title": "t(curiosity-inventory.header_sockets, {"context":"Satellite"})", "value": 10, }, }, diff --git a/src/config/__tests__/__snapshots__/products.test.js.snap b/src/config/__tests__/__snapshots__/products.test.js.snap new file mode 100644 index 000000000..9ee1cca7c --- /dev/null +++ b/src/config/__tests__/__snapshots__/products.test.js.snap @@ -0,0 +1,137 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Product configurations should return sorted product configs: sorted 1`] = ` +{ + "byGroupIdConfigs": { + "dolor": [ + { + "productGroup": "dolor", + "productId": "dolor sit", + "viewId": "view-dolor", + }, + ], + "hello world": [ + { + "productGroup": "hello world", + "productId": "hello world", + "viewId": "view-hello world", + }, + ], + "lorem": [ + { + "productGroup": "lorem", + "productId": "lorem-ipsum", + "viewId": "view-lorem", + }, + { + "productGroup": "lorem", + "productId": "lorem-dolor", + "viewId": "view-lorem", + }, + ], + }, + "byGroupIds": { + "dolor": [ + "dolor sit", + ], + "hello world": [ + "hello world", + ], + "lorem": [ + "lorem-ipsum", + "lorem-dolor", + ], + }, + "byProductIdConfigs": { + "dolor sit": { + "productGroup": "dolor", + "productId": "dolor sit", + "viewId": "view-dolor", + }, + "hello world": { + "productGroup": "hello world", + "productId": "hello world", + "viewId": "view-hello world", + }, + "lorem-dolor": { + "productGroup": "lorem", + "productId": "lorem-dolor", + "viewId": "view-lorem", + }, + "lorem-ipsum": { + "productGroup": "lorem", + "productId": "lorem-ipsum", + "viewId": "view-lorem", + }, + }, + "byProductIds": [ + "lorem-ipsum", + "lorem-dolor", + "dolor sit", + "hello world", + ], + "byViewIdConfigs": { + "view-dolor": [ + { + "productGroup": "dolor", + "productId": "dolor sit", + "viewId": "view-dolor", + }, + ], + "view-hello world": [ + { + "productGroup": "hello world", + "productId": "hello world", + "viewId": "view-hello world", + }, + ], + "view-lorem": [ + { + "productGroup": "lorem", + "productId": "lorem-ipsum", + "viewId": "view-lorem", + }, + { + "productGroup": "lorem", + "productId": "lorem-dolor", + "viewId": "view-lorem", + }, + ], + }, + "byViewIds": { + "view-dolor": [ + "dolor sit", + ], + "view-hello world": [ + "hello world", + ], + "view-lorem": [ + "lorem-ipsum", + "lorem-dolor", + ], + }, +} +`; + +exports[`Product configurations should return specific methods and properties: products 1`] = ` +{ + "configs": [ + "OpenShift Container Platform", + "OpenShift-dedicated-metrics", + "OpenShift-metrics", + "rhacs", + "RHEL", + "rhods", + "rhosak", + "Satellite", + ], + "sortedConfigs": [ + "byGroupIdConfigs", + "byGroupIds", + "byProductIdConfigs", + "byProductIds", + "byViewIdConfigs", + "byViewIds", + ], +} +`; diff --git a/src/config/__tests__/__snapshots__/routes.test.js.snap b/src/config/__tests__/__snapshots__/routes.test.js.snap new file mode 100644 index 000000000..ef4c60fed --- /dev/null +++ b/src/config/__tests__/__snapshots__/routes.test.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Routes configuration should return generated routes: expected paths 1`] = ` +[ + "/openshift-container", + "/OpenShift Container Platform", + "/openshift", + "/OpenShift-metrics", + "/openshift-dedicated-metrics", + "/openshift-dedicated", + "/rhacs", + "/rhel", + "/insights", + "/rhods", + "/rhosak", + "/streams", + "/satellite", + "/optin", + "/", +] +`; diff --git a/src/config/__tests__/index.test.js b/src/config/__tests__/index.test.js index 55f8b9e90..693629801 100644 --- a/src/config/__tests__/index.test.js +++ b/src/config/__tests__/index.test.js @@ -11,7 +11,7 @@ describe('Configuration', () => { const inconsistentEntries = []; - Object.values(config.products).forEach((value, index) => { + Object.values(config.products.configs).forEach((value, index) => { const { productGroup, productId, @@ -37,7 +37,7 @@ describe('Configuration', () => { }; if (Object.values(entryCheck).indexOf('FAIL') > -1) { - inconsistentEntries.push({ [`Entry ${index} inconsistent`]: entryCheck }); + inconsistentEntries.push({ [`Entry ${index}, ${productId} inconsistent`]: entryCheck }); } }); @@ -61,21 +61,19 @@ describe('Configuration', () => { config.routes.forEach((value, index) => { const entryCheck = { - redirect: - typeof config.routes[index].redirect === 'string' || config.routes[index].redirect === null ? 'PASS' : 'FAIL', - isSearchable: typeof config.routes[index].isSearchable === 'boolean' ? 'PASS' : 'FAIL', - aliases: Array.isArray(config.routes[index].aliases) ? 'PASS' : 'FAIL', - activateOnError: typeof config.routes[index].activateOnError === 'boolean' ? 'PASS' : 'FAIL', - disabled: typeof config.routes[index].disabled === 'boolean' ? 'PASS' : 'FAIL', - default: typeof config.routes[index].default === 'boolean' ? 'PASS' : 'FAIL', - component: - typeof config.routes[index].component === 'string' || config.routes[index].component === null - ? 'PASS' - : 'FAIL' + path: typeof value.path === 'string' ? 'PASS' : 'FAIL', + pathParameter: Array.isArray(value.pathParameter) ? 'PASS' : 'FAIL', + productParameter: Array.isArray(value.productParameter) ? 'PASS' : 'FAIL', + productConfig: Array.isArray(value.productConfig) ? 'PASS' : 'FAIL', + redirect: typeof value.redirect === 'string' || value.redirect === null ? 'PASS' : 'FAIL', + activateOnError: typeof value.activateOnError === 'boolean' ? 'PASS' : 'FAIL', + disabled: typeof value.disabled === 'boolean' ? 'PASS' : 'FAIL', + default: typeof value.default === 'boolean' ? 'PASS' : 'FAIL', + component: typeof value.component === 'string' || value.component === null ? 'PASS' : 'FAIL' }; if (Object.values(entryCheck).indexOf('FAIL') > -1) { - inconsistentEntries.push({ [`Entry ${index} inconsistent`]: entryCheck }); + inconsistentEntries.push({ [`Entry ${index}, ${value.path} inconsistent`]: entryCheck }); } }); diff --git a/src/config/__tests__/product.openshiftContainer.test.js b/src/config/__tests__/product.openshiftContainer.test.js index 2571ff457..75e5ea8b0 100644 --- a/src/config/__tests__/product.openshiftContainer.test.js +++ b/src/config/__tests__/product.openshiftContainer.test.js @@ -108,6 +108,16 @@ describe('Product OpenShift Container config', () => { expect(fallbackFilteredInventoryData).toMatchSnapshot('filtered, fallback display'); + const filteredInventoryDataInfinite = parseRowCellsListData({ + filters: initialFilters, + cellData: { + ...inventoryData, + [SUBSCRIPTIONS_INVENTORY_TYPES.HAS_INFINITE_QUANTITY]: false + } + }); + + expect(filteredInventoryDataInfinite).toMatchSnapshot('filtered, infinite'); + expect(inventoryQuery[RHSM_API_QUERY_SET_TYPES.DIRECTION] === SORT_DIRECTION_TYPES.DESCENDING).toBe(true); }); diff --git a/src/config/__tests__/product.rhel.test.js b/src/config/__tests__/product.rhel.test.js index e79c62ba8..b7db9dc81 100644 --- a/src/config/__tests__/product.rhel.test.js +++ b/src/config/__tests__/product.rhel.test.js @@ -101,6 +101,16 @@ describe('Product RHEL config', () => { expect(fallbackFilteredInventoryData).toMatchSnapshot('filtered, fallback display'); + const filteredInventoryDataInfinite = parseRowCellsListData({ + filters: initialFilters, + cellData: { + ...inventoryData, + [SUBSCRIPTIONS_INVENTORY_TYPES.HAS_INFINITE_QUANTITY]: false + } + }); + + expect(filteredInventoryDataInfinite).toMatchSnapshot('filtered, infinite'); + expect(inventoryQuery[RHSM_API_QUERY_SET_TYPES.DIRECTION] === SORT_DIRECTION_TYPES.DESCENDING).toBe(true); }); diff --git a/src/config/__tests__/product.satellite.test.js b/src/config/__tests__/product.satellite.test.js index 8a1d1e3bc..5dfb5400a 100644 --- a/src/config/__tests__/product.satellite.test.js +++ b/src/config/__tests__/product.satellite.test.js @@ -20,7 +20,7 @@ describe('Product Satellite config', () => { const inventoryData = { [INVENTORY_TYPES.DISPLAY_NAME]: 'lorem', - [INVENTORY_TYPES.INVENTORY_ID]: 'lorem inventory id', + [INVENTORY_TYPES.INVENTORY_ID]: undefined, [INVENTORY_TYPES.HARDWARE_TYPE]: 'ipsum', [INVENTORY_TYPES.MEASUREMENT_TYPE]: null, [INVENTORY_TYPES.NUMBER_OF_GUESTS]: 3, diff --git a/src/config/__tests__/products.test.js b/src/config/__tests__/products.test.js new file mode 100644 index 000000000..dcfad1455 --- /dev/null +++ b/src/config/__tests__/products.test.js @@ -0,0 +1,37 @@ +import { products } from '../products'; + +describe('Product configurations', () => { + it('should return specific methods and properties', () => { + expect({ + configs: products.configs.map(({ productId }) => productId), + sortedConfigs: Object.keys(products.sortedConfigs()) + }).toMatchSnapshot('products'); + }); + + it('should return sorted product configs', () => { + expect( + products.sortedConfigs([ + { + productGroup: 'lorem', + productId: 'lorem-ipsum', + viewId: `view-lorem` + }, + { + productGroup: 'lorem', + productId: 'lorem-dolor', + viewId: `view-lorem` + }, + { + productGroup: 'dolor', + productId: 'dolor sit', + viewId: `view-dolor` + }, + { + productGroup: 'hello world', + productId: 'hello world', + viewId: `view-hello world` + } + ]) + ).toMatchSnapshot('sorted'); + }); +}); diff --git a/src/config/__tests__/routes.test.js b/src/config/__tests__/routes.test.js new file mode 100644 index 000000000..c3aae2f72 --- /dev/null +++ b/src/config/__tests__/routes.test.js @@ -0,0 +1,7 @@ +import { routes } from '../routes'; + +describe('Routes configuration', () => { + it('should return generated routes', () => { + expect(routes.map(({ path }) => path)).toMatchSnapshot('expected paths'); + }); +}); diff --git a/src/config/index.js b/src/config/index.js index 3b2369cd5..a9b0954d3 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,25 +1,11 @@ -import { config as rhel } from './product.rhel'; -import { config as openshiftContainer } from './product.openshiftContainer'; -import { config as openshiftMetrics } from './product.openshiftMetrics'; -import { config as openshiftDedicated } from './product.openshiftDedicated'; -import { config as rhosak } from './product.rhosak'; -import { config as satelliteProduct } from './product.satellite'; import rbacConfig from './rbac.json'; +import { products as productConfig } from './products'; import { routes as routesConfig } from './routes'; -const productsConfig = { - rhel, - rhosak, - openshiftContainer, - openshiftMetrics, - openshiftDedicated, - satelliteProduct -}; - const config = { - products: productsConfig, + products: productConfig, rbac: rbacConfig, routes: routesConfig }; -export { config as default, config, productsConfig, rbacConfig, routesConfig }; +export { config as default, config, productConfig, rbacConfig, routesConfig }; diff --git a/src/config/product.openshiftContainer.js b/src/config/product.openshiftContainer.js index a6c002d3a..96dd8a9a3 100644 --- a/src/config/product.openshiftContainer.js +++ b/src/config/product.openshiftContainer.js @@ -28,16 +28,18 @@ import { translate } from '../components/i18n/i18n'; // ToDo: evaluate the need for "productLabel" or using productId -const productGroup = RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT; +const productGroup = 'openshift-container'; const productId = RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT; const productLabel = RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT; const config = { + aliases: [RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT, 'openshift'], productGroup, productId, productLabel, + productPath: productGroup.toLowerCase(), productDisplay: DISPLAY_TYPES.PARTIAL, viewId: `view${productGroup}`, productContextFilterUom: true, diff --git a/src/config/product.openshiftDedicated.js b/src/config/product.openshiftDedicated.js index 2552ad89c..2d1ffac03 100644 --- a/src/config/product.openshiftDedicated.js +++ b/src/config/product.openshiftDedicated.js @@ -29,9 +29,11 @@ const productId = RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT_DEDICATED_METRICS; const productLabel = RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT_DEDICATED_METRICS; const config = { + aliases: ['openshift-dedicated'], productGroup, productId, productLabel, + productPath: productGroup.toLowerCase(), productDisplay: DISPLAY_TYPES.DUAL_AXES, viewId: `view${productGroup}`, query: { diff --git a/src/config/product.openshiftMetrics.js b/src/config/product.openshiftMetrics.js index df9f04b9c..48b9b50f7 100644 --- a/src/config/product.openshiftMetrics.js +++ b/src/config/product.openshiftMetrics.js @@ -21,18 +21,20 @@ import { translate } from '../components/i18n/i18n'; // ToDo: evaluate the need for "productLabel" or using productId -const productGroup = RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT_METRICS; +const productGroup = 'openshift-container'; const productId = RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT_METRICS; const productLabel = RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT_METRICS; const config = { + aliases: [RHSM_API_PATH_PRODUCT_TYPES.OPENSHIFT_METRICS, 'openshift'], productGroup, productId, productLabel, + productPath: productGroup.toLowerCase(), productDisplay: DISPLAY_TYPES.PARTIAL, - viewId: `view${productGroup}`, + viewId: `view${productGroup}-${productId}`, query: { [RHSM_API_QUERY_SET_TYPES.START_DATE]: dateHelpers.getRangedMonthDateTime('current').value.startDate.toISOString(), [RHSM_API_QUERY_SET_TYPES.END_DATE]: dateHelpers.getRangedMonthDateTime('current').value.endDate.toISOString() diff --git a/src/config/product.rhacs.js b/src/config/product.rhacs.js index 1254f3a9b..46a77f6d8 100644 --- a/src/config/product.rhacs.js +++ b/src/config/product.rhacs.js @@ -36,9 +36,11 @@ const productId = RHSM_API_PATH_PRODUCT_TYPES.RHACS; const productLabel = RHSM_API_PATH_PRODUCT_TYPES.RHACS; const config = { + aliases: [], productGroup, productId, productLabel, + productPath: productGroup.toLowerCase(), productDisplay: DISPLAY_TYPES.HOURLY, viewId: `view${productGroup}`, query: { diff --git a/src/config/product.rhel.js b/src/config/product.rhel.js index 9fb2aefeb..04b180e2b 100644 --- a/src/config/product.rhel.js +++ b/src/config/product.rhel.js @@ -41,14 +41,24 @@ import { translate } from '../components/i18n/i18n'; const productGroup = RHSM_API_PATH_PRODUCT_TYPES.RHEL; -const productId = null; +const productId = RHSM_API_PATH_PRODUCT_TYPES.RHEL; const productLabel = RHSM_API_PATH_PRODUCT_TYPES.RHEL; +/** + * RHEL product config + * + * @type {{aliases: string[], productGroup: string, productId: string, productLabel: string, productDisplay: string, viewId: string, + * productArchitectures: string[], query: object, graphTallyQuery: object, inventoryHostQuery: object, inventorySubscriptionsQuery: object, + * initialGraphFilters: {}[], initialGraphSettings: object, initialGuestsFilters: {}[], initialInventoryFilters: {}[], + * initialSubscriptionsInventoryFilters: {}[], initialToolbarFilters: {}[], }} + */ const config = { + aliases: ['insights'], productGroup, productId, productLabel, + productPath: productGroup.toLowerCase(), productDisplay: DISPLAY_TYPES.CAPACITY, viewId: `view${productGroup}`, productArchitectures: [...Object.values(RHSM_API_PATH_PRODUCT_ARCHITECTURE_RHEL_TYPES)], diff --git a/src/config/product.rhods.js b/src/config/product.rhods.js index 43d6f4c43..8248faeba 100644 --- a/src/config/product.rhods.js +++ b/src/config/product.rhods.js @@ -31,9 +31,11 @@ const productId = RHSM_API_PATH_PRODUCT_TYPES.RHODS; const productLabel = RHSM_API_PATH_PRODUCT_TYPES.RHODS; const config = { + aliases: [], productGroup, productId, productLabel, + productPath: productGroup.toLowerCase(), productDisplay: DISPLAY_TYPES.HOURLY, viewId: `view${productGroup}`, query: { diff --git a/src/config/product.rhosak.js b/src/config/product.rhosak.js index 2117d75e0..a7f97d9b7 100644 --- a/src/config/product.rhosak.js +++ b/src/config/product.rhosak.js @@ -40,9 +40,11 @@ const productId = RHSM_API_PATH_PRODUCT_TYPES.RHOSAK; const productLabel = RHSM_API_PATH_PRODUCT_TYPES.RHOSAK; const config = { + aliases: ['streams'], productGroup, productId, productLabel, + productPath: productGroup.toLowerCase(), productDisplay: DISPLAY_TYPES.HOURLY, viewId: `view${productGroup}`, query: { diff --git a/src/config/product.satellite.js b/src/config/product.satellite.js index 195932242..771d6f89d 100644 --- a/src/config/product.satellite.js +++ b/src/config/product.satellite.js @@ -33,14 +33,24 @@ import { translate } from '../components/i18n/i18n'; const productGroup = RHSM_API_PATH_PRODUCT_TYPES.SATELLITE; -const productId = null; +const productId = RHSM_API_PATH_PRODUCT_TYPES.SATELLITE; const productLabel = RHSM_API_PATH_PRODUCT_TYPES.SATELLITE; +/** + * Satellite product config + * + * @type {{aliases: string[], productGroup: string, productId: string, productLabel: string, productDisplay: string, viewId: string, + * productVariants: string[], query: object, graphTallyQuery: object, inventoryHostQuery: object, + * inventorySubscriptionsQuery: object, initialGraphFilters: {}[], initialGraphSettings: object, initialGuestsFilters: {}[], + * initialInventoryFilters: {}[], initialToolbarFilters: {}[], }[]} + */ const config = { + aliases: [], productGroup, productId, productLabel, + productPath: productGroup.toLowerCase(), productDisplay: DISPLAY_TYPES.PARTIAL, viewId: `view${productGroup}`, productVariants: [...Object.values(RHSM_API_PATH_PRODUCT_VARIANT_SATELLITE_TYPES)], diff --git a/src/config/products.js b/src/config/products.js new file mode 100644 index 000000000..6b6215e99 --- /dev/null +++ b/src/config/products.js @@ -0,0 +1,89 @@ +import _memoize from 'lodash/memoize'; + +/** + * IIFE for generating a product configs listing via webpack + * + * @type {{aliases: string[], productGroup: string, productId: string, productLabel: string, productDisplay: string, viewId: string, + * productArchitectures: string[], productVariants: string[], query: object, graphTallyQuery: object, inventoryHostQuery: object, + * inventorySubscriptionsQuery: object, initialGraphFilters: {}[], initialGraphSettings: object, initialGuestsFilters: {}[], + * initialInventoryFilters: {}[], initialSubscriptionsInventoryFilters: {}[], initialToolbarFilters: {}[], }[]} + */ +const productConfigs = (() => { + try { + const path = require.context('./', false, /product\.[\d\D]+\.js$/i); + return path.keys().map(path); + } catch (e) { + /** + * Basic configuration for testing only. + */ + if (process.env.REACT_APP_ENV === 'test' && require) { + return [ + ...require('fs') // eslint-disable-line + ?.readdirSync('./src/config') // eslint-disable-line + ?.filter(file => /product\.[a-z]+\.js/i.test(file)) // eslint-disable-line + ?.map(file => require(`./${file}`)) // eslint-disable-line + ]; + } + + console.warn(`Product configuration failed to load: ${e.message}`); + return []; + } +})()?.map(value => value.config); + +/** + * Sorted/organized/grouped product configs. + * - byGroupIdConfigs, object of productGroup properties against an array of associated product configs + * - byViewIds, object of viewId properties against an array of associated productId strings. "viewId" was created because of the + * overlap with productIds and productGroups, this may be refactored in the future + * - byProductIds, a unique array of all productId strings + * - byGroupIds, object of productGroup properties against an array of associated productId strings. + * - byViewIdConfigs, object of viewId properties against an array of associated product configs + * - byProductIdConfigs, object of productId properties against a product config + * + * @param {productConfigs} configs + * @returns {{byGroupIdConfigs: {}, byViewIds: {}, byProductIds: any[], byGroupIds: {}, byViewIdConfigs: {}, byProductIdConfigs: {}}} + */ +const sortedProductConfigs = _memoize((configs = productConfigs) => { + const productIds = new Set(); + const productIdConfigs = {}; + const groupIdConfigs = {}; + const groupedGroupIds = {}; + const viewIdConfigs = {}; + const groupedViewIds = {}; + + configs?.forEach(config => { + Object.freeze(config); + + const { productGroup, productId, viewId } = config; + productIdConfigs[productId] = config; + productIds.add(productId); + + groupIdConfigs[productGroup] ??= []; + groupIdConfigs[productGroup].push(config); + + groupedGroupIds[productGroup] ??= []; + groupedGroupIds[productGroup].push(productId); + + viewIdConfigs[viewId] ??= []; + viewIdConfigs[viewId].push(config); + + groupedViewIds[viewId] ??= []; + groupedViewIds[viewId].push(productId); + }); + + return { + byGroupIdConfigs: groupIdConfigs, + byGroupIds: groupedGroupIds, + byProductIdConfigs: productIdConfigs, + byProductIds: Array.from(productIds), + byViewIdConfigs: viewIdConfigs, + byViewIds: groupedViewIds + }; +}); + +const products = { + configs: productConfigs, + sortedConfigs: sortedProductConfigs +}; + +export { products as default, products, productConfigs, sortedProductConfigs }; diff --git a/src/config/routes.js b/src/config/routes.js index d88b26901..9a4673a0c 100644 --- a/src/config/routes.js +++ b/src/config/routes.js @@ -1,108 +1,10 @@ -import { config as rhelConfig } from './product.rhel'; -import { config as openshiftContainerConfig } from './product.openshiftContainer'; -import { config as openshiftMetricsConfig } from './product.openshiftMetrics'; -import { config as openshiftDedicatedConfig } from './product.openshiftDedicated'; -import { config as rhacsConfig } from './product.rhacs'; -import { config as rhodsConfig } from './product.rhods'; -import { config as rhosakConfig } from './product.rhosak'; -import { config as satelliteProductConfig } from './product.satellite'; -import { RHSM_API_PATH_PRODUCT_TYPES } from '../services/rhsm/rhsmConstants'; import { helpers } from '../common'; const routes = [ { - id: 'rhel', - path: '/rhel', - pathParameter: [RHSM_API_PATH_PRODUCT_TYPES.RHEL], - productParameter: [rhelConfig.productGroup], - productConfig: [{ ...rhelConfig, productId: RHSM_API_PATH_PRODUCT_TYPES.RHEL }], + id: 'any', + path: ':productPath', redirect: null, - isSearchable: true, - aliases: ['insights'], - activateOnError: false, - disabled: helpers.UI_DISABLED, - default: false, - component: 'productView/productView' - }, - { - id: 'openshift-container', - path: '/openshift-container', - pathParameter: [openshiftContainerConfig.productId, openshiftMetricsConfig.productId], - productParameter: [openshiftContainerConfig.productGroup, openshiftMetricsConfig.productGroup], - productConfig: [openshiftContainerConfig, openshiftMetricsConfig], - redirect: null, - isSearchable: true, - aliases: [], - activateOnError: false, - disabled: helpers.UI_DISABLED, - default: false, - component: 'productView/productView' - }, - { - id: 'openshift-dedicated', - path: '/openshift-dedicated', - pathParameter: [openshiftDedicatedConfig.productId], - productParameter: [openshiftDedicatedConfig.productGroup], - productConfig: [openshiftDedicatedConfig], - redirect: null, - isSearchable: true, - aliases: [], - activateOnError: false, - disabled: helpers.UI_DISABLED, - default: false, - component: 'productView/productView' - }, - { - id: 'rhacs', - path: '/rhacs', - pathParameter: [rhacsConfig.productId], - productParameter: [rhacsConfig.productGroup], - productConfig: [rhacsConfig], - redirect: null, - isSearchable: true, - aliases: ['rhacs'], - activateOnError: false, - disabled: helpers.UI_DISABLED, - default: false, - component: 'productView/productView' - }, - { - id: 'rhods', - path: '/rhods', - pathParameter: [rhodsConfig.productId], - productParameter: [rhodsConfig.productGroup], - productConfig: [rhodsConfig], - redirect: null, - isSearchable: true, - aliases: ['rhods'], - activateOnError: false, - disabled: helpers.UI_DISABLED, - default: false, - component: 'productView/productView' - }, - { - id: 'rhosak', - path: '/streams', - pathParameter: [rhosakConfig.productId], - productParameter: [rhosakConfig.productGroup], - productConfig: [rhosakConfig], - redirect: null, - isSearchable: true, - aliases: ['application-services', 'streams', 'rhosak'], - activateOnError: false, - disabled: helpers.UI_DISABLED, - default: false, - component: 'productView/productView' - }, - { - id: 'satellite', - path: '/satellite', - pathParameter: [RHSM_API_PATH_PRODUCT_TYPES.SATELLITE], - productParameter: [satelliteProductConfig.productGroup], - productConfig: [{ ...satelliteProductConfig, productId: RHSM_API_PATH_PRODUCT_TYPES.SATELLITE }], - redirect: null, - isSearchable: true, - aliases: [], activateOnError: false, disabled: helpers.UI_DISABLED, default: false, @@ -110,13 +12,8 @@ const routes = [ }, { id: 'optin', - path: '/optin', - pathParameter: null, - productParameter: null, - productConfig: null, + path: 'optin', redirect: null, - isSearchable: false, - aliases: [], activateOnError: true, disabled: helpers.UI_DISABLED, default: false, @@ -124,13 +21,8 @@ const routes = [ }, { id: 'missing', - path: '/', - pathParameter: null, - productParameter: null, - productConfig: null, - redirect: '/', - isSearchable: false, - aliases: [], + path: '*', + redirect: './', activateOnError: false, disabled: helpers.UI_DISABLED, default: true, diff --git a/src/hooks/__tests__/__snapshots__/useRouter.test.js.snap b/src/hooks/__tests__/__snapshots__/useRouter.test.js.snap deleted file mode 100644 index 5c52722b9..000000000 --- a/src/hooks/__tests__/__snapshots__/useRouter.test.js.snap +++ /dev/null @@ -1,51 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`useRouter should apply a hook for useHistory that emulates history but allows dispatching platform navigation updates if available: dispatch platform navigation 1`] = ` -[ - [ - { - "meta": { - "appName": undefined, - "id": "rhel", - "secondaryNav": undefined, - }, - "payload": Promise {}, - "type": "PLATFORM_SET_NAV", - }, - ], -] -`; - -exports[`useRouter should apply a hook for useHistory that emulates history but allows dispatching platform navigation updates if available: history push 1`] = ` -[ - [ - "/lorem/ipsum", - undefined, - ], -] -`; - -exports[`useRouter should apply a hook for useHistory: dispatch platform navigation 1`] = `[]`; - -exports[`useRouter should apply a hook for useHistory: history push 1`] = ` -[ - [ - "/lorem/ipsum", - undefined, - ], - [ - "/rhel", - undefined, - ], -] -`; - -exports[`useRouter should return specific properties: specific properties 1`] = ` -{ - "useHistory": [Function], - "useLocation": [Function], - "useParams": [Function], - "useRouteDetail": [Function], - "useRouteMatch": [Function], -} -`; diff --git a/src/hooks/__tests__/useRouter.test.js b/src/hooks/__tests__/useRouter.test.js deleted file mode 100644 index 0f97043be..000000000 --- a/src/hooks/__tests__/useRouter.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { routerHooks, useHistory } from '../useRouter'; - -describe('useRouter', () => { - it('should return specific properties', () => { - expect(routerHooks).toMatchSnapshot('specific properties'); - }); - - it('should apply a hook for useHistory', () => { - const mockDispatch = jest.fn(); - const mockHistoryPush = jest.fn(); - const { result: mockUseHistory } = shallowHook(() => - useHistory({ - useDispatch: () => action => action(mockDispatch), - useHistory: () => ({ push: mockHistoryPush }) - }) - ); - - mockUseHistory.push('/lorem/ipsum'); - mockUseHistory.push('rhel'); - - expect(mockDispatch.mock.calls).toMatchSnapshot('dispatch platform navigation'); - expect(mockHistoryPush.mock.calls).toMatchSnapshot('history push'); - }); - - it('should apply a hook for useHistory that emulates history but allows dispatching platform navigation updates if available', () => { - const mockDispatch = jest.fn(); - const mockHistoryPush = jest.fn(); - const { result: mockUseHistory } = shallowHook(() => - useHistory({ - isSetAppNav: true, - useDispatch: () => action => action(mockDispatch), - useHistory: () => ({ push: mockHistoryPush }) - }) - ); - - mockUseHistory.push('/lorem/ipsum'); - mockUseHistory.push('rhel'); - - expect(mockDispatch.mock.calls).toMatchSnapshot('dispatch platform navigation'); - expect(mockHistoryPush.mock.calls).toMatchSnapshot('history push'); - }); -}); diff --git a/src/hooks/useRouter.js b/src/hooks/useRouter.js deleted file mode 100644 index d2c024439..000000000 --- a/src/hooks/useRouter.js +++ /dev/null @@ -1,47 +0,0 @@ -import { useHistory as useHistoryRRD, useLocation, useParams, useRouteMatch } from 'react-router-dom'; -import { useRouteDetail } from '../components/router/routerContext'; -import { routerHelpers } from '../components/router/routerHelpers'; -import { reduxActions, storeHooks } from '../redux'; - -/** - * Pass useHistory methods. Proxy useHistory push with Platform specific navigation update. - * - * @param {object} options - * @param {boolean} options.isSetAppNav Allow setting the Platform's left navigation if conditions are met or fallback to history.push. - * @param {Function} options.useHistory - * @param {Function} options.useDispatch - * @returns {object} - */ -const useHistory = ({ - isSetAppNav = false, - useHistory: useAliasHistory = useHistoryRRD, - useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch -} = {}) => { - const history = useAliasHistory(); - const dispatch = useAliasDispatch(); - - return { - ...history, - push: (pathLocation, historyState) => { - const pathName = (typeof pathLocation === 'string' && pathLocation) || pathLocation?.pathname; - const { productParameter, id, routeHref } = routerHelpers.getRouteConfig({ pathName, id: pathName }); - const { hash, search } = window.location; - - if (isSetAppNav && productParameter) { - return dispatch(reduxActions.platform.setAppNav(id)); - } - - return history?.push(routeHref || (pathName && `${pathName}${search}${hash}`) || pathLocation, historyState); - } - }; -}; - -const routerHooks = { - useHistory, - useLocation, - useParams, - useRouteDetail, - useRouteMatch -}; - -export { routerHooks as default, routerHooks, useHistory, useLocation, useParams, useRouteDetail, useRouteMatch }; diff --git a/src/index.appEntry.js b/src/index.appEntry.js new file mode 100644 index 000000000..5263c7141 --- /dev/null +++ b/src/index.appEntry.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { routerHelpers } from './components/router'; +import { store } from './redux'; +import App from './app'; +import './styles/index.scss'; +import '@patternfly/react-styles/css/components/Select/select.css'; + +const AppEntry = () => ( + + + + + +); + +export { AppEntry as default, AppEntry }; diff --git a/src/index.bootstrap.js b/src/index.bootstrap.js new file mode 100644 index 000000000..cce9e0f7e --- /dev/null +++ b/src/index.bootstrap.js @@ -0,0 +1,7 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { AppEntry } from './index.appEntry'; + +const root = document.getElementById('root'); + +ReactDOM.render(, root, () => root.setAttribute('data-ouia-safe', true)); diff --git a/src/index.js b/src/index.js index 7f0975627..b9291d068 100644 --- a/src/index.js +++ b/src/index.js @@ -3,17 +3,14 @@ */ import './styles/standalone.scss'; import '@patternfly/react-core/dist/styles/base.css'; -import('./bootstrap'); +import('./index.bootstrap'); import { routerHelpers } from './components/router'; window.insights = { chrome: { appNavClick: ({ id, ...rest }) => { console.log(`Emulated appNavClick: ${JSON.stringify({ id, ...rest })}`); - document.location.href = routerHelpers.pathJoin( - document.location.pathname, - routerHelpers.getRouteConfig({ id }).path - ); + document.location.href = routerHelpers.pathJoin(routerHelpers.dynamicBaseName(), id); }, auth: { getUser: () => diff --git a/src/redux/index.js b/src/redux/index.js index 197de88e0..72e1d1724 100644 --- a/src/redux/index.js +++ b/src/redux/index.js @@ -1,5 +1,4 @@ import { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; import { store } from './store'; import { reduxActions } from './actions'; import { reduxHelpers } from './common'; @@ -7,14 +6,4 @@ import { storeHooks } from './hooks'; import { reduxReducers } from './reducers'; import { reduxTypes } from './types'; -/** - * Wrapper for applying Router Dom withRouter and Redux connect. - * - * @param {Function} mapStateToProps - * @param {Function} mapDispatchToProps - * @returns {Function} - */ -const connectRouter = (mapStateToProps, mapDispatchToProps) => component => - withRouter(connect(mapStateToProps, mapDispatchToProps)(component)); - -export { connect, connectRouter, reduxActions, reduxHelpers, reduxReducers, reduxTypes, store, storeHooks }; +export { connect, reduxActions, reduxHelpers, reduxReducers, reduxTypes, store, storeHooks }; diff --git a/src/redux/reducers/viewReducer.js b/src/redux/reducers/viewReducer.js index 98d48f1ce..259809fb1 100644 --- a/src/redux/reducers/viewReducer.js +++ b/src/redux/reducers/viewReducer.js @@ -1,4 +1,4 @@ -import { routerHelpers } from '../../components/router'; +import { productConfig } from '../../config'; import { reduxTypes } from '../types'; import { reduxHelpers } from '../common/reduxHelpers'; import { RHSM_API_QUERY_SET_TYPES as RHSM_API_QUERY_TYPES } from '../../services/rhsm/rhsmConstants'; @@ -15,7 +15,8 @@ const initialState = { graphTallyQuery: {}, inventoryGuestsQuery: {}, inventoryHostsQuery: {}, - inventorySubscriptionsQuery: {} + inventorySubscriptionsQuery: {}, + product: {} }; /** @@ -29,7 +30,7 @@ const viewReducer = (state = initialState, action) => { switch (action.type) { case reduxTypes.query.SET_QUERY_RESET_INVENTORY_LIST: const updateResetQueries = (query = {}, id) => { - const queryIds = routerHelpers.productGroups[id] || (query[id] && [id]) || []; + const queryIds = productConfig.sortedConfigs().byViewIds[id] || (query[id] && [id]) || []; const updatedQuery = { ...query }; queryIds.forEach(queryId => { @@ -60,7 +61,7 @@ const viewReducer = (state = initialState, action) => { ); case reduxTypes.query.SET_QUERY_CLEAR_INVENTORY_LIST: const updateClearQueries = (query = {}, id) => { - const queryIds = routerHelpers.productGroups[id] || (query[id] && [id]) || []; + const queryIds = productConfig.sortedConfigs().byViewIds[id] || (query[id] && [id]) || []; const updatedQuery = { ...query }; queryIds.forEach(queryId => { @@ -88,7 +89,7 @@ const viewReducer = (state = initialState, action) => { ); case reduxTypes.query.SET_QUERY_CLEAR_INVENTORY_GUESTS_LIST: const updateClearGuestQuery = (query = {}, id) => { - const queryIds = routerHelpers.productGroups[id] || (query[id] && [id]) || []; + const queryIds = productConfig.sortedConfigs().byViewIds[id] || (query[id] && [id]) || []; const updatedQuery = { ...query }; queryIds.forEach(queryId => { @@ -393,6 +394,18 @@ const viewReducer = (state = initialState, action) => { reset: false } ); + case reduxTypes.app.SET_PRODUCT: + return reduxHelpers.setStateProp( + 'product', + { + // [action.viewId]: action.product + config: action.config + }, + { + state, + reset: false + } + ); default: return state; } diff --git a/src/redux/types/appTypes.js b/src/redux/types/appTypes.js index f2620c10f..a9c24d8fb 100644 --- a/src/redux/types/appTypes.js +++ b/src/redux/types/appTypes.js @@ -1,5 +1,6 @@ const STATUS_4XX = '4XX'; const STATUS_5XX = '5XX'; +const SET_PRODUCT = 'SET_PRODUCT'; /** * Application action, reducer types. @@ -8,7 +9,8 @@ const STATUS_5XX = '5XX'; */ const appTypes = { STATUS_4XX, - STATUS_5XX + STATUS_5XX, + SET_PRODUCT }; -export { appTypes as default, appTypes, STATUS_4XX, STATUS_5XX }; +export { appTypes as default, appTypes, STATUS_4XX, STATUS_5XX, SET_PRODUCT }; diff --git a/tests/__snapshots__/code.test.js.snap b/tests/__snapshots__/code.test.js.snap index 3d6aacc30..3d1b2795a 100644 --- a/tests/__snapshots__/code.test.js.snap +++ b/tests/__snapshots__/code.test.js.snap @@ -6,6 +6,7 @@ exports[`General code checks should only have specific console.[warn|log|info|er "components/inventoryCard/inventoryCardContext.js:229: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);", "components/inventoryCard/inventoryCardHelpers.js:87: console.warn(\`Warning: Filter "\${id}" not found in "table row" response data.\`, cellData);", "components/inventoryCardSubscriptions/inventoryCardSubscriptionsContext.js:126: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);", + "config/products.js:28: console.warn(\`Product configuration failed to load: \${e.message}\`);", "index.js:12: console.log(\`Emulated appNavClick: \${JSON.stringify({ id, ...rest })}\`);", "redux/common/reduxHelpers.js:282: console.error(\`Error: Property \${prop} does not exist within the passed state.\`, state);", "redux/common/reduxHelpers.js:286: console.warn(\`Warning: Property \${prop} does not exist within the passed initialState.\`, initialState);", diff --git a/tests/__snapshots__/dist.test.js.snap b/tests/__snapshots__/dist.test.js.snap index d1fee1640..aebda190d 100644 --- a/tests/__snapshots__/dist.test.js.snap +++ b/tests/__snapshots__/dist.test.js.snap @@ -4,19 +4,43 @@ exports[`Build distribution should have a predictable ephemeral navigation based [ { "coverage": "TRUE", - "path": "/rhel", + "path": "/openshift-container", "productParameter": [ - "RHEL", + "OpenShift Container Platform", + "OpenShift-metrics", ], }, { - "coverage": "TRUE", - "path": "/openshift-container", + "coverage": "FALSE", + "path": "/OpenShift Container Platform", + "productParameter": [ + "OpenShift Container Platform", + "OpenShift-metrics", + ], + }, + { + "coverage": "FALSE", + "path": "/openshift", + "productParameter": [ + "OpenShift Container Platform", + "OpenShift-metrics", + ], + }, + { + "coverage": "FALSE", + "path": "/OpenShift-metrics", "productParameter": [ "OpenShift Container Platform", "OpenShift-metrics", ], }, + { + "coverage": "FALSE", + "path": "/openshift-dedicated-metrics", + "productParameter": [ + "OpenShift-dedicated-metrics", + ], + }, { "coverage": "TRUE", "path": "/openshift-dedicated", @@ -31,6 +55,20 @@ exports[`Build distribution should have a predictable ephemeral navigation based "rhacs", ], }, + { + "coverage": "TRUE", + "path": "/rhel", + "productParameter": [ + "RHEL", + ], + }, + { + "coverage": "FALSE", + "path": "/insights", + "productParameter": [ + "RHEL", + ], + }, { "coverage": "TRUE", "path": "/rhods", @@ -38,6 +76,13 @@ exports[`Build distribution should have a predictable ephemeral navigation based "rhods", ], }, + { + "coverage": "FALSE", + "path": "/rhosak", + "productParameter": [ + "rhosak", + ], + }, { "coverage": "TRUE", "path": "/streams", @@ -55,12 +100,12 @@ exports[`Build distribution should have a predictable ephemeral navigation based { "coverage": "FALSE", "path": "/optin", - "productParameter": null, + "productParameter": undefined, }, { "coverage": "FALSE", "path": "/", - "productParameter": null, + "productParameter": undefined, }, ] `; diff --git a/yarn.lock b/yarn.lock index de9b8349b..40c82725b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1108,7 +1108,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== @@ -2517,6 +2517,11 @@ resolved "https://registry.yarnpkg.com/@redhat-cloud-services/types/-/types-0.0.5.tgz#69f1a3cfb24238b6aaba58690d208714295d57ea" integrity sha512-K2EgxRwszLzvch2nh72wNm8HOlcp6ljTNzUT3bcSfHRNN0poUHtslfcaJTE9EHbWglIH4rwKEhHkHQJGGtcexw== +"@remix-run/router@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.3.2.tgz#58cd2bd25df2acc16c628e1b6f6150ea6c7455bc" + integrity sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -7464,19 +7469,7 @@ he@^1.1.0, he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" - -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -8241,11 +8234,6 @@ is-yarn-global@^0.4.0: resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.4.0.tgz#714d94453327db9ea98fbf1a0c5f2b88f59ddd5c" integrity sha512-HneQBCrXGBy15QnaDfcn6OLoU8AQPAa0Qn0IeJR/QCo4E8dNZaGGwxpCwWyEBQC5QvFonP8d6t60iGpAHVAfNA== -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -9745,7 +9733,7 @@ logform@^2.3.2, logform@^2.4.0: safe-stable-stringify "^2.3.1" triple-beam "^1.3.0" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -9969,14 +9957,6 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -mini-create-react-context@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" - integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ== - dependencies: - "@babel/runtime" "^7.12.1" - tiny-warning "^1.0.3" - mini-css-extract-plugin@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8" @@ -10918,13 +10898,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -11943,7 +11916,7 @@ react-i18next@^12.0.0: "@babel/runtime" "^7.14.5" html-parse-stringify "^3.0.1" -react-is@^16.12.0, react-is@^16.13.1, react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0: +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -11980,50 +11953,20 @@ react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-router-dom@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363" - integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ== +react-router-dom@6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.8.1.tgz#7e136b67d9866f55999e9a8482c7008e3c575ac9" + integrity sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ== dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.2.1" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-router@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d" - integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.4.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + "@remix-run/router" "1.3.2" + react-router "6.8.1" -react-router@5.3.3: - version "5.3.3" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.3.tgz#8e3841f4089e728cf82a429d92cdcaa5e4a3a288" - integrity sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.4.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" +react-router@6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.8.1.tgz#e362caf93958a747c649be1b47cd505cf28ca63e" + integrity sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg== + dependencies: + "@remix-run/router" "1.3.2" react-scripts@^5.0.1: version "5.0.1" @@ -12400,11 +12343,6 @@ resolve-global@^1.0.0: dependencies: global-dirs "^0.1.1" -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - resolve-url-loader@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz#d50d4ddc746bb10468443167acf800dcd6c3ad57" @@ -13568,16 +13506,6 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== -tiny-invariant@^1.0.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" - integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== - -tiny-warning@^1.0.0, tiny-warning@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - tippy.js@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-5.1.2.tgz#5ac91233c59ab482ef5988cffe6e08bd26528e66" @@ -14051,11 +13979,6 @@ validate-npm-package-name@^5.0.0: dependencies: builtins "^5.0.0" -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"