From 0fa923f875e5f6db15975d2ed627b712e7ded4c4 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Fri, 1 Dec 2023 10:17:13 +0100 Subject: [PATCH] Replace user object from redux by chrome auth context value. --- src/components/GlobalFilter/GlobalFilter.tsx | 5 +- .../GlobalFilter/GlobalFilterMenu.tsx | 7 +- src/components/Header/UserIcon.tsx | 9 +-- src/components/IDPChecker/IDPChecker.test.js | 77 ++++++++++++++----- src/components/IDPChecker/IDPChecker.tsx | 9 ++- .../DrawerPanelContent.tsx | 6 +- .../useQuickstartsStates.stage.test.js | 65 ++++++++++------ .../QuickStart/useQuickstartsStates.ts | 9 ++- src/components/RootApp/ScalprumRoot.test.js | 1 + src/hooks/useBundleVisitDetection.ts | 8 +- src/redux/chromeReducers.ts | 1 - src/redux/store.d.ts | 2 - 12 files changed, 128 insertions(+), 71 deletions(-) diff --git a/src/components/GlobalFilter/GlobalFilter.tsx b/src/components/GlobalFilter/GlobalFilter.tsx index 928a927bb..912106cb3 100644 --- a/src/components/GlobalFilter/GlobalFilter.tsx +++ b/src/components/GlobalFilter/GlobalFilter.tsx @@ -11,6 +11,7 @@ import { GlobalFilterTag, GlobalFilterWorkloads, ReduxState, SID } from '../../r import { FlagTagsFilter } from '../../@types/types'; import { isGlobalFilterAllowed } from '../../utils/common'; import InternalChromeContext from '../../utils/internalChromeContext'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; const useLoadTags = (hasAccess = false) => { const navigate = useNavigate(); @@ -128,7 +129,7 @@ const GlobalFilter = ({ hasAccess }: { hasAccess: boolean }) => { const GlobalFilterWrapper = () => { const [hasAccess, setHasAccess] = useState(false); const globalFilterRemoved = useSelector(({ globalFilter: { globalFilterRemoved } }: ReduxState) => globalFilterRemoved); - const userLoaded = useSelector(({ chrome: { user } }: ReduxState) => Boolean(user)); + const chromeAuth = useContext(ChromeAuthContext); const { pathname } = useLocation(); const { getUserPermissions } = useContext(InternalChromeContext); @@ -157,7 +158,7 @@ const GlobalFilterWrapper = () => { mounted = false; }; }, []); - return isGlobalFilterEnabled && userLoaded ? : null; + return isGlobalFilterEnabled && chromeAuth.ready ? : null; }; export default GlobalFilterWrapper; diff --git a/src/components/GlobalFilter/GlobalFilterMenu.tsx b/src/components/GlobalFilter/GlobalFilterMenu.tsx index 7d5220b44..1a5080c5b 100644 --- a/src/components/GlobalFilter/GlobalFilterMenu.tsx +++ b/src/components/GlobalFilter/GlobalFilterMenu.tsx @@ -1,4 +1,4 @@ -import React, { FormEvent, Fragment, MouseEventHandler, useMemo } from 'react'; +import React, { FormEvent, Fragment, MouseEventHandler, useContext, useMemo } from 'react'; import { Group, GroupFilter, GroupType } from '@redhat-cloud-services/frontend-components/ConditionalFilter'; import { useIntl } from 'react-intl'; @@ -18,6 +18,7 @@ import { CommonSelectedTag, ReduxState } from '../../redux/store'; import { updateSelected } from './globalFilterApi'; import { fetchAllTags } from '../../redux/actions'; import { FlagTagsFilter } from '../../@types/types'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; export type GlobalFilterMenuGroupKeys = GroupType; @@ -91,7 +92,7 @@ export const GlobalFilterDropdown: React.FunctionComponent undefined; const registeredWith = useSelector(({ globalFilter: { scope } }: ReduxState) => scope); - const userLoaded = useSelector(({ chrome: { user } }: ReduxState) => Boolean(user)); + const auth = useContext(ChromeAuthContext); const intl = useIntl(); const dispatch = useDispatch(); const GroupFilterWrapper = useMemo( @@ -102,7 +103,7 @@ export const GlobalFilterDropdown: React.FunctionComponent - {userLoaded && allowed !== undefined ? ( + {auth.ready && allowed !== undefined ? ( { - const username = useSelector(({ chrome }: ReduxState) => chrome.user?.identity.user?.username); + const auth = useContext(ChromeAuthContext); const [avatar, setAvatar] = useState(ImgAvatar); const getImage = (img: HTMLImageElement) => { @@ -17,7 +16,7 @@ const UserIcon = () => { useEffect(() => { const img = new Image(); - img.src = `https://access.redhat.com/api/users/avatar/${username}/`; + img.src = `https://access.redhat.com/api/users/avatar/${auth.user.identity.user?.username ?? ''}/`; img.onload = () => getImage(img); }, []); diff --git a/src/components/IDPChecker/IDPChecker.test.js b/src/components/IDPChecker/IDPChecker.test.js index 618e54161..eb0a35a10 100644 --- a/src/components/IDPChecker/IDPChecker.test.js +++ b/src/components/IDPChecker/IDPChecker.test.js @@ -6,6 +6,7 @@ import { act } from 'react-dom/test-utils'; import configureStore from 'redux-mock-store'; import * as utils from '../../utils/common'; import IDPChecker from './IDPChecker'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; jest.mock('../../utils/common', () => { const utils = jest.requireActual('../../utils/common'); @@ -48,11 +49,20 @@ describe('', () => { ITLessSpy.mockReturnValueOnce(false); const store = mockStore(initialState); const { container, queryAllByTestId } = render( - - - OK - - + + + + OK + + + ); expect(queryAllByTestId('foo')).toHaveLength(1); @@ -69,11 +79,20 @@ describe('', () => { await act(async () => { render( - - - OK - - + + + + OK + + + ); }); @@ -94,11 +113,20 @@ describe('', () => { await act(async () => { render( - - - OK - - + + + + OK + + + ); }); @@ -111,11 +139,20 @@ describe('', () => { await act(async () => { render( - - - OK - - + + + + OK + + + ); }); diff --git a/src/components/IDPChecker/IDPChecker.tsx b/src/components/IDPChecker/IDPChecker.tsx index 2dfaa79f9..2c8af73db 100644 --- a/src/components/IDPChecker/IDPChecker.tsx +++ b/src/components/IDPChecker/IDPChecker.tsx @@ -1,10 +1,11 @@ -import React, { Fragment, useEffect, useRef, useState } from 'react'; +import React, { Fragment, useContext, useEffect, useRef, useState } from 'react'; import axios from 'axios'; import { useSelector } from 'react-redux'; import { ITLess } from '../../utils/common'; import IDPError from '../ErrorComponents/IDPError'; import { ReduxState } from '../../redux/store'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; const IDPStatuses = { OK: 'OK', @@ -22,11 +23,11 @@ const IDPChecker: React.FunctionComponent = ({ children } return IDPStatuses.OK; }); - const hasUser = useSelector(({ chrome: { user } }: ReduxState) => Object.keys(user || {}).length > 0); + const auth = useContext(ChromeAuthContext); const allowStateChange = useRef(ITLessEnv); useEffect(() => { - if (ITLessEnv && status !== IDPStatuses.PENDING && hasUser) { + if (ITLessEnv && status !== IDPStatuses.PENDING && auth.ready) { allowStateChange.current && setStatus(IDPStatuses.PENDING); axios .get('/api/entitlements/v1/services') @@ -38,7 +39,7 @@ const IDPChecker: React.FunctionComponent = ({ children allowStateChange.current && setStatus(authError ? IDPStatuses.ERROR : IDPStatuses.OK); }); } - }, [hasUser, missingIDP]); + }, [auth.ready, missingIDP]); useEffect(() => { if (missingIDP === true) { diff --git a/src/components/NotificationsDrawer/DrawerPanelContent.tsx b/src/components/NotificationsDrawer/DrawerPanelContent.tsx index 936fddaad..2cc83af28 100644 --- a/src/components/NotificationsDrawer/DrawerPanelContent.tsx +++ b/src/components/NotificationsDrawer/DrawerPanelContent.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { PopoverPosition } from '@patternfly/react-core/dist/dynamic/components/Popover'; import { Icon } from '@patternfly/react-core/dist/dynamic/components/Icon'; import { Badge } from '@patternfly/react-core/dist/dynamic/components/Badge'; @@ -28,6 +28,7 @@ import { NotificationData, ReduxState } from '../../redux/store'; import NotificationItem from './NotificationItem'; import { markAllNotificationsAsRead, markAllNotificationsAsUnread, toggleNotificationsDrawer } from '../../redux/actions'; import { filterConfig } from './notificationDrawerUtils'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; export type DrawerPanelProps = { innerRef: React.Ref; @@ -63,7 +64,8 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { const navigate = useNavigate(); const dispatch = useDispatch(); const notifications = useSelector(({ chrome: { notifications } }: ReduxState) => notifications?.data || []); - const isOrgAdmin = useSelector(({ chrome }: ReduxState) => chrome.user?.identity.user?.is_org_admin); + const auth = useContext(ChromeAuthContext); + const isOrgAdmin = auth?.user?.identity?.user?.is_org_admin; useEffect(() => { const modifiedNotifications = (activeFilters || []).reduce( diff --git a/src/components/QuickStart/useQuickstartsStates.stage.test.js b/src/components/QuickStart/useQuickstartsStates.stage.test.js index 66bf8db51..8a50f9622 100644 --- a/src/components/QuickStart/useQuickstartsStates.stage.test.js +++ b/src/components/QuickStart/useQuickstartsStates.stage.test.js @@ -5,6 +5,7 @@ import { Provider } from 'react-redux'; import * as axios from 'axios'; import useQuickstartsStates from './useQuickstartsStates'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; jest.mock('axios', () => { const axios = jest.requireActual('axios'); @@ -26,26 +27,27 @@ jest.mock('../../utils/common', () => { }; }); -describe('useQuickstartsStates stage', () => { - const getSpy = jest.spyOn(axios.default, 'get'); - const postSpy = jest.spyOn(axios.default, 'post'); - const accountStore = createStore(() => ({ - chrome: { - user: { - identity: { - internal: { - account_id: 666, - }, - }, +const mockChromeContextValue = { + user: { + identity: { + internal: { + account_id: 666, }, }, - })); + }, + ready: true, +}; - const emptyStore = createStore(() => ({ - chrome: { - user: undefined, - }, - })); +const emptyStore = createStore(() => ({})); +const WrapperComponent = ({ children, store = emptyStore, contextValue = mockChromeContextValue }) => ( + + {children} + +); + +describe('useQuickstartsStates stage', () => { + const getSpy = jest.spyOn(axios.default, 'get'); + const postSpy = jest.spyOn(axios.default, 'post'); afterEach(() => { getSpy.mockReset(); @@ -53,7 +55,7 @@ describe('useQuickstartsStates stage', () => { }); test('should not call API if no account Id exists', () => { - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -64,7 +66,7 @@ describe('useQuickstartsStates stage', () => { test('should call quickstarts progress API if account id exists', async () => { getSpy.mockImplementationOnce(() => Promise.resolve({ data: { data: [] } })); - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; let result; await act(async () => { const { result: resultInternal } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -91,7 +93,7 @@ describe('useQuickstartsStates stage', () => { }) ); - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; let result; await act(async () => { const { result: resultInternal } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -108,7 +110,7 @@ describe('useQuickstartsStates stage', () => { }); test('should set active quickstart id', () => { - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -119,7 +121,7 @@ describe('useQuickstartsStates stage', () => { }); test('should set quickstarts states from object', () => { - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -130,7 +132,7 @@ describe('useQuickstartsStates stage', () => { }); test('should set quickstarts states from function', async () => { - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -147,7 +149,22 @@ describe('useQuickstartsStates stage', () => { progress: 'updated-state', }) ); - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => ( + + {children} + + ); const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); diff --git a/src/components/QuickStart/useQuickstartsStates.ts b/src/components/QuickStart/useQuickstartsStates.ts index f52147de3..3377bacba 100644 --- a/src/components/QuickStart/useQuickstartsStates.ts +++ b/src/components/QuickStart/useQuickstartsStates.ts @@ -1,13 +1,14 @@ import axios from 'axios'; -import { useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useContext, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; import { QuickStart, QuickStartState } from '@patternfly/quickstarts'; -import { ReduxState } from '../../redux/store'; import { populateQuickstartsCatalog } from '../../redux/actions'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; const useQuickstartsStates = () => { const dispatch = useDispatch(); - const accountId = useSelector(({ chrome }: ReduxState) => chrome?.user?.identity?.internal?.account_id); + const auth = useContext(ChromeAuthContext); + const accountId = auth.user.identity?.internal?.account_id; const [allQuickStartStates, setAllQuickStartStatesInternal] = useState<{ [key: string | number]: QuickStartState }>({}); const [activeQuickStartID, setActiveQuickStartIDInternal] = useState(''); diff --git a/src/components/RootApp/ScalprumRoot.test.js b/src/components/RootApp/ScalprumRoot.test.js index 44fcdb7e1..3bc2588a0 100644 --- a/src/components/RootApp/ScalprumRoot.test.js +++ b/src/components/RootApp/ScalprumRoot.test.js @@ -56,6 +56,7 @@ describe('ScalprumRoot', () => { getToken() { return Promise.resolve('a.a'); }, + ready: true, user: { identity: { account_number: '0', diff --git a/src/hooks/useBundleVisitDetection.ts b/src/hooks/useBundleVisitDetection.ts index eca75f3bb..2d72c98d6 100644 --- a/src/hooks/useBundleVisitDetection.ts +++ b/src/hooks/useBundleVisitDetection.ts @@ -1,10 +1,9 @@ -import { useEffect, useMemo } from 'react'; +import { useContext, useEffect, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { VisitedBundles, useVisitedBundles } from '@redhat-cloud-services/chrome'; import axios from 'axios'; -import { useSelector } from 'react-redux'; -import { ReduxState } from '../redux/store'; import { getUrl } from './useBundle'; +import ChromeAuthContext from '../auth/ChromeAuthContext'; // TMP Insights specific trigger const shouldSendVisit = (bundle: string, visits: VisitedBundles) => bundle === 'insights' && !visits[bundle]; @@ -18,7 +17,8 @@ const sendVisitedBundle = async (orgId: string) => { const useBundleVisitDetection = () => { const { pathname } = useLocation(); - const orgId = useSelector(({ chrome: { user } }: ReduxState) => user?.identity?.org_id); + const auth = useContext(ChromeAuthContext); + const orgId = auth.user?.identity?.org_id; const { markVisited, visitedBundles, initialized } = useVisitedBundles(); const bundle = useMemo(() => getUrl('bundle'), [pathname]); useEffect(() => { diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index d01f3e627..8fb974ecc 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -25,7 +25,6 @@ export function loginReducer(state: ChromeState, { payload }: { payload: ChromeU return { ...state, missingIDP, - user: payload, }; } diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index beff48101..734f03bd8 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -1,5 +1,4 @@ import { QuickStart } from '@patternfly/quickstarts'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { ChromeModule, FlagTagsFilter, NavItem, Navigation, RouteDefinition } from '../@types/types'; import { ThreeScaleError } from '../utils/responseInterceptors'; @@ -48,7 +47,6 @@ export type ChromeState = { */ appId?: string; missingIDP?: boolean; - user?: ChromeUser; pageAction?: string; pageObjectId?: string; modules?: { [key: string]: ChromeModule };