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"})
-
-
-
- }
- iconPosition="right"
- isInline={true}
- onClick={[Function]}
- variant="link"
- >
- Open
-
-
-
-
@@ -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"})
-`;
-
-exports[`ProductViewMissing Component should render a predictable set of product cards: non-connected 1`] = `
-
-
- t(curiosity-view.title, {"appName":"Subscriptions"})
-
-
-
@@ -315,13 +250,13 @@ exports[`ProductViewMissing Component should render a predictable set of product
headingLevel="h2"
size="lg"
>
- t(curiosity-view.title, {"appName":"Subscriptions","context":"OpenShift Container Platform"})
+ t(curiosity-view.title, {"appName":"Subscriptions","context":"rhosak"})
- t(curiosity-view.description, {"appName":"Subscriptions","context":"OpenShift Container Platform"})
+ t(curiosity-view.description, {"appName":"Subscriptions","context":"rhosak"})
@@ -351,13 +286,13 @@ exports[`ProductViewMissing Component should render a predictable set of product
headingLevel="h2"
size="lg"
>
- t(curiosity-view.title, {"appName":"Subscriptions","context":"OpenShift-dedicated-metrics"})
+ t(curiosity-view.title, {"appName":"Subscriptions","context":"Satellite"})
- t(curiosity-view.description, {"appName":"Subscriptions","context":"OpenShift-dedicated-metrics"})
+ t(curiosity-view.description, {"appName":"Subscriptions","context":"Satellite"})
{
- const useDispatchMock = jest.spyOn(store, 'dispatch');
-
- afterEach(() => {
- useDispatchMock.mockClear();
- });
-
- it('should render a non-connected component', () => {
- useDispatchMock.mockReturnValue(jest.fn());
-
+ it('should render a basic component', () => {
const props = {
availableProductsRedirect: 1
};
const component = shallow();
- expect(component).toMatchSnapshot('non-connected');
- });
-
- it('should render a predictable set of product cards', () => {
- const props = {
- availableProductsRedirect: 1
- };
-
- mockWindowLocation(
- () => {
- const component = shallow();
- expect(component).toMatchSnapshot('non-connected');
- },
- {
- url: 'https://ci.foo.redhat.com/openshift/subscriptions/'
- }
- );
+ expect(component).toMatchSnapshot('basic');
});
it('should redirect when there are limited product cards', async () => {
- const mockDispatch = jest.fn();
- useDispatchMock.mockImplementation(action => action(mockDispatch));
-
+ const mockPush = jest.fn();
const props = {
- availableProductsRedirect: 200
+ availableProductsRedirect: 200,
+ useHistory: () => ({ push: mockPush })
};
await mountHookComponent();
- expect(mockDispatch.mock.calls).toMatchSnapshot('redirect action');
+ expect(mockPush.mock.calls).toMatchSnapshot('redirect action');
});
});
diff --git a/src/components/productView/productView.js b/src/components/productView/productView.js
index 51eaedefa..73fa4a4fc 100644
--- a/src/components/productView/productView.js
+++ b/src/components/productView/productView.js
@@ -1,6 +1,8 @@
-import React from 'react';
+import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
-import { useRouteDetail } from '../../hooks/useRouter';
+import { useMount } from 'react-use';
+import { routerContext } from '../router';
+// import { PageLayout, PageHeader, PageColumns } from '../pageLayout/pageLayout';
import { ProductViewContext } from './productViewContext';
import { PageLayout, PageHeader, PageSection, PageToolbar, PageMessages, PageColumns } from '../pageLayout/pageLayout';
import { GraphCard } from '../graphCard/graphCard';
@@ -13,6 +15,7 @@ import InventoryTabs, { InventoryTab } from '../inventoryTabs/inventoryTabs';
import { InventoryCardSubscriptions } from '../inventoryCardSubscriptions/inventoryCardSubscriptions';
import { RHSM_INTERNAL_PRODUCT_DISPLAY_TYPES as DISPLAY_TYPES } from '../../services/rhsm/rhsmConstants';
import { translate } from '../i18n/i18n';
+import { storeHooks } from '../../redux';
/**
* Display product columns.
@@ -20,11 +23,28 @@ import { translate } from '../i18n/i18n';
* @param {object} props
* @param {Function} props.t
* @param {Function} props.useRouteDetail
+ * @param props.useSelector
* @returns {Node}
*/
-const ProductView = ({ t, useRouteDetail: useAliasRouteDetail }) => {
- const { productParameter: routeProductLabel, productConfig } = useAliasRouteDetail();
+const ProductView = ({ t, useRouteDetail: useAliasRouteDetail, useSelector: useAliasSelector }) => {
+ const { productGroup, productConfig } = useAliasRouteDetail();
+ // const [] = useState();
+ // useEffect(() => {}, []);
+ // const { productGroup, productConfig } = detail || {};
+ // useAliasRouteDetail();
+ // const { productGroup, productConfig } = useAliasSelector(({ view }) => view?.product?.config, {});
+ useMount(() => {
+ console.log('>>>> PRODUCT VIEW MOUNTED', useAliasSelector, productGroup);
+ });
+
+ /**
+ * Render a product with a context provider
+ *
+ * @param {object} config
+ * @returns {React.ReactNode|null}
+ */
+ /*
const renderProduct = config => {
const { initialInventoryFilters, initialSubscriptionsInventoryFilters, productDisplay, productId, viewId } = config;
@@ -83,14 +103,82 @@ const ProductView = ({ t, useRouteDetail: useAliasRouteDetail }) => {
);
};
+ */
+ const renderProduct = useCallback(() => {
+ const updated = config => {
+ console.log('>>>> PRODUCT VIEW', config);
+ const { initialInventoryFilters, initialSubscriptionsInventoryFilters, productDisplay, productId, viewId } =
+ config;
+
+ if (!productId || !viewId) {
+ return null;
+ }
+
+ return (
+
+ {productDisplay !== DISPLAY_TYPES.HOURLY && }
+
+
+
+
+
+
+
+
+ {!helpers.UI_DISABLED_TABLE_HOSTS &&
+ productDisplay !== DISPLAY_TYPES.HOURLY &&
+ productDisplay !== DISPLAY_TYPES.CAPACITY &&
+ initialInventoryFilters && (
+
+
+
+ )}
+ {!helpers.UI_DISABLED_TABLE_INSTANCES &&
+ productDisplay !== DISPLAY_TYPES.DUAL_AXES &&
+ productDisplay !== DISPLAY_TYPES.LEGACY &&
+ productDisplay !== DISPLAY_TYPES.PARTIAL &&
+ initialInventoryFilters && (
+
+
+
+ )}
+ {!helpers.UI_DISABLED_TABLE_SUBSCRIPTIONS && initialSubscriptionsInventoryFilters && (
+
+
+
+ )}
+
+
+
+ );
+ };
+
+ return productConfig?.map(config => updated(config));
+ }, [productConfig, t]);
return (
-
-
- {t(`curiosity-view.title`, { appName: helpers.UI_DISPLAY_NAME, context: routeProductLabel })}
-
- {productConfig.map(config => renderProduct(config))}
-
+ (productGroup && (
+
+
+ {t(`curiosity-view.title`, { appName: helpers.UI_DISPLAY_NAME, context: productGroup })}
+
+ {renderProduct()}
+
+ )) ||
+ null
);
};
@@ -101,7 +189,8 @@ const ProductView = ({ t, useRouteDetail: useAliasRouteDetail }) => {
*/
ProductView.propTypes = {
t: PropTypes.func,
- useRouteDetail: PropTypes.func
+ useRouteDetail: PropTypes.func,
+ useSelector: PropTypes.func
};
/**
@@ -111,7 +200,8 @@ ProductView.propTypes = {
*/
ProductView.defaultProps = {
t: translate,
- useRouteDetail
+ useRouteDetail: routerContext.useRouteDetail,
+ useSelector: storeHooks.reactRedux.useSelector
};
export { ProductView as default, ProductView };
diff --git a/src/components/productView/productViewMissing.js b/src/components/productView/productViewMissing.js
index de5e632f6..195e470db 100644
--- a/src/components/productView/productViewMissing.js
+++ b/src/components/productView/productViewMissing.js
@@ -4,21 +4,10 @@ import { Button, Card, CardBody, CardFooter, CardTitle, Gallery, Title, PageSect
import { ArrowRightIcon } from '@patternfly/react-icons';
import { useMount } from 'react-use';
import { PageLayout, PageHeader } from '../pageLayout/pageLayout';
-import { routerHelpers } from '../router';
-import { routerHooks } from '../../hooks/useRouter';
+import { routerContext } from '../router';
import { helpers } from '../../common';
import { translate } from '../i18n/i18n';
-/**
- * Return a list of available products.
- *
- * @returns {Array}
- */
-const filterAvailableProducts = () => {
- const { configs, allConfigs } = routerHelpers.getRouteConfigByPath();
- return (configs.length && configs) || allConfigs.filter(({ isSearchable }) => isSearchable === true);
-};
-
/**
* Render a missing product view.
*
@@ -26,16 +15,24 @@ const filterAvailableProducts = () => {
* @param {object} props
* @param {number} props.availableProductsRedirect
* @param {Function} props.t
- * @param {Function} props.useHistory
+ * @param {Function} props.useNavigate
+ * @param {Function} props.useRouteDetail
* @returns {Node}
*/
-const ProductViewMissing = ({ availableProductsRedirect, t, useHistory: useAliasHistory }) => {
- const history = useAliasHistory({ isSetAppNav: true });
- const availableProducts = filterAvailableProducts();
+const ProductViewMissing = ({
+ availableProductsRedirect,
+ t,
+ useNavigate: useAliasNavigate,
+ useRouteDetail: useAliasRouteDetail
+}) => {
+ const navigate = useAliasNavigate();
+ const { productConfig, allProductConfigs } = useAliasRouteDetail();
+ const availableProducts = (productConfig?.length && productConfig) || allProductConfigs;
useMount(() => {
- if (availableProducts.length <= availableProductsRedirect) {
- history.push(availableProducts[0].path);
+ if (availableProducts?.length <= availableProductsRedirect) {
+ // navigate(availableProducts[0].productPath);
+ console.log('>>>> MISSING VIEW', navigate);
}
});
@@ -43,40 +40,44 @@ const ProductViewMissing = ({ availableProductsRedirect, t, useHistory: useAlias
* On click, update history.
*
* @event onNavigate
- * @param {string} id
+ * @param {string} path
* @returns {void}
*/
- const onNavigate = id => history.push(id);
+ const onNavigate = path => navigate(path);
return (
{t(`curiosity-view.title`, { appName: helpers.UI_DISPLAY_NAME })}
- {availableProducts.map(product => (
- onNavigate(product.id)}>
+ {availableProducts?.map(({ productGroup, productId, productPath }) => (
+ onNavigate(productPath)}
+ >
{t('curiosity-view.title', {
appName: helpers.UI_DISPLAY_NAME,
- context:
- (Array.isArray(product.pathParameter) && product.pathParameter?.[0]) || product.pathParameter
+ context: productId
})}
{t('curiosity-view.description', {
appName: helpers.UI_DISPLAY_NAME,
- context:
- (Array.isArray(product.productParameter) && product.productParameter?.[0]) ||
- product.productParameter
+ context: productGroup
})}
onNavigate(product.id)}
+ onClick={event => {
+ event.preventDefault();
+ onNavigate(productPath);
+ }}
icon={}
iconPosition="right"
>
@@ -94,23 +95,25 @@ const ProductViewMissing = ({ availableProductsRedirect, t, useHistory: useAlias
/**
* Prop types.
*
- * @type {{useHistory: Function, availableProductsRedirect: number, t: Function}}
+ * @type {{useNavigate: Function, availableProductsRedirect: number, t: Function, useRouteDetail: Function}}
*/
ProductViewMissing.propTypes = {
availableProductsRedirect: PropTypes.number,
t: PropTypes.func,
- useHistory: PropTypes.func
+ useNavigate: PropTypes.func,
+ useRouteDetail: PropTypes.func
};
/**
* Default props.
*
- * @type {{useHistory: Function, availableProductsRedirect: number, t: translate}}
+ * @type {{useNavigate: Function, availableProductsRedirect: number, t: translate, useRouteDetail: Function}}
*/
ProductViewMissing.defaultProps = {
availableProductsRedirect: 4,
t: translate,
- useHistory: routerHooks.useHistory
+ useNavigate: routerContext.useNavigate,
+ useRouteDetail: routerContext.useRouteDetail
};
export { ProductViewMissing as default, ProductViewMissing };
diff --git a/src/components/router/__tests__/__snapshots__/redirect.test.js.snap b/src/components/router/__tests__/__snapshots__/redirect.test.js.snap
deleted file mode 100644
index 2cdfe7bd4..000000000
--- a/src/components/router/__tests__/__snapshots__/redirect.test.js.snap
+++ /dev/null
@@ -1,453 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Redirect Component should handle a forced redirect: forced route 1`] = `
-
- Redirected towards
- /dolor
-
-`;
-
-exports[`Redirect Component should handle a forced redirect: forced route, replace 1`] = `
-[
- [
- "/subscriptions/dolor?dolor=sit",
- ],
-]
-`;
-
-exports[`Redirect Component should handle a redirect with a url: redirect url 1`] = `
-
- Redirected towards
- //lorem/ipsum?dolor=sit
-
-`;
-
-exports[`Redirect Component should handle existing routes: existing route 1`] = `
-{
- "activateOnError": false,
- "aliases": [],
- "component": "productView/productView",
- "default": false,
- "disabled": false,
- "id": "openshift-container",
- "isSearchable": true,
- "path": "*",
- "pathParameter": "OpenShift Container Platform",
- "productConfig": [
- {
- "graphTallyQuery": {
- "granularity": "Daily",
- },
- "initialGraphFilters": [
- {
- "color": "#06c",
- "fill": "#8bc1f7",
- "isOptional": true,
- "metric": "Cores",
- "stroke": "#06c",
- },
- {
- "color": "#06c",
- "fill": "#8bc1f7",
- "isOptional": true,
- "metric": "Sockets",
- "stroke": "#06c",
- },
- {
- "chartType": "threshold",
- "isOptional": true,
- "metric": "Sockets",
- },
- {
- "chartType": "threshold",
- "isOptional": true,
- "metric": "Cores",
- },
- ],
- "initialGraphSettings": {
- "actions": [
- {
- "id": "uom",
- "position": "right",
- },
- {
- "id": "granularity",
- "position": "right",
- },
- ],
- "isCardTitleDescription": true,
- },
- "initialGuestsFilters": [
- {
- "cell": [Function],
- "header": [Function],
- "id": "displayName",
- },
- {
- "cellWidth": 40,
- "id": "inventoryId",
- },
- {
- "cell": [Function],
- "cellWidth": 15,
- "id": "lastSeen",
- },
- ],
- "initialInventoryFilters": [
- {
- "cell": [Function],
- "id": "display_name",
- "isSortable": true,
- },
- {
- "cellWidth": 15,
- "id": "sockets",
- "isOptional": true,
- "isSortable": true,
- "isWrappable": true,
- },
- {
- "cellWidth": 15,
- "id": "cores",
- "isOptional": true,
- "isSortable": true,
- "isWrappable": true,
- },
- {
- "cell": [Function],
- "cellWidth": 20,
- "id": "last_seen",
- "isSortable": true,
- "isWrappable": true,
- },
- ],
- "initialInventorySettings": {},
- "initialOption": "cores",
- "initialSubscriptionsInventoryFilters": [
- {
- "id": "product_name",
- "isSortable": true,
- "isWrappable": true,
- },
- {
- "cellWidth": 15,
- "id": "service_level",
- "isSortable": true,
- "isWrappable": true,
- },
- {
- "cellWidth": 20,
- "id": "quantity",
- "isSortable": true,
- "isWrappable": true,
- },
- {
- "cell": [Function],
- "cellWidth": 15,
- "header": [Function],
- "id": "total_capacity",
- "isSortable": true,
- "isWrappable": true,
- },
- {
- "cell": [Function],
- "cellWidth": 15,
- "id": "next_event_date",
- "isSortable": true,
- "isWrappable": true,
- },
- ],
- "initialToolbarFilters": [
- {
- "id": "sla",
- },
- ],
- "inventoryHostsQuery": {
- "dir": "desc",
- "limit": 100,
- "offset": 0,
- "sort": "last_seen",
- },
- "inventorySubscriptionsQuery": {
- "dir": "desc",
- "limit": 100,
- "offset": 0,
- "sort": "next_event_date",
- },
- "productContextFilterUom": true,
- "productDisplay": "partial",
- "productGroup": "OpenShift Container Platform",
- "productId": "OpenShift Container Platform",
- "productLabel": "OpenShift Container Platform",
- "query": {
- "beginning": "2019-06-20T00:00:00.000Z",
- "ending": "2019-07-20T23:59:59.999Z",
- "uom": "cores",
- },
- "viewId": "viewOpenShift Container Platform",
- },
- {
- "graphTallyQuery": {
- "granularity": "Daily",
- },
- "initialGraphFilters": [
- {
- "color": "#06c",
- "fill": "#8bc1f7",
- "isStacked": false,
- "metric": "Cores",
- "stroke": "#06c",
- },
- ],
- "initialGraphSettings": {
- "actions": [
- {
- "content": [Function],
- },
- {
- "id": "rangedMonthly",
- "position": "right",
- },
- ],
- "isCardTitleDescription": true,
- },
- "initialInventoryFilters": [
- {
- "cell": [Function],
- "id": "display_name",
- "isSortable": true,
- },
- {
- "cell": [Function],
- "cellWidth": 20,
- "id": "core_hours",
- "isSortable": true,
- "isWrappable": true,
- },
- {
- "cell": [Function],
- "cellWidth": 20,
- "id": "last_seen",
- "isSortable": true,
- "isWrappable": true,
- },
- ],
- "initialToolbarFilters": undefined,
- "inventoryHostsQuery": {
- "dir": "desc",
- "limit": 100,
- "offset": 0,
- "sort": "last_seen",
- },
- "productDisplay": "partial",
- "productGroup": "OpenShift-metrics",
- "productId": "OpenShift-metrics",
- "productLabel": "OpenShift-metrics",
- "query": {
- "beginning": "2019-07-01T00:00:00.000Z",
- "ending": "2019-07-31T23:59:59.999Z",
- },
- "viewId": "viewOpenShift-metrics",
- },
- ],
- "productParameter": "OpenShift Container Platform",
- "redirect": null,
- "routeHref": "/openshift-container",
- "viewParameter": "viewOpenShift Container Platform",
-}
-`;
-
-exports[`Redirect Component should handle missing routes: missing route, component 1`] = `
-
- Redirected towards
- /lorem-ipsum
-
-`;
-
-exports[`Redirect Component should render a basic component: basic 1`] = `
-
-`;
diff --git a/src/components/router/__tests__/__snapshots__/router.test.js.snap b/src/components/router/__tests__/__snapshots__/router.test.js.snap
index 05ad6a9ec..d9aa92763 100644
--- a/src/components/router/__tests__/__snapshots__/router.test.js.snap
+++ b/src/components/router/__tests__/__snapshots__/router.test.js.snap
@@ -29,6 +29,20 @@ exports[`Router Component should handle unique route settings: settings 1`] = `
"exact": false,
"path": "/ipsum",
},
+ {
+ "activateOnError": false,
+ "component": "helloWorld",
+ "disabled": true,
+ "exact": false,
+ "path": "/hello",
+ },
+ {
+ "activateOnError": false,
+ "component": "loremDolorHello",
+ "disabled": false,
+ "exact": false,
+ "path": "/loremDolorHello",
+ },
]
}
>
@@ -38,9 +52,13 @@ exports[`Router Component should handle unique route settings: settings 1`] = `
exports[`Router Component should pass route context: context 1`] = `
{
- "pathParameter": "rhods",
- "productParameter": "rhods",
- "viewParameter": "viewrhods",
+ "pathParameter": [
+ "rhods",
+ ],
+ "productParameter": [
+ "rhods",
+ ],
+ "viewParameter": undefined,
}
`;
@@ -62,71 +80,2374 @@ exports[`Router Component should render a basic component: basic 1`] = `
{
"activateOnError": false,
"component": "productView/productView",
+ "default": false,
"disabled": false,
- "exact": true,
- "id": "rhel",
- "path": "/rhel",
+ "path": "/openshift-container",
+ "pathParameter": [
+ "OpenShift Container Platform",
+ "OpenShift-metrics",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "OpenShift Container Platform",
+ "openshift",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isOptional": true,
+ "metric": "Cores",
+ "stroke": "#06c",
+ },
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isOptional": true,
+ "metric": "Sockets",
+ "stroke": "#06c",
+ },
+ {
+ "chartType": "threshold",
+ "isOptional": true,
+ "metric": "Sockets",
+ },
+ {
+ "chartType": "threshold",
+ "isOptional": true,
+ "metric": "Cores",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "id": "uom",
+ "position": "right",
+ },
+ {
+ "id": "granularity",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialGuestsFilters": [
+ {
+ "cell": [Function],
+ "header": [Function],
+ "id": "displayName",
+ },
+ {
+ "cellWidth": 40,
+ "id": "inventoryId",
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "lastSeen",
+ },
+ ],
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "sockets",
+ "isOptional": true,
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "cores",
+ "isOptional": true,
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialInventorySettings": {},
+ "initialOption": "cores",
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 20,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "header": [Function],
+ "id": "total_capacity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "sla",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productContextFilterUom": true,
+ "productDisplay": "partial",
+ "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",
+ },
+ {
+ "aliases": [
+ "OpenShift-metrics",
+ "openshift",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "metric": "Cores",
+ "stroke": "#06c",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "content": [Function],
+ },
+ {
+ "id": "rangedMonthly",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "core_hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": undefined,
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "productDisplay": "partial",
+ "productGroup": "openshift-container",
+ "productId": "OpenShift-metrics",
+ "productLabel": "OpenShift-metrics",
+ "productPath": "openshift-container",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewopenshift-container-OpenShift-metrics",
+ },
+ ],
+ "productParameter": [
+ "OpenShift Container Platform",
+ "OpenShift-metrics",
+ ],
"redirect": null,
},
{
"activateOnError": false,
"component": "productView/productView",
+ "default": false,
"disabled": false,
- "exact": true,
- "id": "openshift-container",
- "path": "/openshift-container",
+ "path": "/OpenShift Container Platform",
+ "pathParameter": [
+ "OpenShift Container Platform",
+ "OpenShift-metrics",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "OpenShift Container Platform",
+ "openshift",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isOptional": true,
+ "metric": "Cores",
+ "stroke": "#06c",
+ },
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isOptional": true,
+ "metric": "Sockets",
+ "stroke": "#06c",
+ },
+ {
+ "chartType": "threshold",
+ "isOptional": true,
+ "metric": "Sockets",
+ },
+ {
+ "chartType": "threshold",
+ "isOptional": true,
+ "metric": "Cores",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "id": "uom",
+ "position": "right",
+ },
+ {
+ "id": "granularity",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialGuestsFilters": [
+ {
+ "cell": [Function],
+ "header": [Function],
+ "id": "displayName",
+ },
+ {
+ "cellWidth": 40,
+ "id": "inventoryId",
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "lastSeen",
+ },
+ ],
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "sockets",
+ "isOptional": true,
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "cores",
+ "isOptional": true,
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialInventorySettings": {},
+ "initialOption": "cores",
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 20,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "header": [Function],
+ "id": "total_capacity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "sla",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productContextFilterUom": true,
+ "productDisplay": "partial",
+ "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",
+ },
+ {
+ "aliases": [
+ "OpenShift-metrics",
+ "openshift",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "metric": "Cores",
+ "stroke": "#06c",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "content": [Function],
+ },
+ {
+ "id": "rangedMonthly",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "core_hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": undefined,
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "productDisplay": "partial",
+ "productGroup": "openshift-container",
+ "productId": "OpenShift-metrics",
+ "productLabel": "OpenShift-metrics",
+ "productPath": "openshift-container",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewopenshift-container-OpenShift-metrics",
+ },
+ ],
+ "productParameter": [
+ "OpenShift Container Platform",
+ "OpenShift-metrics",
+ ],
"redirect": null,
},
{
"activateOnError": false,
"component": "productView/productView",
+ "default": false,
+ "disabled": false,
+ "path": "/openshift",
+ "pathParameter": [
+ "OpenShift Container Platform",
+ "OpenShift-metrics",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "OpenShift Container Platform",
+ "openshift",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isOptional": true,
+ "metric": "Cores",
+ "stroke": "#06c",
+ },
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isOptional": true,
+ "metric": "Sockets",
+ "stroke": "#06c",
+ },
+ {
+ "chartType": "threshold",
+ "isOptional": true,
+ "metric": "Sockets",
+ },
+ {
+ "chartType": "threshold",
+ "isOptional": true,
+ "metric": "Cores",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "id": "uom",
+ "position": "right",
+ },
+ {
+ "id": "granularity",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialGuestsFilters": [
+ {
+ "cell": [Function],
+ "header": [Function],
+ "id": "displayName",
+ },
+ {
+ "cellWidth": 40,
+ "id": "inventoryId",
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "lastSeen",
+ },
+ ],
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "sockets",
+ "isOptional": true,
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "cores",
+ "isOptional": true,
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialInventorySettings": {},
+ "initialOption": "cores",
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 20,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "header": [Function],
+ "id": "total_capacity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "sla",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productContextFilterUom": true,
+ "productDisplay": "partial",
+ "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",
+ },
+ {
+ "aliases": [
+ "OpenShift-metrics",
+ "openshift",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "metric": "Cores",
+ "stroke": "#06c",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "content": [Function],
+ },
+ {
+ "id": "rangedMonthly",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "core_hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": undefined,
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "productDisplay": "partial",
+ "productGroup": "openshift-container",
+ "productId": "OpenShift-metrics",
+ "productLabel": "OpenShift-metrics",
+ "productPath": "openshift-container",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewopenshift-container-OpenShift-metrics",
+ },
+ ],
+ "productParameter": [
+ "OpenShift Container Platform",
+ "OpenShift-metrics",
+ ],
+ "redirect": null,
+ },
+ {
+ "activateOnError": false,
+ "component": "productView/productView",
+ "default": false,
+ "disabled": false,
+ "path": "/OpenShift-metrics",
+ "pathParameter": [
+ "OpenShift Container Platform",
+ "OpenShift-metrics",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "OpenShift Container Platform",
+ "openshift",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isOptional": true,
+ "metric": "Cores",
+ "stroke": "#06c",
+ },
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isOptional": true,
+ "metric": "Sockets",
+ "stroke": "#06c",
+ },
+ {
+ "chartType": "threshold",
+ "isOptional": true,
+ "metric": "Sockets",
+ },
+ {
+ "chartType": "threshold",
+ "isOptional": true,
+ "metric": "Cores",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "id": "uom",
+ "position": "right",
+ },
+ {
+ "id": "granularity",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialGuestsFilters": [
+ {
+ "cell": [Function],
+ "header": [Function],
+ "id": "displayName",
+ },
+ {
+ "cellWidth": 40,
+ "id": "inventoryId",
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "lastSeen",
+ },
+ ],
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "sockets",
+ "isOptional": true,
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "cores",
+ "isOptional": true,
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialInventorySettings": {},
+ "initialOption": "cores",
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 20,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "header": [Function],
+ "id": "total_capacity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "sla",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productContextFilterUom": true,
+ "productDisplay": "partial",
+ "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",
+ },
+ {
+ "aliases": [
+ "OpenShift-metrics",
+ "openshift",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "metric": "Cores",
+ "stroke": "#06c",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "content": [Function],
+ },
+ {
+ "id": "rangedMonthly",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "core_hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": undefined,
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "productDisplay": "partial",
+ "productGroup": "openshift-container",
+ "productId": "OpenShift-metrics",
+ "productLabel": "OpenShift-metrics",
+ "productPath": "openshift-container",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewopenshift-container-OpenShift-metrics",
+ },
+ ],
+ "productParameter": [
+ "OpenShift Container Platform",
+ "OpenShift-metrics",
+ ],
+ "redirect": null,
+ },
+ {
+ "activateOnError": false,
+ "component": "productView/productView",
+ "default": false,
+ "disabled": false,
+ "path": "/openshift-dedicated-metrics",
+ "pathParameter": [
+ "OpenShift-dedicated-metrics",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "openshift-dedicated",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "chartType": "line",
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "metric": "Cores",
+ "stroke": "#06c",
+ "yAxisUseDataSet": true,
+ },
+ {
+ "chartType": "line",
+ "color": "#009596",
+ "fill": "#a2d9d9",
+ "isStacked": false,
+ "metric": "Instance-hours",
+ "stroke": "#009596",
+ "yAxisUseDataSet": true,
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "content": [Function],
+ },
+ {
+ "id": "rangedMonthly",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "core_hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "instance_hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": undefined,
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "productDisplay": "dual-axes",
+ "productGroup": "OpenShift-dedicated-metrics",
+ "productId": "OpenShift-dedicated-metrics",
+ "productLabel": "OpenShift-dedicated-metrics",
+ "productPath": "openshift-dedicated-metrics",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewOpenShift-dedicated-metrics",
+ },
+ ],
+ "productParameter": [
+ "OpenShift-dedicated-metrics",
+ ],
+ "redirect": null,
+ },
+ {
+ "activateOnError": false,
+ "component": "productView/productView",
+ "default": false,
"disabled": false,
- "exact": true,
- "id": "openshift-dedicated",
"path": "/openshift-dedicated",
+ "pathParameter": [
+ "OpenShift-dedicated-metrics",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "openshift-dedicated",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "chartType": "line",
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "metric": "Cores",
+ "stroke": "#06c",
+ "yAxisUseDataSet": true,
+ },
+ {
+ "chartType": "line",
+ "color": "#009596",
+ "fill": "#a2d9d9",
+ "isStacked": false,
+ "metric": "Instance-hours",
+ "stroke": "#009596",
+ "yAxisUseDataSet": true,
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "content": [Function],
+ },
+ {
+ "id": "rangedMonthly",
+ "position": "right",
+ },
+ ],
+ "isCardTitleDescription": true,
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "core_hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "instance_hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": undefined,
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "productDisplay": "dual-axes",
+ "productGroup": "OpenShift-dedicated-metrics",
+ "productId": "OpenShift-dedicated-metrics",
+ "productLabel": "OpenShift-dedicated-metrics",
+ "productPath": "openshift-dedicated-metrics",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewOpenShift-dedicated-metrics",
+ },
+ ],
+ "productParameter": [
+ "OpenShift-dedicated-metrics",
+ ],
"redirect": null,
},
{
"activateOnError": false,
"component": "productView/productView",
+ "default": false,
"disabled": false,
- "exact": true,
- "id": "rhacs",
"path": "/rhacs",
+ "pathParameter": [
+ "rhacs",
+ ],
+ "productConfig": [
+ {
+ "aliases": [],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "chartType": "line",
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "isStandalone": true,
+ "metric": "Cores",
+ "stroke": "#06c",
+ "yAxisChartLabel": [Function],
+ },
+ ],
+ "initialGraphSettings": {
+ "isCardTitleDescription": true,
+ "xAxisChartLabel": [Function],
+ "yAxisTickFormat": [Function],
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "billing_provider",
+ "isSortable": true,
+ "isWrappable": false,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "Cores",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "billing_provider",
+ "isSortable": true,
+ "isWrappable": false,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 10,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "subscription_type",
+ "isSortable": false,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "billing_provider",
+ },
+ {
+ "id": "rangedMonthly",
+ "isSecondary": true,
+ "position": "right",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productDisplay": "hourly",
+ "productGroup": "rhacs",
+ "productId": "rhacs",
+ "productLabel": "rhacs",
+ "productPath": "rhacs",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewrhacs",
+ },
+ ],
+ "productParameter": [
+ "rhacs",
+ ],
+ "redirect": null,
+ },
+ {
+ "activateOnError": false,
+ "component": "productView/productView",
+ "default": false,
+ "disabled": false,
+ "path": "/rhel",
+ "pathParameter": [
+ "RHEL",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "insights",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "metric": "Sockets",
+ "query": {
+ "category": "physical",
+ },
+ "stroke": "#06c",
+ },
+ {
+ "color": "#009596",
+ "fill": "#a2d9d9",
+ "metric": "Sockets",
+ "query": {
+ "category": "virtual",
+ },
+ "stroke": "#009596",
+ },
+ {
+ "color": "#5752d1",
+ "fill": "#b2b0ea",
+ "metric": "Sockets",
+ "query": {
+ "category": "cloud",
+ },
+ "stroke": "#5752d1",
+ },
+ {
+ "color": "#ec7a08",
+ "fill": "#f4b678",
+ "metric": "Sockets",
+ "query": {
+ "category": "hypervisor",
+ },
+ "stroke": "#ec7a08",
+ },
+ {
+ "chartType": "threshold",
+ "metric": "Sockets",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "id": "granularity",
+ "position": "right",
+ },
+ ],
+ "isDisabledLegendClick": true,
+ },
+ "initialGuestsFilters": [
+ {
+ "cell": [Function],
+ "header": [Function],
+ "id": "displayName",
+ },
+ {
+ "cellWidth": 40,
+ "id": "inventoryId",
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "lastSeen",
+ },
+ ],
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "number_of_guests",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "category",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "Sockets",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialInventorySettings": {},
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 10,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 10,
+ "header": [Function],
+ "id": "total_capacity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "sla",
+ },
+ {
+ "id": "usage",
+ "selected": true,
+ },
+ {
+ "id": "category",
+ },
+ {
+ "id": "architecture",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productArchitectures": [
+ "RHEL for ARM",
+ "RHEL for IBM Power",
+ "RHEL for IBM z",
+ "RHEL for x86",
+ ],
+ "productDisplay": "capacity",
+ "productGroup": "RHEL",
+ "productId": "RHEL",
+ "productLabel": "RHEL",
+ "productPath": "rhel",
+ "query": {
+ "beginning": "2019-06-20T00:00:00.000Z",
+ "ending": "2019-07-20T23:59:59.999Z",
+ "uom": "sockets",
+ },
+ "viewId": "viewRHEL",
+ },
+ ],
+ "productParameter": [
+ "RHEL",
+ ],
"redirect": null,
},
{
"activateOnError": false,
"component": "productView/productView",
+ "default": false,
+ "disabled": false,
+ "path": "/insights",
+ "pathParameter": [
+ "RHEL",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "insights",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "metric": "Sockets",
+ "query": {
+ "category": "physical",
+ },
+ "stroke": "#06c",
+ },
+ {
+ "color": "#009596",
+ "fill": "#a2d9d9",
+ "metric": "Sockets",
+ "query": {
+ "category": "virtual",
+ },
+ "stroke": "#009596",
+ },
+ {
+ "color": "#5752d1",
+ "fill": "#b2b0ea",
+ "metric": "Sockets",
+ "query": {
+ "category": "cloud",
+ },
+ "stroke": "#5752d1",
+ },
+ {
+ "color": "#ec7a08",
+ "fill": "#f4b678",
+ "metric": "Sockets",
+ "query": {
+ "category": "hypervisor",
+ },
+ "stroke": "#ec7a08",
+ },
+ {
+ "chartType": "threshold",
+ "metric": "Sockets",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "id": "granularity",
+ "position": "right",
+ },
+ ],
+ "isDisabledLegendClick": true,
+ },
+ "initialGuestsFilters": [
+ {
+ "cell": [Function],
+ "header": [Function],
+ "id": "displayName",
+ },
+ {
+ "cellWidth": 40,
+ "id": "inventoryId",
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "lastSeen",
+ },
+ ],
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "number_of_guests",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "category",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "Sockets",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialInventorySettings": {},
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 10,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 10,
+ "header": [Function],
+ "id": "total_capacity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "sla",
+ },
+ {
+ "id": "usage",
+ "selected": true,
+ },
+ {
+ "id": "category",
+ },
+ {
+ "id": "architecture",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productArchitectures": [
+ "RHEL for ARM",
+ "RHEL for IBM Power",
+ "RHEL for IBM z",
+ "RHEL for x86",
+ ],
+ "productDisplay": "capacity",
+ "productGroup": "RHEL",
+ "productId": "RHEL",
+ "productLabel": "RHEL",
+ "productPath": "rhel",
+ "query": {
+ "beginning": "2019-06-20T00:00:00.000Z",
+ "ending": "2019-07-20T23:59:59.999Z",
+ "uom": "sockets",
+ },
+ "viewId": "viewRHEL",
+ },
+ ],
+ "productParameter": [
+ "RHEL",
+ ],
+ "redirect": null,
+ },
+ {
+ "activateOnError": false,
+ "component": "productView/productView",
+ "default": false,
"disabled": false,
- "exact": true,
- "id": "rhods",
"path": "/rhods",
+ "pathParameter": [
+ "rhods",
+ ],
+ "productConfig": [
+ {
+ "aliases": [],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "chartType": "line",
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "isStandalone": true,
+ "metric": "Cores",
+ "stroke": "#06c",
+ "yAxisChartLabel": [Function],
+ },
+ ],
+ "initialGraphSettings": {
+ "isCardTitleDescription": true,
+ "xAxisChartLabel": [Function],
+ "yAxisTickFormat": [Function],
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "billing_provider",
+ "isSortable": true,
+ "isWrappable": false,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "Cores",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "billing_provider",
+ "isSortable": true,
+ "isWrappable": false,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 10,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "subscription_type",
+ "isSortable": false,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "billing_provider",
+ },
+ {
+ "id": "rangedMonthly",
+ "isSecondary": true,
+ "position": "right",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productDisplay": "hourly",
+ "productGroup": "rhods",
+ "productId": "rhods",
+ "productLabel": "rhods",
+ "productPath": "rhods",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewrhods",
+ },
+ ],
+ "productParameter": [
+ "rhods",
+ ],
+ "redirect": null,
+ },
+ {
+ "activateOnError": false,
+ "component": "productView/productView",
+ "default": false,
+ "disabled": false,
+ "path": "/rhosak",
+ "pathParameter": [
+ "rhosak",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "streams",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "chartType": "line",
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "isStandalone": true,
+ "metric": "Transfer-gibibytes",
+ "stroke": "#06c",
+ "yAxisChartLabel": [Function],
+ },
+ {
+ "chartType": "line",
+ "color": "#5752d1",
+ "fill": "#b2b0ea",
+ "isStacked": false,
+ "isStandalone": true,
+ "metric": "Storage-gibibyte-months",
+ "stroke": "#5752d1",
+ "yAxisChartLabel": [Function],
+ },
+ {
+ "chartType": "line",
+ "color": "#009596",
+ "fill": "#a2d9d9",
+ "isStacked": false,
+ "isStandalone": true,
+ "metric": "Instance-hours",
+ "stroke": "#009596",
+ "yAxisChartLabel": [Function],
+ },
+ ],
+ "initialGraphSettings": {
+ "isCardTitleDescription": true,
+ "xAxisChartLabel": [Function],
+ "yAxisTickFormat": [Function],
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "billing_provider",
+ "isSortable": true,
+ "isWrappable": false,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "header": {
+ "tooltip": [Function],
+ },
+ "id": "Transfer-gibibytes",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "header": {
+ "tooltip": [Function],
+ },
+ "id": "Storage-gibibyte-months",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "Instance-hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "billing_provider",
+ "isSortable": true,
+ "isWrappable": false,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 10,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "subscription_type",
+ "isSortable": false,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "billing_provider",
+ },
+ {
+ "id": "rangedMonthly",
+ "isSecondary": true,
+ "position": "right",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productDisplay": "hourly",
+ "productGroup": "rhosak",
+ "productId": "rhosak",
+ "productLabel": "rhosak",
+ "productPath": "rhosak",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewrhosak",
+ },
+ ],
+ "productParameter": [
+ "rhosak",
+ ],
"redirect": null,
},
{
"activateOnError": false,
"component": "productView/productView",
+ "default": false,
"disabled": false,
- "exact": true,
- "id": "rhosak",
"path": "/streams",
+ "pathParameter": [
+ "rhosak",
+ ],
+ "productConfig": [
+ {
+ "aliases": [
+ "streams",
+ ],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "chartType": "line",
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "isStacked": false,
+ "isStandalone": true,
+ "metric": "Transfer-gibibytes",
+ "stroke": "#06c",
+ "yAxisChartLabel": [Function],
+ },
+ {
+ "chartType": "line",
+ "color": "#5752d1",
+ "fill": "#b2b0ea",
+ "isStacked": false,
+ "isStandalone": true,
+ "metric": "Storage-gibibyte-months",
+ "stroke": "#5752d1",
+ "yAxisChartLabel": [Function],
+ },
+ {
+ "chartType": "line",
+ "color": "#009596",
+ "fill": "#a2d9d9",
+ "isStacked": false,
+ "isStandalone": true,
+ "metric": "Instance-hours",
+ "stroke": "#009596",
+ "yAxisChartLabel": [Function],
+ },
+ ],
+ "initialGraphSettings": {
+ "isCardTitleDescription": true,
+ "xAxisChartLabel": [Function],
+ "yAxisTickFormat": [Function],
+ },
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "billing_provider",
+ "isSortable": true,
+ "isWrappable": false,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "header": {
+ "tooltip": [Function],
+ },
+ "id": "Transfer-gibibytes",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "header": {
+ "tooltip": [Function],
+ },
+ "id": "Storage-gibibyte-months",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "Instance-hours",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialSubscriptionsInventoryFilters": [
+ {
+ "id": "product_name",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "billing_provider",
+ "isSortable": true,
+ "isWrappable": false,
+ },
+ {
+ "cellWidth": 15,
+ "id": "service_level",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cellWidth": 10,
+ "id": "quantity",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "subscription_type",
+ "isSortable": false,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "next_event_date",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialToolbarFilters": [
+ {
+ "id": "billing_provider",
+ },
+ {
+ "id": "rangedMonthly",
+ "isSecondary": true,
+ "position": "right",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productDisplay": "hourly",
+ "productGroup": "rhosak",
+ "productId": "rhosak",
+ "productLabel": "rhosak",
+ "productPath": "rhosak",
+ "query": {
+ "beginning": "2019-07-01T00:00:00.000Z",
+ "ending": "2019-07-31T23:59:59.999Z",
+ },
+ "viewId": "viewrhosak",
+ },
+ ],
+ "productParameter": [
+ "rhosak",
+ ],
"redirect": null,
},
{
"activateOnError": false,
"component": "productView/productView",
+ "default": false,
"disabled": false,
- "exact": true,
- "id": "satellite",
"path": "/satellite",
+ "pathParameter": [
+ "Satellite",
+ ],
+ "productConfig": [
+ {
+ "aliases": [],
+ "graphTallyQuery": {
+ "granularity": "Daily",
+ },
+ "initialGraphFilters": [
+ {
+ "color": "#06c",
+ "fill": "#8bc1f7",
+ "metric": "Sockets",
+ "query": {
+ "category": "physical",
+ },
+ "stroke": "#06c",
+ },
+ {
+ "color": "#009596",
+ "fill": "#a2d9d9",
+ "metric": "Sockets",
+ "query": {
+ "category": "virtual",
+ },
+ "stroke": "#009596",
+ },
+ {
+ "color": "#5752d1",
+ "fill": "#b2b0ea",
+ "metric": "Sockets",
+ "query": {
+ "category": "cloud",
+ },
+ "stroke": "#5752d1",
+ },
+ ],
+ "initialGraphSettings": {
+ "actions": [
+ {
+ "id": "granularity",
+ "position": "right",
+ },
+ ],
+ "isDisabledLegendClick": true,
+ },
+ "initialGuestsFilters": [
+ {
+ "cell": [Function],
+ "header": [Function],
+ "id": "displayName",
+ },
+ {
+ "cellWidth": 40,
+ "id": "inventoryId",
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "lastSeen",
+ },
+ ],
+ "initialInventoryFilters": [
+ {
+ "cell": [Function],
+ "id": "display_name",
+ "isSortable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 20,
+ "id": "measurement_type",
+ "isSortable": true,
+ },
+ {
+ "cellWidth": 15,
+ "id": "sockets",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ {
+ "cell": [Function],
+ "cellWidth": 15,
+ "id": "last_seen",
+ "isSortable": true,
+ "isWrappable": true,
+ },
+ ],
+ "initialInventorySettings": {},
+ "initialToolbarFilters": [
+ {
+ "id": "sla",
+ },
+ {
+ "id": "usage",
+ "selected": true,
+ },
+ {
+ "id": "category",
+ },
+ {
+ "id": "variant",
+ },
+ ],
+ "inventoryHostsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "last_seen",
+ },
+ "inventorySubscriptionsQuery": {
+ "dir": "desc",
+ "limit": 100,
+ "offset": 0,
+ "sort": "next_event_date",
+ },
+ "productDisplay": "partial",
+ "productGroup": "Satellite",
+ "productId": "Satellite",
+ "productLabel": "Satellite",
+ "productPath": "satellite",
+ "productVariants": [
+ "Satellite Capsule",
+ "Satellite Server",
+ ],
+ "query": {
+ "beginning": "2019-06-20T00:00:00.000Z",
+ "ending": "2019-07-20T23:59:59.999Z",
+ },
+ "viewId": "viewSatellite",
+ },
+ ],
+ "productParameter": [
+ "Satellite",
+ ],
"redirect": null,
},
{
"activateOnError": true,
"component": "optinView/optinView",
+ "default": false,
"disabled": false,
- "exact": true,
"id": "optin",
"path": "/optin",
"redirect": null,
@@ -134,11 +2455,11 @@ exports[`Router Component should render a basic component: basic 1`] = `
{
"activateOnError": false,
"component": "productView/productViewMissing",
+ "default": true,
"disabled": false,
- "exact": true,
"id": "missing",
"path": "/",
- "redirect": "/",
+ "redirect": "./",
},
]
}
diff --git a/src/components/router/__tests__/__snapshots__/routerContext.test.js.snap b/src/components/router/__tests__/__snapshots__/routerContext.test.js.snap
index fb94f13f7..35faaf36e 100644
--- a/src/components/router/__tests__/__snapshots__/routerContext.test.js.snap
+++ b/src/components/router/__tests__/__snapshots__/routerContext.test.js.snap
@@ -6,6 +6,49 @@ exports[`RouterContext should apply a hook for return routeDetail: route details
}
`;
+exports[`RouterContext should apply a hook for useHistory: history push 1`] = `
+[
+ [
+ "/dolor/sit",
+ undefined,
+ ],
+ [
+ "/rhel",
+ undefined,
+ ],
+ [
+ "/rhel",
+ undefined,
+ ],
+]
+`;
+
+exports[`RouterContext should apply a hook for useLocation: location 1`] = `
+{
+ "assign": [Function],
+ "hash": "",
+ "host": "localhost",
+ "hostname": "localhost",
+ "href": undefined,
+ "origin": "http://localhost",
+ "pathname": "/",
+ "port": "",
+ "protocol": "http:",
+ "reload": [Function],
+ "replace": [Function],
+ "search": "?lorem=ipsum",
+ "toString": [Function],
+}
+`;
+
+exports[`RouterContext should apply a hook for useRedirect: redirect, replace 1`] = `
+[
+ [
+ "/subscriptions/dolor/sit",
+ ],
+]
+`;
+
exports[`RouterContext should return specific properties: specific properties 1`] = `
{
"DEFAULT_CONTEXT": [
@@ -57,7 +100,12 @@ exports[`RouterContext should return specific properties: specific properties 1`
],
"_threadCount": 0,
},
+ "useHistory": [Function],
+ "useLocation": [Function],
+ "useParams": [Function],
+ "useRedirect": [Function],
"useRouteDetail": [Function],
+ "useRouteMatch": [Function],
"useRouterContext": [Function],
}
`;
diff --git a/src/components/router/__tests__/__snapshots__/routerHelpers.test.js.snap b/src/components/router/__tests__/__snapshots__/routerHelpers.test.js.snap
index b3fb585e1..b8a89cbc0 100644
--- a/src/components/router/__tests__/__snapshots__/routerHelpers.test.js.snap
+++ b/src/components/router/__tests__/__snapshots__/routerHelpers.test.js.snap
@@ -8,30 +8,6 @@ exports[`RouterHelpers should apply a simple pathJoin helper: single forward sla
exports[`RouterHelpers should apply a simple pathJoin helper: triple forward slash 1`] = `"//hello/world/lorem/ipsum/dolor"`;
-exports[`RouterHelpers should handle location search and hash passthrough values: NO search and hash 1`] = `
-{
- "routeHref": "/rhel",
-}
-`;
-
-exports[`RouterHelpers should handle location search and hash passthrough values: hash 1`] = `
-{
- "routeHref": "/rhel#lorem",
-}
-`;
-
-exports[`RouterHelpers should handle location search and hash passthrough values: search 1`] = `
-{
- "routeHref": "/rhel?dolor=sit",
-}
-`;
-
-exports[`RouterHelpers should handle location search and hash passthrough values: search and hash 1`] = `
-{
- "routeHref": "/rhel?dolor=sit#lorem",
-}
-`;
-
exports[`RouterHelpers should return a generated baseName using NO path prefix: app lorem route name 1`] = `"/appName"`;
exports[`RouterHelpers should return a generated baseName using NO path prefix: empty pathPrefix: app base name 1`] = `"/appName"`;
@@ -56,230 +32,113 @@ exports[`RouterHelpers should return a generated basePath: insights, beta app ba
exports[`RouterHelpers should return a generated basePath: insights, beta app lorem route name base path 1`] = `"/beta/insights/"`;
-exports[`RouterHelpers should return an error route: error route 1`] = `
+exports[`RouterHelpers should return parse search parameters into unique key, value pairs: unique pairs 1`] = `
{
- "activateOnError": true,
- "component": "optinView/optinView",
- "disabled": false,
- "exact": true,
- "id": "optin",
- "path": "/optin",
- "redirect": null,
+ "dolor": "sit",
+ "lorem": "hello world",
}
`;
-exports[`RouterHelpers should return default navigation and route details: detail: defaults 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: NO search and hash 1`] = `
{
- "configs": [],
- "configsById": {},
- "firstMatch": undefined,
+ "productId": "RHEL",
}
`;
-exports[`RouterHelpers should return navigation and route details from a path: detail: match specific path 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: detail: alias insights 1`] = `
{
- "id": "rhel",
+ "productId": "RHEL",
}
`;
-exports[`RouterHelpers should return navigation and route details from a path: detail: missing parameters 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: detail: match specific path 1`] = `
{
- "id": undefined,
+ "productId": "RHEL",
}
`;
-exports[`RouterHelpers should return navigation and route details from a path: detail: missing pathName 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: detail: missing parameters 1`] = `
{
- "id": undefined,
+ "productId": undefined,
}
`;
-exports[`RouterHelpers should return navigation and route details from a path: detail: specific product path 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: detail: missing pathName 1`] = `
{
- "id": "rhods",
+ "productId": undefined,
}
`;
-exports[`RouterHelpers should return navigation and route details from a related name: detail: match related name 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: detail: specific navigation ID 1`] = `
{
- "id": "rhods",
+ "productId": "rhods",
}
`;
-exports[`RouterHelpers should return navigation and route details that align to location: detail: match specific path navigation 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: detail: specific product path 1`] = `
{
- "id": "rhel",
+ "productId": "rhods",
}
`;
-exports[`RouterHelpers should return navigation and route details that align to location: detail: missing ID, specific path 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: search 1`] = `
{
- "id": "rhel",
+ "productId": "RHEL",
}
`;
-exports[`RouterHelpers should return navigation and route details that align to location: detail: missing id and pathName and default 1`] = `
+exports[`RouterHelpers should return product configuration details from a url, path, or path-like id, or alias: search and hash 1`] = `
{
- "id": undefined,
-}
-`;
-
-exports[`RouterHelpers should return navigation and route details that align to location: detail: missing id and pathName, default 1`] = `
-{
- "id": "missing",
-}
-`;
-
-exports[`RouterHelpers should return navigation and route details that align to location: detail: missing parameters 1`] = `
-{
- "id": undefined,
-}
-`;
-
-exports[`RouterHelpers should return navigation and route details that align to location: detail: specific navigation ID 1`] = `
-{
- "id": "rhods",
-}
-`;
-
-exports[`RouterHelpers should return navigation and route details that align to location: detail: specific route ID 1`] = `
-{
- "id": "optin",
+ "productId": "RHEL",
}
`;
exports[`RouterHelpers should return specific properties: routerHelpers 1`] = `
{
"appName": "subscriptions",
- "baseName": "/",
- "basePath": "/",
"dynamicBaseName": [Function],
"dynamicBasePath": [Function],
- "generateProductGroups": [Function],
- "generateRoutes": [Function],
- "getErrorRoute": {
+ "errorRoute": {
"activateOnError": true,
"component": "optinView/optinView",
+ "default": false,
"disabled": false,
- "exact": true,
"id": "optin",
"path": "/optin",
"redirect": null,
},
- "getRouteConfig": [Function],
"getRouteConfigByPath": [Function],
"importView": [Function],
+ "parseSearchParams": [Function],
"pathJoin": [Function],
- "platformLandingRedirect": "/",
- "platformModalRedirect": "/?not_entitled=subscriptions",
- "productGroups": {
- "viewOpenShift Container Platform": [
- "OpenShift Container Platform",
- ],
- "viewOpenShift-dedicated-metrics": [
- "OpenShift-dedicated-metrics",
- ],
- "viewOpenShift-metrics": [
- "OpenShift-metrics",
- ],
- "viewRHEL": [
- "RHEL",
- ],
- "viewSatellite": [
- "Satellite",
- ],
- "viewrhacs": [
- "rhacs",
- ],
- "viewrhods": [
- "rhods",
- ],
- "viewrhosak": [
- "rhosak",
- ],
+ "redirectRoute": {
+ "activateOnError": false,
+ "component": "productView/productViewMissing",
+ "default": true,
+ "disabled": false,
+ "id": "missing",
+ "path": "/",
+ "redirect": "./",
},
- "routes": [
- {
- "activateOnError": false,
- "component": "productView/productView",
- "disabled": false,
- "exact": true,
- "id": "rhel",
- "path": "/rhel",
- "redirect": null,
- },
- {
- "activateOnError": false,
- "component": "productView/productView",
- "disabled": false,
- "exact": true,
- "id": "openshift-container",
- "path": "/openshift-container",
- "redirect": null,
- },
- {
- "activateOnError": false,
- "component": "productView/productView",
- "disabled": false,
- "exact": true,
- "id": "openshift-dedicated",
- "path": "/openshift-dedicated",
- "redirect": null,
- },
- {
- "activateOnError": false,
- "component": "productView/productView",
- "disabled": false,
- "exact": true,
- "id": "rhacs",
- "path": "/rhacs",
- "redirect": null,
- },
- {
- "activateOnError": false,
- "component": "productView/productView",
- "disabled": false,
- "exact": true,
- "id": "rhods",
- "path": "/rhods",
- "redirect": null,
- },
- {
- "activateOnError": false,
- "component": "productView/productView",
- "disabled": false,
- "exact": true,
- "id": "rhosak",
- "path": "/streams",
- "redirect": null,
- },
- {
- "activateOnError": false,
- "component": "productView/productView",
- "disabled": false,
- "exact": true,
- "id": "satellite",
- "path": "/satellite",
- "redirect": null,
- },
- {
- "activateOnError": true,
- "component": "optinView/optinView",
- "disabled": false,
- "exact": true,
- "id": "optin",
- "path": "/optin",
- "redirect": null,
- },
- {
- "activateOnError": false,
- "component": "productView/productViewMissing",
- "disabled": false,
- "exact": true,
- "id": "missing",
- "path": "/",
- "redirect": "/",
- },
- ],
}
`;
+
+exports[`RouterHelpers should return specific properties: routerHelpers: routes 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/components/router/__tests__/redirect.test.js b/src/components/router/__tests__/redirect.test.js
deleted file mode 100644
index df07e94a9..000000000
--- a/src/components/router/__tests__/redirect.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { Redirect } from '../redirect';
-import { Router } from '../router';
-
-describe('Redirect Component', () => {
- it('should render a basic component', () => {
- const props = {
- route: '/openshift-dedicated'
- };
- const component = shallow();
- expect(component).toMatchSnapshot('basic');
- });
-
- it('should handle a forced redirect', () => {
- const props = {
- isForced: true,
- route: '/dolor'
- };
-
- mockWindowLocation(
- () => {
- const mockReplace = jest.spyOn(window.location, 'replace').mockImplementation((type, data) => ({ type, data }));
- const component = shallow();
-
- expect(mockReplace.mock.calls).toMatchSnapshot('forced route, replace');
- expect(component).toMatchSnapshot('forced route');
-
- mockReplace.mockClear();
- },
- {
- url: 'http://lorem/subscriptions/ipsum?dolor=sit'
- }
- );
- });
-
- it('should handle a redirect with a url', () => {
- const props = {
- url: '//lorem/ipsum?dolor=sit'
- };
- const component = shallow();
- expect(component).toMatchSnapshot('redirect url');
- });
-
- it('should handle missing routes', () => {
- const props = {
- route: '/lorem-ipsum'
- };
- const component = shallow();
-
- expect(component).toMatchSnapshot('missing route, component');
- });
-
- it('should handle existing routes', () => {
- const props = {
- route: '/openshift-container'
- };
-
- const component = shallow();
- const { routes } = component.find(Router).props();
-
- expect(routes[0]).toMatchSnapshot('existing route');
- });
-});
diff --git a/src/components/router/__tests__/router.test.js b/src/components/router/__tests__/router.test.js
index 2e7a2a14a..30888baae 100644
--- a/src/components/router/__tests__/router.test.js
+++ b/src/components/router/__tests__/router.test.js
@@ -32,6 +32,20 @@ describe('Router Component', () => {
activateOnError: true,
exact: false,
disabled: false
+ },
+ {
+ path: '/hello',
+ component: 'helloWorld',
+ activateOnError: false,
+ exact: false,
+ disabled: true
+ },
+ {
+ path: '/loremDolorHello',
+ component: 'loremDolorHello',
+ activateOnError: false,
+ exact: false,
+ disabled: false
}
]
};
diff --git a/src/components/router/__tests__/routerContext.test.js b/src/components/router/__tests__/routerContext.test.js
index a3c1f9898..6755e61d9 100644
--- a/src/components/router/__tests__/routerContext.test.js
+++ b/src/components/router/__tests__/routerContext.test.js
@@ -1,4 +1,4 @@
-import { context, useRouteDetail } from '../routerContext';
+import { context, useHistory, useLocation, useRedirect, useRouteDetail } from '../routerContext';
describe('RouterContext', () => {
it('should return specific properties', () => {
@@ -16,4 +16,39 @@ describe('RouterContext', () => {
const { result } = shallowHook(() => useRouteDetail({ useRouterContext: mockUseRouterContext }));
expect(result).toMatchSnapshot('route details');
});
+
+ it('should apply a hook for useHistory', () => {
+ const mockHistoryPush = jest.fn();
+ const { result: mockUseHistory } = shallowHook(() =>
+ useHistory({
+ useHistory: () => ({ push: mockHistoryPush })
+ })
+ );
+
+ mockUseHistory.push('/dolor/sit');
+ mockUseHistory.push('rhel');
+ mockUseHistory.push('insights');
+
+ expect(mockHistoryPush.mock.calls).toMatchSnapshot('history push');
+ });
+
+ it('should apply a hook for useLocation', async () => {
+ const mockLocation = {
+ search: '?lorem=ipsum'
+ };
+
+ const { result: mockUseLocation } = await shallowHook(() => useLocation({ useLocation: () => mockLocation }));
+ expect(mockUseLocation).toMatchSnapshot('location');
+ });
+
+ it('should apply a hook for useRedirect', async () => {
+ const mockReplace = jest.fn();
+ const mockLocation = {
+ replace: mockReplace
+ };
+
+ const { result: mockUseRedirect } = await shallowHook(() => useRedirect({ useLocation: () => mockLocation }));
+ mockUseRedirect('/dolor/sit');
+ expect(mockReplace.mock.calls).toMatchSnapshot('redirect, replace');
+ });
});
diff --git a/src/components/router/__tests__/routerHelpers.test.js b/src/components/router/__tests__/routerHelpers.test.js
index 0a400d09b..7112c4844 100644
--- a/src/components/router/__tests__/routerHelpers.test.js
+++ b/src/components/router/__tests__/routerHelpers.test.js
@@ -2,18 +2,16 @@ import {
routerHelpers,
dynamicBaseName,
dynamicBasePath,
- getErrorRoute,
- getRouteConfig,
getRouteConfigByPath,
+ parseSearchParams,
pathJoin
} from '../routerHelpers';
-import { routes } from '../../../config/routes';
describe('RouterHelpers', () => {
it('should return specific properties', () => {
- const { routesConfig, ...helpers } = routerHelpers;
- expect(routesConfig).toBeDefined();
- expect({ ...helpers }).toMatchSnapshot('routerHelpers');
+ const { routes, ...rest } = routerHelpers;
+ expect(routes.map(value => value.path)).toMatchSnapshot('routerHelpers: routes');
+ expect(rest).toMatchSnapshot('routerHelpers');
});
it('should return a generated baseName using NO path prefix', () => {
@@ -117,122 +115,46 @@ describe('RouterHelpers', () => {
);
});
- it('should return an error route', () => {
- expect(getErrorRoute).toMatchSnapshot('error route');
- });
-
- it('should return navigation and route details that align to location', () => {
- expect({
- id: getRouteConfig({ id: 'optin' })?.id
- }).toMatchSnapshot('detail: specific route ID');
-
+ it('should return product configuration details from a url, path, or path-like id, or alias', () => {
expect({
- id: getRouteConfig({ id: 'rhods' })?.id
+ productId: getRouteConfigByPath({ pathName: 'rhods' }).firstMatch?.productId
}).toMatchSnapshot('detail: specific navigation ID');
expect({
- id: getRouteConfig({ pathName: '/rhel' })?.id
- }).toMatchSnapshot('detail: match specific path navigation');
-
- expect({
- id: getRouteConfig({ id: 'lorem-missing', pathName: '/rhel' })?.id
- }).toMatchSnapshot('detail: missing ID, specific path');
-
- expect({
- id: getRouteConfig({ id: 'lorem', pathName: '/lorem-ipsum-missing', returnDefault: true })?.id
- }).toMatchSnapshot('detail: missing id and pathName, default');
-
- expect({
- id: getRouteConfig({ id: 'lorem', pathName: '/lorem-ipsum-missing', returnDefault: false })?.id
- }).toMatchSnapshot('detail: missing id and pathName and default');
+ productId: getRouteConfigByPath({ pathName: 'insights' }).firstMatch?.productId
+ }).toMatchSnapshot('detail: alias insights');
expect({
- id: getRouteConfig({})?.id
- }).toMatchSnapshot('detail: missing parameters');
- });
-
- it('should return default navigation and route details', () => {
- mockWindowLocation(
- () => {
- const { allConfigs, allConfigsById, ...matchingConfigs } = getRouteConfigByPath();
-
- expect(allConfigs.length).toBe(routes.length);
- expect(Object.entries(allConfigsById).length).toBe(routes.length);
- expect(matchingConfigs).toMatchSnapshot('detail: defaults');
- },
- {
- url: 'https://ci.foo.redhat.com/loremIpsum/dolorSit/'
- }
- );
- });
-
- it('should return navigation and route details from a path', () => {
- expect({
- id: getRouteConfigByPath({ pathName: '/rhel' }).firstMatch?.id
+ productId: getRouteConfigByPath({ pathName: '/rhel' }).firstMatch?.productId
}).toMatchSnapshot('detail: match specific path');
expect({
- id: getRouteConfigByPath({ pathName: '/rhods' }).firstMatch?.id
+ productId: getRouteConfigByPath({ pathName: '/lorem-ipsum/RHODS' }).firstMatch?.productId
}).toMatchSnapshot('detail: specific product path');
expect({
- id: getRouteConfigByPath({ pathName: '/lorem-ipsum-missing' }).firstMatch?.id
+ productId: getRouteConfigByPath({ pathName: '/lorem-ipsum-missing' }).firstMatch?.productId
}).toMatchSnapshot('detail: missing pathName');
expect({
- id: getRouteConfigByPath({}).firstMatch?.id
+ productId: getRouteConfigByPath({}).firstMatch?.productId
}).toMatchSnapshot('detail: missing parameters');
- });
- it('should return navigation and route details from a related name', () => {
expect({
- id: getRouteConfigByPath({ pathName: '/lorem-ipsum/RHODS' }).firstMatch?.id
- }).toMatchSnapshot('detail: match related name');
- });
-
- it('should handle location search and hash passthrough values', () => {
- mockWindowLocation(
- () => {
- expect({
- routeHref: getRouteConfig({ pathName: '/rhel' }).routeHref
- }).toMatchSnapshot('NO search and hash');
- },
- {
- url: 'https://ci.foo.redhat.com/subscriptions/rhel'
- }
- );
+ productId: getRouteConfigByPath({ pathName: 'https://ci.foo.redhat.com/subscriptions/rhel' }).firstMatch
+ ?.productId
+ }).toMatchSnapshot('NO search and hash');
- mockWindowLocation(
- () => {
- expect({
- routeHref: getRouteConfig({ pathName: '/rhel' }).routeHref
- }).toMatchSnapshot('search');
- },
- {
- url: 'https://ci.foo.redhat.com/subscriptions/rhel?dolor=sit'
- }
- );
+ expect({
+ productId: getRouteConfigByPath({ pathName: '/rhel?dolor=sit' }).firstMatch?.productId
+ }).toMatchSnapshot('search');
- mockWindowLocation(
- () => {
- expect({
- routeHref: getRouteConfig({ pathName: '/rhel' }).routeHref
- }).toMatchSnapshot('hash');
- },
- {
- url: 'https://ci.foo.redhat.com/subscriptions/rhel#lorem'
- }
- );
+ expect({
+ productId: getRouteConfigByPath({ pathName: '/subscriptions/rhel?dolor=sit#lorem' }).firstMatch?.productId
+ }).toMatchSnapshot('search and hash');
+ });
- mockWindowLocation(
- () => {
- expect({
- routeHref: getRouteConfig({ pathName: '/rhel' }).routeHref
- }).toMatchSnapshot('search and hash');
- },
- {
- url: 'https://ci.foo.redhat.com/subscriptions/rhel?dolor=sit#lorem'
- }
- );
+ it('should return parse search parameters into unique key, value pairs', () => {
+ expect(parseSearchParams('?lorem=ipsum&dolor=sit&lorem=hello%20world')).toMatchSnapshot('unique pairs');
});
});
diff --git a/src/components/router/index.js b/src/components/router/index.js
index 493436635..f525972b5 100644
--- a/src/components/router/index.js
+++ b/src/components/router/index.js
@@ -1,5 +1,5 @@
-import { Redirect } from './redirect';
import { Router } from './router';
+import { context as routerContext } from './routerContext';
import { routerHelpers } from './routerHelpers';
-export { Redirect, Router, routerHelpers };
+export { Router, routerContext, routerHelpers };
diff --git a/src/components/router/redirect.js b/src/components/router/redirect.js
deleted file mode 100644
index 9bae41ec2..000000000
--- a/src/components/router/redirect.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Router } from './router';
-import { pathJoin, routerHelpers } from './routerHelpers';
-import { helpers } from '../../common';
-/**
- * A routing redirect.
- *
- * @param {object} props
- * @param {boolean} props.isForced
- * @param {string} props.route
- * @param {string} props.routes
- * @param {string} props.url
- * @returns {Node}
- */
-const Redirect = ({ isForced, route, routes, url }) => {
- const baseName = routerHelpers.dynamicBaseName();
-
- /**
- * Bypass router, force the location.
- */
- const forceNavigation = () => {
- const { hash = '', search = '' } = window.location;
- const forcePath = url || (route && `${pathJoin(baseName, route)}${search}${hash}`);
-
- window.location.replace(forcePath);
- };
-
- const { path: matchedRoutePath, ...matchedRoute } = routerHelpers.getRouteConfig({ pathName: route, id: route });
-
- if (!isForced && matchedRoutePath) {
- return ;
- }
-
- forceNavigation();
-
- return (helpers.TEST_MODE && Redirected towards {url || route}) || null;
-};
-
-/**
- * Prop types.
- *
- * @type {{isRedirect: boolean, route: string, routes: Array, isReplace: boolean, url: string,
- * isForced: boolean}}
- */
-Redirect.propTypes = {
- isForced: PropTypes.bool,
- route: PropTypes.string,
- routes: PropTypes.array,
- url: PropTypes.string
-};
-
-/**
- * Default props.
- *
- * @type {{isRedirect: boolean, route: string, routes: Array, isReplace: boolean, url: string,
- * isForced: boolean}}
- */
-Redirect.defaultProps = {
- isForced: false,
- route: null,
- routes: routerHelpers.routes,
- url: null
-};
-
-export { Redirect as default, Redirect };
diff --git a/src/components/router/router.js b/src/components/router/router.js
index 739568b10..9b4e22a86 100644
--- a/src/components/router/router.js
+++ b/src/components/router/router.js
@@ -1,93 +1,42 @@
-/* eslint-disable react/jsx-no-constructed-context-values */
-import React, { useState } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
-import { Redirect as ReactRouterDomRedirect, Route, Switch } from 'react-router-dom';
-import { useMount } from 'react-use';
-import { RouterContext } from './routerContext';
+import { Navigate, Routes, Route } from 'react-router-dom';
+import { useSetRouteDetail } from './routerContext';
import { routerHelpers } from './routerHelpers';
import { Loader } from '../loader/loader';
+import { helpers } from '../../common';
+// ToDo: consider moving the filter for disabled routes towards routerHelpers
/**
- * Load routes.
+ * Create and load routes.
*
* @param {object} props
+ * @param {object} props.redirectRoute
* @param {Array} props.routes
- * @returns {Node}
+ * @returns {React.ReactNode}
*/
-const Router = ({ routes } = {}) => {
- const [updatedRoutes, setUpdatedRoutes] = useState([]);
- const [redirectDefault, setRedirectDefault] = useState(null);
+const Router = ({ redirectRoute, routes } = {}) => {
+ // this bypasses react router all together, its like we don't even need a router
+ useSetRouteDetail();
+ // const params = useParams();
+ // const location = useLocation();
+ // console.log('>>>>> ROUTER PARAMS', params);
- /**
- * Initialize routes.
- */
- useMount(async () => {
- const activateOnErrorRoute = routes.find(route => route.activateOnError === true);
-
- const results = await Promise.all(
- routes.map(async item => {
- if (item.disabled) {
- return null;
- }
-
- const View = await routerHelpers.importView(item.component);
-
- return (
- {
- const routeConfig = item.id && routerHelpers.getRouteConfig({ id: item.id });
- const { URLSearchParams, decodeURIComponent } = window;
- const parsedSearch = {};
-
- [
- ...new Set(
- [...new URLSearchParams(decodeURIComponent(location.search))].map(
- ([param, value]) => `${param}~${value}`
- )
- )
- ].forEach(v => {
- const [param, value] = v.split('~');
- parsedSearch[param] = value;
- });
-
- const updatedLocation = {
- ...location,
- parsedSearch
- };
-
- const routeDetail = {
- baseName: routerHelpers.dynamicBaseName(),
- errorRoute: activateOnErrorRoute,
- routes,
- routeItem: { ...item },
- ...routeConfig
- };
-
- return (
-
-
-
- );
- }}
- />
- );
- })
- );
-
- setUpdatedRoutes(results);
- setRedirectDefault(routes.find(({ disabled, redirect }) => !disabled && redirect) ?? null);
- });
+ const updatedRoutes = routes
+ .filter(item => !item.disabled)
+ .map(item => {
+ const View = routerHelpers.importView(item.component);
+ return } />;
+ });
return (
}>
-
+
{updatedRoutes}
- {redirectDefault && }
-
+ {redirectRoute && (
+ } />
+ )}
+
);
};
@@ -95,20 +44,18 @@ const Router = ({ routes } = {}) => {
/**
* Prop types.
*
- * @type {{routes: Array}}
+ * @type {{routes: Array, redirectRoute: object}}
*/
Router.propTypes = {
+ redirectRoute: PropTypes.shape({
+ path: PropTypes.string.isRequired,
+ redirect: PropTypes.string.isRequired
+ }),
routes: PropTypes.arrayOf(
PropTypes.shape({
- activateOnError: PropTypes.bool,
component: PropTypes.string.isRequired,
disabled: PropTypes.bool,
- exact: PropTypes.bool,
- id: PropTypes.string,
- path: PropTypes.string.isRequired,
- redirect: PropTypes.string,
- render: PropTypes.bool,
- strict: PropTypes.bool
+ path: PropTypes.string.isRequired
})
)
};
@@ -116,9 +63,10 @@ Router.propTypes = {
/**
* Default props.
*
- * @type {{routes: Array}}
+ * @type {{routes: Array, redirectRoute: object}}
*/
Router.defaultProps = {
+ redirectRoute: routerHelpers.redirectRoute,
routes: routerHelpers.routes
};
diff --git a/src/components/router/routerContext.js b/src/components/router/routerContext.js
index 530c5063e..18818c01c 100644
--- a/src/components/router/routerContext.js
+++ b/src/components/router/routerContext.js
@@ -1,42 +1,546 @@
-import React, { useContext } from 'react';
+import { useCallback, useEffect, useState } from 'react';
+// import _memoize from 'lodash/memoize';
+// import { useShallowCompareEffect } from 'react-use';
+import {
+ useLocation as useLocationRRD,
+ useNavigate as useRRDNavigate,
+ // useParams,
+ // useParams as useRRDParams,
+ useSearchParams as useRRDSearchParams
+} from 'react-router-dom';
+import { routerHelpers } from './routerHelpers';
import { helpers } from '../../common/helpers';
+import { storeHooks, reduxTypes } from '../../redux';
/**
- * Route context.
+ * ToDo: Review react-router-dom useParams once v6 updates are in env
+ * During implementation testing react-router-dom "useParams" was helping spawn multiple
+ * component refreshes so we rolled our own.
*
- * @type {React.Context<{}>}
+ * We were unable to tell if this was a combination of
+ * using the proxy combined with v5 of router. The most noticeable double (to sometimes triple)
+ * refresh is jumping between OpenShift Subs and RHEL Subs, unclear if there's some
+ * special left navigation at play here, or simply the lazy load of the component associated with
+ * the path.
+ *
+ * On a personal note...
+ * react-router has always been between the category of "close enough" and "[roll your own router or ... not]"...
+ * this new version doesn't do itself any favors, it tries to do more and ends up just
+ * offsetting some of the odd original loading issues we had. The current goal is to just
+ * get something working close enough.
*/
-const DEFAULT_CONTEXT = [
- { routeDetail: { baseName: null, errorRoute: null, routes: [], routeItem: {} } },
- helpers.noop
-];
+/**
+ * We ignore react router doms useParams.
+ *
+ * @returns {{productPath: string}}
+ */
+/*
+const useParams = () => {
+ const productPath = routerHelpers.dynamicProductParameter();
+ console.log('>>>> run set params', productPath);
+ return { productPath };
+};
+*/
+/* using the outside cache busts the reload...
+const testParamsCache = {};
+const useParams = () => {
+ // const productPath = routerHelpers.dynamicProductParameter();
+ // console.log('>>>> run set params', productPath);
+ // return { productPath };
+ const [params, setParams] = useState({});
+ const productPath = routerHelpers.dynamicProductParameter();
-const RouterContext = React.createContext(DEFAULT_CONTEXT);
+ useEffect(() => {
+ if (testParamsCache.productPath !== productPath) {
+ console.log('>>>> run set params', testParamsCache.productPath, productPath);
+ setParams({ productPath });
+ testParamsCache.productPath = productPath;
+ }
+ }, [productPath]);
+
+ return params;
+};
+*/
+const useParams = () => {
+ // const productPath = routerHelpers.dynamicProductParameter();
+ // console.log('>>>> run set params', productPath);
+ // return { productPath };
+ const [params, setParams] = useState({});
+ const productPath = routerHelpers.dynamicProductParameter();
+
+ useEffect(() => {
+ if (productPath && params.productPath !== productPath) {
+ console.log('>>>> run set params', params.productPath, productPath);
+ setParams({ productPath });
+ }
+ }, [params.productPath, productPath]);
+
+ return params;
+};
+
+/*
+const useParams = ({ useParams: useAliasParams = useRRDParams } = {}) => {
+ const { productPath } = useAliasParams();
+ // return { productPath };
+ /*
+ const { productPath } = useAliasParams();
+ const [updatedProductPath, setUpdatedProductPath] = useState(productPath);
+ console.log('>>>> run set params', productPath);
+
+ useEffect(() => {
+ if (productPath !== updatedProductPath) {
+ console.log('>>>> UPDATE SET PARAMS', productPath);
+ setUpdatedProductPath(productPath);
+ }
+ }, [productPath, updatedProductPath]);
+
+ return { productPath: updatedProductPath };
+ * /
+
+ console.log('>>>> run params', productPath);
+
+ return useMemo(() => {
+ console.log('>>>> SET PARAMS', productPath);
+ return { productPath };
+ }, [productPath]);
+
+ /*
+ useShallowCompareEffect(() => {
+ // useShallowCompare
+ // useDeepCompare
+ // if (!_isEqual(updatedParams, params)) {
+ console.log('>>>> UPDATE SET PARAMS', params);
+ setUpdatedParams(params);
+ // }
+ }, [params]);
+ * /
+};
+ */
/**
- * Get an updated router context.
+ * Combine react-router-dom useLocation with actual window location.
+ * Focused on exposing replace and href.
*
- * @returns {React.Context<{}>}
+ * @param {Function} useLocation
+ * @returns {{search, replace: Function, href, hash}}
*/
-const useRouterContext = () => useContext(RouterContext);
+const useLocation = ({ useLocation: useAliasLocation = useLocationRRD } = {}) => {
+ const location = useAliasLocation();
+ const { location: windowLocation } = window;
+ const [updatedLocation, setUpdatedLocation] = useState({});
+
+ useEffect(() => {
+ const _id = helpers.generateHash(windowLocation);
+ if (updatedLocation?._id !== _id) {
+ console.log('>>>> set use location', _id);
+ setUpdatedLocation({
+ ...location,
+ ...windowLocation,
+ _id,
+ replace: path => windowLocation.replace(path),
+ hash: location?.hash || '',
+ set href(path) {
+ windowLocation.href = path;
+ },
+ search: location?.search || ''
+ });
+ }
+ }, [location, updatedLocation?._id, windowLocation]);
+
+ return updatedLocation;
+
+ /*
+ const { location: windowLocation } = window;
+ console.log('>>>> set use location', useAliasLocation.toString());
+ return useMemo(
+ () => ({
+ ...windowLocation,
+ replace: path => windowLocation.replace(path),
+ set href(path) {
+ windowLocation.href = path;
+ }
+ }),
+ [windowLocation]
+ );
+ */
+ /*
+ return useMemo(
+ () => ({
+ ...location,
+ ...windowLocation,
+ replace: path => windowLocation.replace(path),
+ hash: location?.hash || '',
+ set href(path) {
+ windowLocation.href = path;
+ },
+ search: location?.search || ''
+ }),
+ [location, windowLocation]
+ );
+ */
+};
+
+/**
+ * Return a callback for redirecting, and replacing, towards a new path, or url.
+ *
+ * @callback redirect
+ * @param {object} options
+ * @param {Function} options.useLocation
+ * @returns {(function(*): void)|*}
+ */
+const useRedirect = ({ useLocation: useAliasLocation = useLocation } = {}) => {
+ const { hash = '', search = '', ...location } = useAliasLocation();
+
+ /**
+ * redirect
+ *
+ * @param {string} route
+ * @returns {void}
+ */
+ return useCallback(
+ (route, { isReplace = true } = {}) => {
+ console.log('>>> use redirect fired', route);
+ const baseName = routerHelpers.dynamicBaseName();
+ let isUrl;
+
+ try {
+ isUrl = !!new URL(route);
+ } catch (e) {
+ isUrl = false;
+ }
+
+ const updatedRoute = (isUrl && route) || `${routerHelpers.pathJoin(baseName, route)}${search}${hash}`;
+
+ if (isReplace) {
+ location.replace(updatedRoute);
+ return;
+ }
+
+ location.href = updatedRoute;
+ },
+ [hash, location, search]
+ );
+};
+
+const useSetRouteDetail = ({
+ useParams: useAliasParams = useParams,
+ useSelector: useAliasSelector = storeHooks.reactRedux.useSelectors,
+ useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch
+} = {}) => {
+ const dispatch = useAliasDispatch();
+ const { productPath } = useAliasParams();
+ const [updatedPath] = useAliasSelector([({ view }) => view?.product?.config]);
+
+ useEffect(() => {
+ if (productPath && updatedPath !== productPath) {
+ dispatch({
+ type: reduxTypes.app.SET_PRODUCT,
+ config: productPath
+ });
+ }
+ }, [updatedPath, dispatch, productPath]);
+
+ return updatedPath;
+};
/**
* Get a route detail from router context.
*
* @param {object} options
- * @param {Function} options.useRouterContext
- * @returns {{routes: Array, routeItem: object, baseName: string, errorRoute: object}}
+ * @param {Function} options.useSelector
+ * @returns {{baseName: string, errorRoute: object}}
*/
-const useRouteDetail = ({ useRouterContext: useAliasRouterContext = useRouterContext } = {}) => {
- const { routeDetail } = useAliasRouterContext();
- return routeDetail;
+const useRouteDetail = ({ useSelector: useAliasSelector = storeHooks.reactRedux.useSelectors } = {}) => {
+ const [productPath] = useAliasSelector([({ view }) => view?.product?.config]);
+ const [detail, setDetail] = useState({});
+ console.log('>>> use route detail', productPath);
+
+ useEffect(() => {
+ if (productPath && detail?._passed !== productPath) {
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log('>>> SET ROUTE DETAIL', firstMatch?.productGroup, detail?._passed, productPath);
+ const updateDetail = {
+ _passed: productPath,
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || [],
+ productPath
+ };
+ setDetail(updateDetail);
+ }
+ }, [detail?._passed, productPath]);
+
+ return detail;
+
+ /* 3x same
+ const [productPath] = useAliasSelector([({ view }) => view?.product?.config]);
+
+ return useMemo(() => {
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log('>>> SET ROUTE DETAIL', firstMatch?.productGroup, firstMatch?.productId);
+ return {
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || [],
+ productPath
+ };
+ }, [productPath]);
+ */
+
+ /* 3x, possibly more due to set state
+ const [productPath] = useAliasSelector([({ view }) => view?.product?.config]);
+ const [detail, setDetail] = useState({});
+ console.log('>>> use route detail', productPath);
+
+ useEffect(() => {
+ if (productPath && detail?._passed !== productPath) {
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log('>>> SET ROUTE DETAIL', firstMatch?.productGroup, detail?._passed, productPath);
+ const updateDetail = {
+ _passed: productPath,
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || [],
+ productPath
+ };
+ setDetail(updateDetail);
+ }
+ }, [detail?._passed, productPath]);
+
+ return detail;
+ */
+ /* same 3x redraw
+ const [productPath] = useAliasSelector([({ view }) => view?.product?.config]);
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log('>>> SET ROUTE DETAIL', firstMatch?.productGroup, firstMatch?.productId);
+ const [detail] = useState({
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || [],
+ productPath
+ });
+
+ return detail;
+ */
+ /* same 3x redraw
+ const [productPath] = useAliasSelector([({ view }) => view?.product?.config]);
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log('>>> SET ROUTE DETAIL', firstMatch?.productGroup, firstMatch?.productId);
+ const detail = {
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || [],
+ productPath
+ };
+
+ return { ...detail };
+ */
+ /*
+ const [detail, setDetail] = useState({});
+ console.log('>>> use route detail', productPath);
+
+ useEffect(() => {
+ if (productPath && detail?.productPath !== productPath) {
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log('>>> SET ROUTE DETAIL', firstMatch?.productGroup, firstMatch?.productId);
+ const updateDetail = {
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || [],
+ productPath
+ };
+ setDetail(updateDetail);
+ }
+ }, [detail?.productPath, productPath]);
+ */
+};
+
+const useRouteDetailWorkingish = ({
+ useParams: useAliasParams = useParams,
+ useSelector: useAliasSelector = storeHooks.reactRedux.useSelectors,
+ useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch
+} = {}) => {
+ const dispatch = useAliasDispatch();
+ const { productPath } = useAliasParams();
+ const [detail = {}] = useAliasSelector([({ view }) => view?.product?.config]);
+ // const [detail, setDetail] = useState();
+ console.log('>>> use route detail', productPath);
+
+ useEffect(() => {
+ if (productPath && detail?.productPath !== productPath) {
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log(
+ '>>> SET ROUTE DETAIL',
+ `det.prodPath=${detail?.productPath}`,
+ `prodPath=${productPath}`,
+ configs.length,
+ firstMatch?.productGroup
+ );
+ const updateDetail = {
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || [],
+ productPath
+ };
+ // setDetail(updateDetail);
+ dispatch({
+ type: reduxTypes.app.SET_PRODUCT,
+ config: updateDetail
+ });
+ }
+ }, [detail?.productPath, dispatch, productPath]);
+
+ return detail;
+
+ /*
+ const doIt = _memoize(pp => {
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: pp });
+ console.log('>>> SET ROUTE DETAIL', pp, configs.length, firstMatch?.productGroup);
+ return {
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || []
+ };
+ });
+
+ return doIt(productPath);
+ */
+ /*
+ return useMemo(() => {
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log('>>> SET ROUTE DETAIL', productPath, configs.length, firstMatch?.productGroup);
+ return {
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || []
+ };
+ }, [productPath]);
+ */
+ /*
+ const { productPath } = useAliasParams();
+ const { allConfigs, configs, firstMatch } = routerHelpers.getRouteConfigByPath({ pathName: productPath });
+ console.log('>>> USE ROUTE DETAIL', productPath, configs.length, firstMatch?.productGroup);
+
+ return {
+ allProductConfigs: allConfigs,
+ firstMatch,
+ errorRoute: routerHelpers.errorRoute,
+ productGroup: firstMatch?.productGroup,
+ productConfig: (configs?.length && configs) || []
+ };
+ */
+};
+
+/**
+ * useNavigate wrapper, apply application config context routing
+ *
+ * @param {object} options
+ * @param {Function} options.useLocation
+ * @param {Function} options.useNavigate
+ * @returns {Function}
+ */
+const useNavigate = ({
+ useLocation: useAliasLocation = useLocation,
+ useNavigate: useAliasNavigate = useRRDNavigate
+} = {}) => {
+ const { search, hash } = useAliasLocation();
+ const navigate = useAliasNavigate();
+
+ return useCallback(
+ (pathLocation, options) => {
+ const pathName = (typeof pathLocation === 'string' && pathLocation) || pathLocation?.pathname;
+ const { firstMatch } = routerHelpers.getRouteConfigByPath({ pathName });
+
+ return navigate(
+ (firstMatch?.productPath && `${routerHelpers.pathJoin('.', firstMatch?.productPath)}${search}${hash}`) ||
+ (pathName && `${pathName}${search}${hash}`) ||
+ pathLocation,
+ options
+ );
+ },
+ [hash, navigate, search]
+ );
+};
+
+/**
+ * Search parameter, return
+ *
+ * @param {object} options
+ * @param {Function} options.useLocation
+ * @param {Function} options.useSearchParams
+ * @returns {Array}
+ */
+const useSearchParams = ({
+ useSearchParams: useAliasSearchParams = useRRDSearchParams,
+ useLocation: useAliasLocation = useLocation
+} = {}) => {
+ const { search } = useAliasLocation();
+ const [, setAliasSearchParams] = useAliasSearchParams();
+
+ /**
+ * Alias returned React Router Dom useSearchParams hook to something expected.
+ * Defaults to merging search objects instead of overwriting them.
+ *
+ * @param {object} updatedQuery
+ * @param {object} options
+ * @param {boolean} options.isMerged Merge search with existing search, or don't
+ * @param {string|*} options.currentSearch search returned from useLocation
+ */
+ const setSearchParams = useCallback(
+ (updatedQuery, { isMerged = true, currentSearch = search } = {}) => {
+ let updatedSearch = {};
+
+ if (isMerged) {
+ Object.assign(updatedSearch, routerHelpers.parseSearchParams(currentSearch), updatedQuery);
+ } else {
+ updatedSearch = updatedQuery;
+ }
+
+ setAliasSearchParams(updatedSearch);
+ },
+ [search, setAliasSearchParams]
+ );
+
+ return [routerHelpers.parseSearchParams(search), setSearchParams];
};
const context = {
- RouterContext,
- DEFAULT_CONTEXT,
- useRouterContext,
- useRouteDetail
+ useLocation,
+ useNavigate,
+ useParams,
+ useRedirect,
+ useSetRouteDetail,
+ useRouteDetailWorkingish,
+ useRouteDetail,
+ useSearchParams
};
-export { context as default, context, RouterContext, DEFAULT_CONTEXT, useRouterContext, useRouteDetail };
+export {
+ context as default,
+ context,
+ useLocation,
+ useNavigate,
+ useParams,
+ useRedirect,
+ useSetRouteDetail,
+ useRouteDetail,
+ useSearchParams
+};
diff --git a/src/components/router/routerHelpers.js b/src/components/router/routerHelpers.js
index a31a02537..6c0e0bf91 100644
--- a/src/components/router/routerHelpers.js
+++ b/src/components/router/routerHelpers.js
@@ -1,6 +1,7 @@
import React from 'react';
+import _memoize from 'lodash/memoize';
import { helpers } from '../../common/helpers';
-import { routesConfig } from '../../config';
+import { routesConfig, productConfig } from '../../config';
/**
* Platform name/id.
@@ -10,21 +11,7 @@ import { routesConfig } from '../../config';
const appName = helpers.UI_NAME;
/**
- * Return a string that describes a platform redirect.
- *
- * @returns {Array}
- */
-const platformLandingRedirect = `${helpers.UI_DEPLOY_PATH_PREFIX || ''}/`;
-
-/**
- * Return a string that describes a platform redirect.
- *
- * @returns {Array}
- */
-const platformModalRedirect = `${helpers.UI_DEPLOY_PATH_PREFIX || ''}/?not_entitled=subscriptions`;
-
-/**
- * Return an assumed route baseName directory based on existing app name.
+ * The app baseName. Return an assumed route baseName directory based on existing app name.
* App name is defined in dotenv and package.json/insights.appname
* [environment]/[OPTIONAL]/[OPTIONAL]/[APP NAME]
*
@@ -37,14 +24,7 @@ const dynamicBaseName = ({ pathName = window.location.pathname, appName: applica
`${pathName.split(applicationName)[0]}${applicationName}`;
/**
- * The app baseName.
- *
- * @type {string}
- */
-const baseName = (helpers.TEST_MODE && '/') || dynamicBaseName();
-
-/**
- * Return a base path.
+ * App basePath. Return a base path.
*
* @param {object} params
* @param {string} params.pathName
@@ -54,100 +34,31 @@ const baseName = (helpers.TEST_MODE && '/') || dynamicBaseName();
const dynamicBasePath = ({ pathName = window.location.pathname, appName: applicationName = helpers.UI_NAME } = {}) =>
pathName.split(applicationName)[0];
-/**
- * App basePath.
- *
- * @type {string}
- */
-const basePath = (helpers.TEST_MODE && '/') || dynamicBasePath();
-
-/**
- * Basic path join, minor emulation for path.join.
- *
- * @param {object} paths
- * @returns {string}
- */
-const pathJoin = (...paths) => {
- let updatedPath = Array.from(paths);
- const hasLead = /^\/\//.test(updatedPath[0]);
- updatedPath = updatedPath
- .join('/')
- .replace(/(\/\/)+/g, '~')
- .replace(/~/g, '/')
- .replace(/\/\//g, '/');
-
- if (hasLead) {
- updatedPath = `/${updatedPath}`;
- }
-
- return updatedPath;
-};
+const dynamicProductParameter = ({
+ pathName = window.location.pathname,
+ appName: applicationName = helpers.UI_NAME
+} = {}) => pathName.split(applicationName)[1]?.replace(/\//g, '');
/**
- * Generate product groups for applying query filter resets.
+ * The first error route.
*
- * @param {Array} config
- * @returns {Array}
- */
-const generateProductGroups = (config = routesConfig) => {
- const productGroups = {};
-
- config.forEach(({ pathParameter, productParameter }) => {
- const viewIds = ((Array.isArray(productParameter) && productParameter) || [productParameter]).map(
- id => (id && `view${id}`) || id
- );
-
- viewIds.forEach((id, index) => {
- if (id) {
- if (!productGroups[id]) {
- productGroups[id] = [];
- }
-
- if (pathParameter) {
- productGroups[id].push((Array.isArray(pathParameter) && pathParameter?.[index]) || pathParameter);
- }
- }
- });
- });
-
- return productGroups;
-};
-
-/**
- * Reference for products grouped by view.
+ * @type {object}
*/
-const productGroups = generateProductGroups();
+const errorRoute = routesConfig.find(route => route.activateOnError === true) || {};
/**
- * Generate routes to be consumed by router.
+ * The first redirect route.
*
- * @param {Array} config
- * @returns {Array}
+ * @type {object}
*/
-const generateRoutes = (config = routesConfig) =>
- config.map(({ activateOnError, component, disabled, id, path: routePath, redirect }) => ({
- activateOnError,
- component,
- disabled,
- exact: true,
- id,
- path: routePath,
- redirect
- }));
+const redirectRoute = routesConfig.find(({ disabled, redirect }) => !disabled && redirect);
/**
* Return array of objects that describes routing.
*
* @returns {Array}
*/
-const routes = generateRoutes();
-
-/**
- * The first error route.
- *
- * @type {object}
- */
-const getErrorRoute = routes.find(route => route.activateOnError === true) || {};
+const routes = routesConfig;
/**
* Match route config entries by path.
@@ -155,99 +66,68 @@ const getErrorRoute = routes.find(route => route.activateOnError === true) || {}
* @param {object} params
* @param {string} params.pathName
* @param {Array} params.config
+ * @param {boolean} params.isFailureAcceptable
* @returns {{configs: Array, configFirstMatch: object, configsById: object}}
*/
-const getRouteConfigByPath = ({ pathName = dynamicBasePath(), config = routesConfig } = {}) => {
- const basePathDirs = pathName?.split('/').filter(str => str.length > 0);
- const configs = [];
- const allConfigs = [];
- const configsById = {};
- const allConfigsById = {};
-
- const findConfig = dir => {
- config.forEach(({ id, path: configPath, pathParameter, productParameter, aliases, ...configItem }) => {
- const updatedConfigItem = {
- aliases,
- id,
- path: configPath,
- pathParameter,
- productParameter,
- ...configItem
- };
-
- if (
- dir &&
- (new RegExp(dir, 'i').test(configPath) ||
- new RegExp(dir, 'i').test(productParameter?.toString()) ||
- new RegExp(dir, 'i').test(pathParameter?.toString()) ||
- new RegExp(dir, 'i').test(aliases?.toString()))
- ) {
- if (!configsById[id]) {
- configsById[id] = { ...updatedConfigItem };
- configs.push({ ...updatedConfigItem });
+const getRouteConfigByPath = _memoize(
+ ({ pathName, configs = productConfig.configs, isFailureAcceptable = false } = {}) => {
+ const updatedPathName =
+ (/^http/i.test(pathName) && new URL(pathName).pathname) ||
+ pathName ||
+ (!isFailureAcceptable && window.location.pathname) ||
+ '';
+
+ const basePathDirs = updatedPathName
+ ?.split('#')?.[0]
+ ?.split('?')?.[0]
+ ?.split('/')
+ .filter(str => str.length > 0)
+ ?.reverse();
+ const filteredConfigs = [];
+ const filteredConfigsById = {};
+ const filteredConfigsByGroup = {};
+ const allConfigs = configs;
+
+ const findConfig = dir => {
+ configs.forEach(configItem => {
+ const { productId, productGroup, aliases } = configItem;
+
+ if (
+ !(productId in filteredConfigsById) &&
+ dir &&
+ (new RegExp(dir, 'i').test(aliases?.toString()) ||
+ new RegExp(dir, 'i').test(productGroup?.toString()) ||
+ new RegExp(dir, 'i').test(productId?.toString()))
+ ) {
+ filteredConfigsByGroup[productGroup] ??= [];
+ filteredConfigsByGroup[productGroup].push(configItem);
+
+ filteredConfigsById[productId] = configItem;
+ filteredConfigs.push(configItem);
}
- }
-
- if (!allConfigsById[id]) {
- allConfigsById[id] = { ...updatedConfigItem };
- allConfigs.push({ ...updatedConfigItem });
- }
- });
- };
-
- if (basePathDirs?.length) {
- basePathDirs.forEach(dir => {
- if (dir) {
- const decodedDir = window.decodeURI(dir);
- findConfig(decodedDir);
- }
- });
- } else {
- findConfig();
- }
-
- return { allConfigs, allConfigsById, configs, configsById, firstMatch: configs?.[0] };
-};
-
-/**
- * Return a route config object.
- *
- * @param {object} params
- * @param {string} params.id
- * @param {string} params.pathName
- * @param {boolean} params.returnDefault
- * @param {Array} params.config
- * @returns {object}
- */
-const getRouteConfig = ({ id = null, pathName, returnDefault = false, config = routesConfig } = {}) => {
- let navRouteItem;
-
- if (id) {
- navRouteItem = config.find(item => item.id === id);
- }
-
- if ((!navRouteItem && pathName) || (!navRouteItem && !pathName && !returnDefault)) {
- navRouteItem = getRouteConfigByPath({ pathName, config }).firstMatch;
- }
-
- if (!navRouteItem && returnDefault) {
- navRouteItem = config.find(item => item.default === true);
- }
-
- if (navRouteItem) {
- const { search = '', hash = '' } = window.location;
- navRouteItem.routeHref = `${navRouteItem.path}${search}${hash}`;
-
- const { pathParameter, productParameter } = navRouteItem;
- navRouteItem.pathParameter = (Array.isArray(pathParameter) && pathParameter[0]) || pathParameter;
- navRouteItem.productParameter = (Array.isArray(productParameter) && productParameter[0]) || productParameter;
- navRouteItem.viewParameter =
- (productParameter && `view${(Array.isArray(productParameter) && productParameter[0]) || productParameter}`) ||
- productParameter;
+ });
+ };
+
+ if (basePathDirs?.length) {
+ basePathDirs.forEach(dir => {
+ if (dir) {
+ const decodedDir = window.decodeURI(dir);
+ findConfig(decodedDir);
+ }
+ });
+ } else {
+ findConfig();
+ }
+
+ return {
+ allConfigs,
+ configs: filteredConfigs,
+ configsById: filteredConfigsById,
+ configsByGroup: filteredConfigsByGroup,
+ firstMatch: filteredConfigs?.[0]
+ };
}
-
- return { ...(navRouteItem || {}) };
-};
+);
/**
* Import a route component.
@@ -263,44 +143,76 @@ const importView = component => {
return p => {JSON.stringify({ ...p, component }, null, 2)};
};
+/**
+ * Parse search parameters from a string, using a set
+ *
+ * @param {string} currentPathAndOrSearch
+ * @returns {{}}
+ */
+const parseSearchParams = _memoize((currentPathAndOrSearch = window.location.search) => {
+ const { decodeURIComponent, URLSearchParams } = window;
+ const parsedSearch = {};
+
+ [
+ ...new Set(
+ [...new URLSearchParams(decodeURIComponent(currentPathAndOrSearch))].map(([param, value]) => `${param}~${value}`)
+ )
+ ].forEach(v => {
+ const [param, value] = v.split('~');
+ parsedSearch[param] = value;
+ });
+
+ return parsedSearch;
+});
+
+/**
+ * Basic path join, minor emulation for path.join.
+ *
+ * @param {object} paths
+ * @returns {string}
+ */
+const pathJoin = _memoize((...paths) => {
+ let updatedPath = Array.from(paths);
+ const hasLead = /^\/\//.test(updatedPath[0]);
+ updatedPath = updatedPath
+ .join('/')
+ .replace(/(\/\/)+/g, '~')
+ .replace(/~/g, '/')
+ .replace(/\/\//g, '/');
+
+ if (hasLead) {
+ updatedPath = `/${updatedPath}`;
+ }
+
+ return updatedPath;
+});
+
const routerHelpers = {
appName,
- baseName,
- basePath,
dynamicBaseName,
dynamicBasePath,
- generateProductGroups,
- generateRoutes,
- getErrorRoute,
- getRouteConfig,
+ dynamicProductParameter,
+ redirectRoute,
+ errorRoute,
getRouteConfigByPath,
importView,
+ parseSearchParams,
pathJoin,
- platformLandingRedirect,
- platformModalRedirect,
- productGroups,
- routes,
- routesConfig
+ routes
};
export {
routerHelpers as default,
routerHelpers,
appName,
- baseName,
- basePath,
dynamicBaseName,
dynamicBasePath,
- generateProductGroups,
- generateRoutes,
- getErrorRoute,
- getRouteConfig,
+ dynamicProductParameter,
+ redirectRoute,
+ errorRoute,
getRouteConfigByPath,
importView,
+ parseSearchParams,
pathJoin,
- platformLandingRedirect,
- platformModalRedirect,
- productGroups,
- routes,
- routesConfig
+ routes
};
diff --git a/src/config/__tests__/__snapshots__/index.test.js.snap b/src/config/__tests__/__snapshots__/index.test.js.snap
index a0356fa8f..07980ac71 100644
--- a/src/config/__tests__/__snapshots__/index.test.js.snap
+++ b/src/config/__tests__/__snapshots__/index.test.js.snap
@@ -1,17 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Configuration should have a consistent route configuration: inconsistent entries 1`] = `[]`;
+exports[`Configuration should have a consistent route configuration: inconsistent entries 1`] = `
+[
+ {
+ "Entry 13, /optin inconsistent": {
+ "activateOnError": "PASS",
+ "component": "PASS",
+ "default": "PASS",
+ "disabled": "PASS",
+ "path": "PASS",
+ "pathParameter": "FAIL",
+ "productConfig": "FAIL",
+ "productParameter": "FAIL",
+ "redirect": "PASS",
+ },
+ },
+ {
+ "Entry 14, / inconsistent": {
+ "activateOnError": "PASS",
+ "component": "PASS",
+ "default": "PASS",
+ "disabled": "PASS",
+ "path": "PASS",
+ "pathParameter": "FAIL",
+ "productConfig": "FAIL",
+ "productParameter": "FAIL",
+ "redirect": "PASS",
+ },
+ },
+]
+`;
exports[`Configuration should have consistent product configuration: inconsistent entries 1`] = `[]`;
exports[`Configuration should have consistent product configuration: products 1`] = `
[
- "rhel",
- "rhosak",
- "openshiftContainer",
- "openshiftMetrics",
- "openshiftDedicated",
- "satelliteProduct",
+ "configs",
+ "sortedConfigs",
]
`;
diff --git a/src/config/__tests__/__snapshots__/product.openshiftContainer.test.js.snap b/src/config/__tests__/__snapshots__/product.openshiftContainer.test.js.snap
index 71b351c7a..280e73eb6 100644
--- a/src/config/__tests__/__snapshots__/product.openshiftContainer.test.js.snap
+++ b/src/config/__tests__/__snapshots__/product.openshiftContainer.test.js.snap
@@ -761,3 +761,77 @@ exports[`Product OpenShift Container config should apply subscriptions inventory
},
}
`;
+
+exports[`Product OpenShift Container config should apply subscriptions inventory configuration: filtered, infinite 1`] = `
+{
+ "cells": [
+ {
+ "title": "lorem",
+ },
+ {
+ "title": "hello world",
+ },
+ {
+ "title": "",
+ },
+ {
+ "title": 2000,
+ },
+ {
+ "title": "2022-01-01",
+ },
+ ],
+ "columnHeaders": [
+ {
+ "title": "t(curiosity-inventory.header_product, {"context":"name"})",
+ "transforms": [],
+ },
+ {
+ "title": "t(curiosity-inventory.header_service, {"context":"level"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ {
+ "title": "t(curiosity-inventory.header, {"context":"quantity"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ {
+ "title": "t(curiosity-inventory.header, {"context":"subscriptions"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ {
+ "title": "t(curiosity-inventory.header_next_event, {"context":"date"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ ],
+ "data": {
+ "has_infinite_quantity": {
+ "title": "t(curiosity-inventory.header_has_infinite, {"context":"quantity"})",
+ "value": false,
+ },
+ "next_event_date": {
+ "title": "t(curiosity-inventory.header_next_event, {"context":"date"})",
+ "value": "2022-01-01T00:00:00.000Z",
+ },
+ "product_name": {
+ "title": "t(curiosity-inventory.header_product, {"context":"name"})",
+ "value": "lorem",
+ },
+ "service_level": {
+ "title": "t(curiosity-inventory.header_service, {"context":"level"})",
+ "value": "hello world",
+ },
+ "total_capacity": {
+ "title": "t(curiosity-inventory.header_total, {"context":"capacity"})",
+ "value": 2000,
+ },
+ },
+}
+`;
diff --git a/src/config/__tests__/__snapshots__/product.rhel.test.js.snap b/src/config/__tests__/__snapshots__/product.rhel.test.js.snap
index 0cd4d5541..bf7378b0c 100644
--- a/src/config/__tests__/__snapshots__/product.rhel.test.js.snap
+++ b/src/config/__tests__/__snapshots__/product.rhel.test.js.snap
@@ -780,3 +780,77 @@ exports[`Product RHEL config should apply subscriptions inventory configuration:
},
}
`;
+
+exports[`Product RHEL config should apply subscriptions inventory configuration: filtered, infinite 1`] = `
+{
+ "cells": [
+ {
+ "title": "lorem",
+ },
+ {
+ "title": "hello world",
+ },
+ {
+ "title": "",
+ },
+ {
+ "title": 2000,
+ },
+ {
+ "title": "2022-01-01",
+ },
+ ],
+ "columnHeaders": [
+ {
+ "title": "t(curiosity-inventory.header_product, {"context":"name"})",
+ "transforms": [],
+ },
+ {
+ "title": "t(curiosity-inventory.header_service, {"context":"level"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ {
+ "title": "t(curiosity-inventory.header, {"context":"quantity"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ {
+ "title": "t(curiosity-inventory.header, {"context":"subscriptions"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ {
+ "title": "t(curiosity-inventory.header_next_event, {"context":"date"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ ],
+ "data": {
+ "has_infinite_quantity": {
+ "title": "t(curiosity-inventory.header_has_infinite, {"context":"quantity"})",
+ "value": false,
+ },
+ "next_event_date": {
+ "title": "t(curiosity-inventory.header_next_event, {"context":"date"})",
+ "value": "2022-01-01T00:00:00.000Z",
+ },
+ "product_name": {
+ "title": "t(curiosity-inventory.header_product, {"context":"name"})",
+ "value": "lorem",
+ },
+ "service_level": {
+ "title": "t(curiosity-inventory.header_service, {"context":"level"})",
+ "value": "hello world",
+ },
+ "total_capacity": {
+ "title": "t(curiosity-inventory.header_total, {"context":"capacity"})",
+ "value": 2000,
+ },
+ },
+}
+`;
diff --git a/src/config/__tests__/__snapshots__/product.satellite.test.js.snap b/src/config/__tests__/__snapshots__/product.satellite.test.js.snap
index 1be47e95b..c274a0f73 100644
--- a/src/config/__tests__/__snapshots__/product.satellite.test.js.snap
+++ b/src/config/__tests__/__snapshots__/product.satellite.test.js.snap
@@ -261,9 +261,103 @@ exports[`Product Satellite config should apply guest inventory configuration: fi
exports[`Product Satellite config should apply hosts inventory configuration: filtered 1`] = `
{
"cells": [
+ {
+ "title": "lorem",
+ },
{
"title":
- lorem
+ t(curiosity-inventory.measurementType, {"context":null})
+
+
+ ,
+ },
+ {
+ "title": 10,
+ },
+ {
+ "title": ,
+ },
+ ],
+ "columnHeaders": [
+ {
+ "title": "t(curiosity-inventory.header_display_name, {"context":"Satellite"})",
+ "transforms": [],
+ },
+ {
+ "title": "t(curiosity-inventory.header_measurement_type, {"context":"Satellite"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ {
+ "title": "t(curiosity-inventory.header_sockets, {"context":"Satellite"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ {
+ "title": "t(curiosity-inventory.header_last_seen, {"context":"Satellite"})",
+ "transforms": [
+ [Function],
+ ],
+ },
+ ],
+ "data": {
+ "cores": {
+ "title": "t(curiosity-inventory.header_cores, {"context":"Satellite"})",
+ "value": 12,
+ },
+ "display_name": {
+ "title": "t(curiosity-inventory.header_display_name, {"context":"Satellite"})",
+ "value": "lorem",
+ },
+ "hardware_type": {
+ "title": "t(curiosity-inventory.header_hardware_type, {"context":"Satellite"})",
+ "value": "ipsum",
+ },
+ "inventory_id": {
+ "title": "t(curiosity-inventory.header_inventory_id, {"context":"Satellite"})",
+ "value": "",
+ },
+ "last_seen": {
+ "title": "t(curiosity-inventory.header_last_seen, {"context":"Satellite"})",
+ "value": "2022-01-01T00:00:00.000Z",
+ },
+ "loremIpsum": {
+ "title": "t(curiosity-inventory.header_loremIpsum, {"context":"Satellite"})",
+ "value": "hello world",
+ },
+ "measurement_type": {
+ "title": "t(curiosity-inventory.header_measurement_type, {"context":"Satellite"})",
+ "value": null,
+ },
+ "number_of_guests": {
+ "title": "t(curiosity-inventory.header_number_of_guests, {"context":"Satellite"})",
+ "value": 3,
+ },
+ "sockets": {
+ "title": "t(curiosity-inventory.header_sockets, {"context":"Satellite"})",
+ "value": 10,
+ },
+ },
+}
+`;
+
+exports[`Product Satellite config should apply hosts inventory configuration: filtered, authorized 1`] = `
+{
+ "cells": [
+ {
+ "title":
+
+ lorem
+
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"