From ca9680023ebb651447bfafdf845042099c2d2b0b Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Wed, 21 Feb 2024 11:42:42 +0100 Subject: [PATCH 1/2] Migrate gateway error state from redux to Jotai. --- cypress/component/GatewayErrors.cy.tsx | 43 +++++++++++----------- src/auth/OIDCConnector/OIDCSecured.tsx | 5 +-- src/chrome/create-chrome.ts | 3 +- src/components/ChromeRoute/ChromeRoute.tsx | 8 ++-- src/redux/action-types.ts | 2 - src/redux/actions.ts | 6 --- src/redux/chromeReducers.ts | 8 ---- src/redux/index.ts | 3 -- src/redux/store.d.ts | 2 - src/state/atoms/gatewayErrorAtom.ts | 8 ++++ src/state/chromeStore.ts | 2 + src/utils/iqeEnablement.ts | 10 ++--- src/utils/useNavigation.ts | 7 +++- 13 files changed, 49 insertions(+), 58 deletions(-) create mode 100644 src/state/atoms/gatewayErrorAtom.ts diff --git a/cypress/component/GatewayErrors.cy.tsx b/cypress/component/GatewayErrors.cy.tsx index eb1b17354..c67c75bbb 100644 --- a/cypress/component/GatewayErrors.cy.tsx +++ b/cypress/component/GatewayErrors.cy.tsx @@ -1,29 +1,26 @@ import React, { useEffect, useState } from 'react'; import { IntlProvider } from 'react-intl'; import { MemoryRouter } from 'react-router-dom'; -import { Provider, useSelector } from 'react-redux'; -import { applyMiddleware, combineReducers, createStore } from 'redux'; -import logger from 'redux-logger'; +import { Provider } from 'react-redux'; +import { createStore as reduxCreateStore } from 'redux'; import { removeScalprum } from '@scalprum/core'; import type { AuthContextProps } from 'react-oidc-context'; import { ChromeUser } from '@redhat-cloud-services/types'; -import { useSetAtom } from 'jotai'; +import { Provider as JotaiProvider, createStore, useAtomValue } from 'jotai'; -import chromeReducer from '../../src/redux'; -import { userLogIn } from '../../src/redux/actions'; import qe from '../../src/utils/iqeEnablement'; import { COMPLIACE_ERROR_CODES } from '../../src/utils/responseInterceptors'; import testUserJson from '../fixtures/testUser.json'; import { BLOCK_CLEAR_GATEWAY_ERROR } from '../../src/utils/common'; import { initializeVisibilityFunctions } from '../../src/utils/VisibilitySingleton'; -import { ReduxState } from '../../src/redux/store'; import GatewayErrorComponent from '../../src/components/ErrorComponents/GatewayErrorComponent'; import { activeModuleAtom } from '../../src/state/atoms/activeModuleAtom'; +import { gatewayErrorAtom } from '../../src/state/atoms/gatewayErrorAtom'; const testUser: ChromeUser = testUserJson as unknown as ChromeUser; const ErrorCatcher = ({ children }: { children: React.ReactNode }) => { - const gatewayError = useSelector(({ chrome: { gatewayError } }: ReduxState) => gatewayError); + const gatewayError = useAtomValue(gatewayErrorAtom); if (gatewayError) { return ; @@ -33,31 +30,33 @@ const ErrorCatcher = ({ children }: { children: React.ReactNode }) => { }; function createEnv(code: string, childNode: React.ReactNode) { - const reduxStore = createStore(combineReducers(chromeReducer()), applyMiddleware(logger)); - // initialize user object for feature flags - reduxStore.dispatch(userLogIn(testUser)); + const reduxStore = reduxCreateStore(() => ({ chrome: {} })); + const chromeStore = createStore(); + chromeStore.set(activeModuleAtom, undefined); + chromeStore.set(gatewayErrorAtom, undefined); // initializes request interceptors - qe.init(reduxStore, { current: { user: { access_token: 'foo' } } as unknown as AuthContextProps }); + qe.init(chromeStore, { current: { user: { access_token: 'foo' } } as unknown as AuthContextProps }); const Component = () => { const [mounted, setMounted] = useState(false); - const setActiveModule = useSetAtom(activeModuleAtom); useEffect(() => { setMounted(true); - setActiveModule(code); + chromeStore.set(activeModuleAtom, code); }, []); if (!mounted) { return null; } return ( - - - - {childNode} - - - + + + + + {childNode} + + + + ); }; return Component; @@ -88,7 +87,7 @@ describe('Gateway errors', () => { removeScalprum(); }); - it('handles 403 3scale gateway error', () => { + it.only('handles 403 3scale gateway error', () => { const code = 'gateway-403'; const TestComponent = () => { useEffect(() => { diff --git a/src/auth/OIDCConnector/OIDCSecured.tsx b/src/auth/OIDCConnector/OIDCSecured.tsx index fb047fe6d..e3f73aa31 100644 --- a/src/auth/OIDCConnector/OIDCSecured.tsx +++ b/src/auth/OIDCConnector/OIDCSecured.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { hasAuthParams, useAuth } from 'react-oidc-context'; import { User } from 'oidc-client-ts'; import { BroadcastChannel } from 'broadcast-channel'; -import { useStore } from 'react-redux'; import { ChromeUser } from '@redhat-cloud-services/types'; import ChromeAuthContext, { ChromeAuthContextValue } from '../ChromeAuthContext'; import { generateRoutesList } from '../../utils/common'; @@ -26,6 +25,7 @@ import { useAtomValue } from 'jotai'; import shouldReAuthScopes from '../shouldReAuthScopes'; import { activeModuleDefinitionReadAtom } from '../../state/atoms/activeModuleAtom'; import { loadModulesSchemaWriteAtom } from '../../state/atoms/chromeModuleAtom'; +import chromeStore from '../../state/chromeStore'; type Entitlement = { is_entitled: boolean; is_trial: boolean }; const serviceAPI = entitlementsApi(); @@ -84,7 +84,6 @@ export function OIDCSecured({ }: React.PropsWithChildren<{ microFrontendConfig: Record; ssoUrl: string } & FooterProps>) { const auth = useAuth(); const authRef = useRef(auth); - const store = useStore(); const setScalprumConfigAtom = useSetAtom(writeInitialScalprumConfigAtom); const loadModulesSchema = useSetAtom(loadModulesSchemaWriteAtom); @@ -147,7 +146,7 @@ export function OIDCSecured({ async function onUserAuthenticated(user: User) { // order of calls is important // init the IQE enablement first to add the necessary auth headers to the requests - init(store, authRef); + init(chromeStore, authRef); const entitlements = await fetchEntitlements(user); const chromeUser = mapOIDCUserToChromeUser(user, entitlements); const getUser = () => Promise.resolve(chromeUser); diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 48c7f1a16..ccc6ea96a 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -36,6 +36,7 @@ import { ChromeAuthContextValue } from '../auth/ChromeAuthContext'; import qe from '../utils/iqeEnablement'; import { RegisterModulePayload } from '../state/atoms/chromeModuleAtom'; import requestPdf from '../pdf/requestPdf'; +import chromeStore from '../state/chromeStore'; export type CreateChromeContextConfig = { useGlobalFilter: (callback: (selectedTags?: FlagTagsFilter) => any) => ReturnType; @@ -108,7 +109,7 @@ export const createChromeContext = ({ getOfflineToken: chromeAuth.getOfflineToken, qe: { ...qe, - init: () => qe.init(store, { current: { user: { access_token: chromeAuth.token } } as any }), + init: () => qe.init(chromeStore, { current: { user: { access_token: chromeAuth.token } } as any }), }, reAuthWithScopes: chromeAuth.reAuthWithScopes, }, diff --git a/src/components/ChromeRoute/ChromeRoute.tsx b/src/components/ChromeRoute/ChromeRoute.tsx index b1401591b..8117eec43 100644 --- a/src/components/ChromeRoute/ChromeRoute.tsx +++ b/src/components/ChromeRoute/ChromeRoute.tsx @@ -1,19 +1,19 @@ import { ScalprumComponent } from '@scalprum/react-core'; import React, { memo, useContext, useEffect } from 'react'; import LoadingFallback from '../../utils/loading-fallback'; -import { batch, useDispatch, useSelector } from 'react-redux'; +import { batch, useDispatch } from 'react-redux'; import { toggleGlobalFilter } from '../../redux/actions'; import ErrorComponent from '../ErrorComponents/DefaultErrorComponent'; import { getPendoConf } from '../../analytics'; import classNames from 'classnames'; import { HelpTopicContext } from '@patternfly/quickstarts'; import GatewayErrorComponent from '../ErrorComponents/GatewayErrorComponent'; -import { ReduxState } from '../../redux/store'; import { DeepRequired } from 'utility-types'; import { ChromeUser } from '@redhat-cloud-services/types'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; -import { useSetAtom } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { activeModuleAtom } from '../../state/atoms/activeModuleAtom'; +import { gatewayErrorAtom } from '../../state/atoms/gatewayErrorAtom'; export type ChromeRouteProps = { scope: string; @@ -30,7 +30,7 @@ const ChromeRoute = memo( const dispatch = useDispatch(); const { setActiveHelpTopicByName } = useContext(HelpTopicContext); const { user } = useContext(ChromeAuthContext); - const gatewayError = useSelector(({ chrome: { gatewayError } }: ReduxState) => gatewayError); + const gatewayError = useAtomValue(gatewayErrorAtom); const setActiveModule = useSetAtom(activeModuleAtom); diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index 1a58b08d7..440e37789 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -32,8 +32,6 @@ export const ADD_QUICKSTARTS_TO_APP = '@@chrome/add-quickstart'; export const DISABLE_QUICKSTARTS = '@@chrome/disable-quickstarts'; export const CLEAR_QUICKSTARTS = '@@chrome/clear-quickstarts'; -export const SET_GATEWAY_ERROR = '@@chrome/set-gateway-error'; - export const TOGGLE_NOTIFICATIONS_DRAWER = '@@chrome/toggle-notifications-drawer'; export const POPULATE_NOTIFICATIONS = '@@chrome/populate-notifications'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 6b8173ce9..d3091a723 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -5,7 +5,6 @@ import type { ChromeUser } from '@redhat-cloud-services/types'; import type { FlagTagsFilter, NavDOMEvent, NavItem, Navigation } from '../@types/types'; import type { AccessRequest, NotificationData, NotificationsPayload } from './store'; import type { QuickStart } from '@patternfly/quickstarts'; -import type { ThreeScaleError } from '../utils/responseInterceptors'; export function userLogIn(user: ChromeUser | boolean) { return { @@ -169,11 +168,6 @@ export const markActiveProduct = (product?: string) => ({ payload: product, }); -export const setGatewayError = (error?: ThreeScaleError) => ({ - type: actionTypes.SET_GATEWAY_ERROR, - payload: error, -}); - export const toggleNotificationsDrawer = () => ({ type: actionTypes.TOGGLE_NOTIFICATIONS_DRAWER, }); diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index 75e77cdaa..595fc7735 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -3,7 +3,6 @@ import { ChromeUser } from '@redhat-cloud-services/types'; import { REQUESTS_COUNT, REQUESTS_DATA } from '../utils/consts'; import { NavItem, Navigation } from '../@types/types'; import { ITLess, highlightItems, levelArray } from '../utils/common'; -import { ThreeScaleError } from '../utils/responseInterceptors'; import { AccessRequest, ChromeState, NotificationData, NotificationsPayload } from './store'; export function appNavClick(state: ChromeState, { payload }: { payload: { id: string } }): ChromeState { @@ -244,13 +243,6 @@ export function markActiveProduct(state: ChromeState, { payload }: { payload?: s }; } -export function setGatewayError(state: ChromeState, { payload }: { payload?: ThreeScaleError }): ChromeState { - return { - ...state, - gatewayError: payload, - }; -} - export function toggleNotificationsReducer(state: ChromeState) { return { ...state, diff --git a/src/redux/index.ts b/src/redux/index.ts index 939e60819..2a8478aef 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -20,7 +20,6 @@ import { onPageObjectId, populateNotificationsReducer, populateQuickstartsReducer, - setGatewayError, setPendoFeedbackFlag, toggleDebuggerButton, toggleDebuggerModal, @@ -65,7 +64,6 @@ import { MARK_REQUEST_NOTIFICATION_SEEN, POPULATE_NOTIFICATIONS, POPULATE_QUICKSTARTS_CATALOG, - SET_GATEWAY_ERROR, SET_PENDO_FEEDBACK_FLAG, TOGGLE_DEBUGGER_BUTTON, TOGGLE_DEBUGGER_MODAL, @@ -97,7 +95,6 @@ const reducers = { [DISABLE_QUICKSTARTS]: disableQuickstartsReducer, [UPDATE_DOCUMENT_TITLE_REDUCER]: documentTitleReducer, [MARK_ACTIVE_PRODUCT]: markActiveProduct, - [SET_GATEWAY_ERROR]: setGatewayError, [CLEAR_QUICKSTARTS]: clearQuickstartsReducer, [TOGGLE_NOTIFICATIONS_DRAWER]: toggleNotificationsReducer, [POPULATE_NOTIFICATIONS]: populateNotificationsReducer, diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index d885a1794..6540e901a 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -1,7 +1,6 @@ import { QuickStart } from '@patternfly/quickstarts'; import { FlagTagsFilter, NavItem, Navigation } from '../@types/types'; -import { ThreeScaleError } from '../utils/responseInterceptors'; export type InternalNavigation = { [key: string]: Navigation | NavItem[] | undefined; @@ -60,7 +59,6 @@ export type ChromeState = { }; }; documentTitle?: string; - gatewayError?: ThreeScaleError; notifications: Notifications; }; diff --git a/src/state/atoms/gatewayErrorAtom.ts b/src/state/atoms/gatewayErrorAtom.ts new file mode 100644 index 000000000..62fa8263e --- /dev/null +++ b/src/state/atoms/gatewayErrorAtom.ts @@ -0,0 +1,8 @@ +import { atom } from 'jotai'; +import { ThreeScaleError } from '../../utils/responseInterceptors'; + +export const gatewayErrorAtom = atom(undefined); + +export const clearGatewayErrorAtom = atom(null, (_get, set) => { + set(gatewayErrorAtom, undefined); +}); diff --git a/src/state/chromeStore.ts b/src/state/chromeStore.ts index ddce3b852..0397ca3a3 100644 --- a/src/state/chromeStore.ts +++ b/src/state/chromeStore.ts @@ -3,6 +3,7 @@ import { activeModuleAtom } from './atoms/activeModuleAtom'; import { contextSwitcherOpenAtom } from './atoms/contextSwitcher'; import { isPreviewAtom } from './atoms/releaseAtom'; import { isBeta } from '../utils/common'; +import { gatewayErrorAtom } from './atoms/gatewayErrorAtom'; const chromeStore = createStore(); @@ -10,6 +11,7 @@ const chromeStore = createStore(); chromeStore.set(contextSwitcherOpenAtom, false); chromeStore.set(activeModuleAtom, undefined); chromeStore.set(isPreviewAtom, isBeta()); +chromeStore.set(gatewayErrorAtom, undefined); // globally handle subscription to activeModuleAtom chromeStore.sub(activeModuleAtom, () => { diff --git a/src/utils/iqeEnablement.ts b/src/utils/iqeEnablement.ts index 90c8ce146..d24d84d50 100644 --- a/src/utils/iqeEnablement.ts +++ b/src/utils/iqeEnablement.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable prefer-rest-params */ -import type { Store } from 'redux'; -import { setGatewayError } from '../redux/actions'; import { get3scaleError } from './responseInterceptors'; import crossAccountBouncer from '../auth/crossAccountBouncer'; +import { createStore } from 'jotai'; // eslint-disable-next-line no-restricted-imports import type { AuthContextProps } from 'react-oidc-context'; +import { gatewayErrorAtom } from '../state/atoms/gatewayErrorAtom'; // TODO: Refactor this file to use modern JS let xhrResults: XMLHttpRequest[] = []; @@ -63,7 +63,7 @@ const spreadAdditionalHeaders = (options: RequestInit | undefined) => { return additionalHeaders; }; -export function init(store: Store, authRef: React.MutableRefObject) { +export function init(chromeStore: ReturnType, authRef: React.MutableRefObject) { const open = window.XMLHttpRequest.prototype.open; const send = window.XMLHttpRequest.prototype.send; const setRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader; @@ -116,7 +116,7 @@ export function init(store: Store, authRef: React.MutableRefObject { const useNavigation = () => { const { flagsReady, flagsError } = useFlagsStatus(); + const clearGatewayError = useSetAtom(clearGatewayErrorAtom); const dispatch = useDispatch(); const location = useLocation(); const navigate = useNavigate(); @@ -77,7 +80,7 @@ const useNavigation = () => { * Clean gateway error on URL change */ if (localStorage.getItem(BLOCK_CLEAR_GATEWAY_ERROR) !== 'true') { - dispatch(setGatewayError()); + clearGatewayError(); } }); } From 7c34fb4b18ec78c049e48bbd5bcce30de37db001 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 27 Feb 2024 15:52:27 +0100 Subject: [PATCH 2/2] Remove .only from cy component tests. --- cypress/component/GatewayErrors.cy.tsx | 2 +- cypress/component/OIDCConnector/OIDCSecured.cy.tsx | 2 +- cypress/component/helptopics/HelpTopicManager.cy.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/component/GatewayErrors.cy.tsx b/cypress/component/GatewayErrors.cy.tsx index c67c75bbb..48faae596 100644 --- a/cypress/component/GatewayErrors.cy.tsx +++ b/cypress/component/GatewayErrors.cy.tsx @@ -87,7 +87,7 @@ describe('Gateway errors', () => { removeScalprum(); }); - it.only('handles 403 3scale gateway error', () => { + it('handles 403 3scale gateway error', () => { const code = 'gateway-403'; const TestComponent = () => { useEffect(() => { diff --git a/cypress/component/OIDCConnector/OIDCSecured.cy.tsx b/cypress/component/OIDCConnector/OIDCSecured.cy.tsx index 687334197..4126f76c4 100644 --- a/cypress/component/OIDCConnector/OIDCSecured.cy.tsx +++ b/cypress/component/OIDCConnector/OIDCSecured.cy.tsx @@ -129,7 +129,7 @@ describe('ODIC Secured', () => { cy.contains(CHILD_TEXT).should('exist'); }); - it.only('Chrome auth context methods should be initialized and called on click', () => { + it('Chrome auth context methods should be initialized and called on click', () => { cy.mount( diff --git a/cypress/component/helptopics/HelpTopicManager.cy.tsx b/cypress/component/helptopics/HelpTopicManager.cy.tsx index 4816feb19..294dca51f 100644 --- a/cypress/component/helptopics/HelpTopicManager.cy.tsx +++ b/cypress/component/helptopics/HelpTopicManager.cy.tsx @@ -169,7 +169,7 @@ describe('HelpTopicManager', () => { cy.intercept('http://localhost:8080/api/chrome-service/v1/static/stable/stage/search/search-index.json', []); }); - it.only('should switch help topics drawer content', () => { + it('should switch help topics drawer content', () => { // change screen size cy.viewport(1280, 720); cy.window().then((win) => {