Skip to content

Commit

Permalink
Merge pull request RedHatInsights#2773 from Hyperkid123/jotai-migration
Browse files Browse the repository at this point in the history
Migrate gateway error state from redux to Jotai.
  • Loading branch information
Hyperkid123 authored May 29, 2024
2 parents 5b5ce55 + 7c34fb4 commit babf03a
Show file tree
Hide file tree
Showing 15 changed files with 50 additions and 59 deletions.
41 changes: 20 additions & 21 deletions cypress/component/GatewayErrors.cy.tsx
Original file line number Diff line number Diff line change
@@ -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 <GatewayErrorComponent error={gatewayError} />;
Expand All @@ -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 (
<Provider store={reduxStore}>
<MemoryRouter initialEntries={['/']}>
<IntlProvider locale="en">
<ErrorCatcher>{childNode}</ErrorCatcher>
</IntlProvider>
</MemoryRouter>
</Provider>
<JotaiProvider store={chromeStore}>
<Provider store={reduxStore}>
<MemoryRouter initialEntries={['/']}>
<IntlProvider locale="en">
<ErrorCatcher>{childNode}</ErrorCatcher>
</IntlProvider>
</MemoryRouter>
</Provider>
</JotaiProvider>
);
};
return Component;
Expand Down
2 changes: 1 addition & 1 deletion cypress/component/OIDCConnector/OIDCSecured.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<AuthContext.Provider value={authContextValue}>
<Wrapper store={store}>
Expand Down
2 changes: 1 addition & 1 deletion cypress/component/helptopics/HelpTopicManager.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
5 changes: 2 additions & 3 deletions src/auth/OIDCConnector/OIDCSecured.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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();
Expand Down Expand Up @@ -84,7 +84,6 @@ export function OIDCSecured({
}: React.PropsWithChildren<{ microFrontendConfig: Record<string, any>; ssoUrl: string } & FooterProps>) {
const auth = useAuth();
const authRef = useRef(auth);
const store = useStore();
const setScalprumConfigAtom = useSetAtom(writeInitialScalprumConfigAtom);
const loadModulesSchema = useSetAtom(loadModulesSchemaWriteAtom);

Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/chrome/create-chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof callback>;
Expand Down Expand Up @@ -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,
},
Expand Down
8 changes: 4 additions & 4 deletions src/components/ChromeRoute/ChromeRoute.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);

Expand Down
2 changes: 0 additions & 2 deletions src/redux/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
6 changes: 0 additions & 6 deletions src/redux/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
});
Expand Down
8 changes: 0 additions & 8 deletions src/redux/chromeReducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 0 additions & 3 deletions src/redux/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
onPageObjectId,
populateNotificationsReducer,
populateQuickstartsReducer,
setGatewayError,
setPendoFeedbackFlag,
toggleDebuggerButton,
toggleDebuggerModal,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 0 additions & 2 deletions src/redux/store.d.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -60,7 +59,6 @@ export type ChromeState = {
};
};
documentTitle?: string;
gatewayError?: ThreeScaleError;
notifications: Notifications;
};

Expand Down
8 changes: 8 additions & 0 deletions src/state/atoms/gatewayErrorAtom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { atom } from 'jotai';
import { ThreeScaleError } from '../../utils/responseInterceptors';

export const gatewayErrorAtom = atom<ThreeScaleError | undefined>(undefined);

export const clearGatewayErrorAtom = atom(null, (_get, set) => {
set(gatewayErrorAtom, undefined);
});
2 changes: 2 additions & 0 deletions src/state/chromeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ 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();

// setup initial chrome store state
chromeStore.set(contextSwitcherOpenAtom, false);
chromeStore.set(activeModuleAtom, undefined);
chromeStore.set(isPreviewAtom, isBeta());
chromeStore.set(gatewayErrorAtom, undefined);

// globally handle subscription to activeModuleAtom
chromeStore.sub(activeModuleAtom, () => {
Expand Down
10 changes: 5 additions & 5 deletions src/utils/iqeEnablement.ts
Original file line number Diff line number Diff line change
@@ -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[] = [];
Expand Down Expand Up @@ -63,7 +63,7 @@ const spreadAdditionalHeaders = (options: RequestInit | undefined) => {
return additionalHeaders;
};

export function init(store: Store, authRef: React.MutableRefObject<AuthContextProps>) {
export function init(chromeStore: ReturnType<typeof createStore>, authRef: React.MutableRefObject<AuthContextProps>) {
const open = window.XMLHttpRequest.prototype.open;
const send = window.XMLHttpRequest.prototype.send;
const setRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader;
Expand Down Expand Up @@ -116,7 +116,7 @@ export function init(store: Store, authRef: React.MutableRefObject<AuthContextPr
crossAccountBouncer();
// check for 3scale error
} else if (gatewayError) {
store.dispatch(setGatewayError(gatewayError));
chromeStore.set(gatewayErrorAtom, gatewayError);
}
}
};
Expand Down Expand Up @@ -157,7 +157,7 @@ export function init(store: Store, authRef: React.MutableRefObject<AuthContextPr
const data = isJson ? await resCloned.json() : await resCloned.text();
const gatewayError = get3scaleError(data);
if (gatewayError) {
store.dispatch(setGatewayError(gatewayError));
chromeStore.set(gatewayErrorAtom, gatewayError);
}

return res;
Expand Down
7 changes: 5 additions & 2 deletions src/utils/useNavigation.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import axios from 'axios';
import { useSetAtom } from 'jotai';
import { useContext, useEffect, useRef, useState } from 'react';
import { batch, useDispatch, useSelector } from 'react-redux';
import { loadLeftNavSegment, setGatewayError } from '../redux/actions';
import { loadLeftNavSegment } from '../redux/actions';
import { useLocation, useNavigate } from 'react-router-dom';
import { BLOCK_CLEAR_GATEWAY_ERROR, getChromeStaticPathname, isBeta } from './common';
import { evaluateVisibility } from './isNavItemVisible';
import { QuickStartContext } from '@patternfly/quickstarts';
import { useFlagsStatus } from '@unleash/proxy-client-react';
import { BundleNavigation, NavItem, Navigation } from '../@types/types';
import { ReduxState } from '../redux/store';
import { clearGatewayErrorAtom } from '../state/atoms/gatewayErrorAtom';

function cleanNavItemsHref(navItem: NavItem) {
const result = { ...navItem };
Expand Down Expand Up @@ -44,6 +46,7 @@ const appendQSSearch = (currentSearch: string, activeQuickStartID: string) => {

const useNavigation = () => {
const { flagsReady, flagsError } = useFlagsStatus();
const clearGatewayError = useSetAtom(clearGatewayErrorAtom);
const dispatch = useDispatch();
const location = useLocation();
const navigate = useNavigate();
Expand Down Expand Up @@ -77,7 +80,7 @@ const useNavigation = () => {
* Clean gateway error on URL change
*/
if (localStorage.getItem(BLOCK_CLEAR_GATEWAY_ERROR) !== 'true') {
dispatch(setGatewayError());
clearGatewayError();
}
});
}
Expand Down

0 comments on commit babf03a

Please sign in to comment.