From 4042b8ae2a407f572258e40a9733f8be2f85f384 Mon Sep 17 00:00:00 2001 From: zzyangh <799463087@qq.com> Date: Fri, 12 Dec 2025 17:24:15 +0800 Subject: [PATCH 1/2] [fix]: Default jump to SQL workbench adjusted to touch after logging in --- packages/base/src/App.tsx | 14 +-- .../src/hooks/useNavigateToWorkbench/index.ts | 86 +++++++++++++++++ .../base/src/hooks/useSessionUser/index.tsx | 28 +++++- packages/base/src/page/BindUser/index.tsx | 93 +++++++++++++++---- packages/base/src/page/Login/index.tsx | 68 ++++++++++---- .../Nav/UserGuideModal/UserGuideModal.tsx | 51 ++-------- .../src/page/Nav/UserGuideModal/index.tsx | 19 +--- packages/base/src/store/user/index.ts | 10 +- 8 files changed, 263 insertions(+), 106 deletions(-) create mode 100644 packages/base/src/hooks/useNavigateToWorkbench/index.ts diff --git a/packages/base/src/App.tsx b/packages/base/src/App.tsx index 98c2eb73a..3578b121e 100644 --- a/packages/base/src/App.tsx +++ b/packages/base/src/App.tsx @@ -96,8 +96,8 @@ export const Wrapper: React.FC<{ return <>{!initRenderApp && children}; }; function App() { - const { token } = useSelector((state: IReduxState) => ({ - token: state.user.token + const { isAfterLoggingIn } = useSelector((state: IReduxState) => ({ + isAfterLoggingIn: !state.user.isLoggingIn && !!state.user.token })); const dispatch = useDispatch(); const { notificationContextHolder } = useNotificationContext(); @@ -160,7 +160,9 @@ function App() { userOperationPermissions ]); const elements = useRoutes( - token ? (AuthRouterConfigData as RouteObject[]) : unAuthRouterConfig + isAfterLoggingIn + ? (AuthRouterConfigData as RouteObject[]) + : unAuthRouterConfig ); useChangeTheme(); const themeData = useMemo(() => { @@ -204,10 +206,10 @@ function App() { }); }, [dispatch, fetchModuleSupportStatus, getUserBySession, updateDriverList]); useEffect(() => { - if (token) { + if (isAfterLoggingIn) { getInitialData(); } - }, [token, getInitialData]); + }, [getInitialData, isAfterLoggingIn]); useEffect(() => { i18n.changeLanguage(currentLanguage); }, [currentLanguage]); @@ -314,7 +316,7 @@ function App() { {notificationContextHolder} - {elements}}> + {elements}}> {body} diff --git a/packages/base/src/hooks/useNavigateToWorkbench/index.ts b/packages/base/src/hooks/useNavigateToWorkbench/index.ts new file mode 100644 index 000000000..d459cdb5f --- /dev/null +++ b/packages/base/src/hooks/useNavigateToWorkbench/index.ts @@ -0,0 +1,86 @@ +import { useRequest } from 'ahooks'; +import { useDispatch, useSelector } from 'react-redux'; +import { DmsApi } from '@actiontech/shared/lib/api'; +import { ResponseCode } from '@actiontech/dms-kit'; +import useRecentlySelectedZone from '@actiontech/dms-kit/es/features/useRecentlySelectedZone'; +import { IReduxState } from '../../store'; +import { updateAvailabilityZoneTips } from '../../store/availabilityZone'; + +const useNavigateToWorkbench = () => { + const dispatch = useDispatch(); + + const availabilityZoneTips = useSelector( + (state: IReduxState) => state.availabilityZone.availabilityZoneTips + ); + + const { availabilityZone, updateRecentlySelectedZone } = + useRecentlySelectedZone(); + + const { + runAsync: getAvailabilityZoneTipsAsync, + loading: getAvailabilityZoneTipsLoading + } = useRequest( + () => + DmsApi.GatewayService.GetGatewayTips().then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + return res.data.data; + } + return []; + }), + { + onSuccess: (res) => { + dispatch( + updateAvailabilityZoneTips({ + availabilityZoneTips: res ?? [] + }) + ); + }, + manual: true + } + ); + + const { + loading: navigateToWorkbenchLoading, + runAsync: navigateToWorkbenchAsync + } = useRequest( + () => { + return DmsApi.CloudBeaverService.GetSQLQueryConfiguration().then( + (res) => { + if (res.data.code === ResponseCode.SUCCESS) { + return res.data.data; + } + } + ); + }, + { + manual: true, + onSuccess: (res) => { + if ( + res?.enable_sql_query && + res.sql_query_root_uri && + res.sql_query_root_uri !== location.pathname + ) { + // 如果当前设置了可用区 并且没有最近选择的可用区记录 则设置一个默认的可用区 + if (!!availabilityZoneTips.length && !availabilityZone) { + updateRecentlySelectedZone(availabilityZoneTips[0]); + } + + // res.sql_query_root_uri !== location.pathname 防止无限刷新 + // 因为sql_query_root_uri是不携带origin的,只有pathname。所以开发环境localhost不可以直接跳转到CB + // #if [PROD] + window.location.href = res.sql_query_root_uri; + // #endif + } + } + } + ); + + return { + navigateToWorkbenchLoading, + navigateToWorkbenchAsync, + getAvailabilityZoneTipsAsync, + getAvailabilityZoneTipsLoading + }; +}; + +export default useNavigateToWorkbench; diff --git a/packages/base/src/hooks/useSessionUser/index.tsx b/packages/base/src/hooks/useSessionUser/index.tsx index 96edeb043..1eab24adf 100644 --- a/packages/base/src/hooks/useSessionUser/index.tsx +++ b/packages/base/src/hooks/useSessionUser/index.tsx @@ -4,6 +4,8 @@ import { ResponseCode } from '@actiontech/dms-kit'; import { updateUserUid } from '../../store/user'; import { useUserInfo } from '@actiontech/shared/lib/features'; import Session from '@actiontech/shared/lib/api/base/service/Session'; +import User from '@actiontech/shared/lib/api/base/service/User'; +import { GetUserSystemEnum } from '@actiontech/shared/lib/api/base/service/common.enum'; const useSessionUser = () => { const dispatch = useDispatch(); @@ -28,10 +30,34 @@ const useSessionUser = () => { } }); + const { + data: shouldNavigateToWorkbench, + runAsync: getSessionUserInfoAsync, + loading: getSessionUserSystemLoading + } = useRequest( + () => + Session.GetUserBySession({}).then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + const uid = res.data.data?.user_uid ?? ''; + return User.GetUser({ user_uid: uid }).then((resp) => { + if (resp.data.code === ResponseCode.SUCCESS) { + return resp.data.data?.system === GetUserSystemEnum.WORKBENCH; + } + }); + } + }), + { + manual: true + } + ); + return { sessionUser, getSessionUserLoading, - getUserBySession + getUserBySession, + getSessionUserInfoAsync, + shouldNavigateToWorkbench, + getSessionUserSystemLoading }; }; diff --git a/packages/base/src/page/BindUser/index.tsx b/packages/base/src/page/BindUser/index.tsx index 35056ac07..f1efb2e10 100644 --- a/packages/base/src/page/BindUser/index.tsx +++ b/packages/base/src/page/BindUser/index.tsx @@ -1,9 +1,15 @@ -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { + useCallback, + useEffect, + useMemo, + useRef, + startTransition +} from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { Typography, Form } from 'antd'; import { OauthLoginFormFields } from './index.type'; -import { updateToken } from '../../store/user'; +import { updateToken, updateIsLoggingIn } from '../../store/user'; import { ResponseCode } from '@actiontech/dms-kit'; import OAuth2 from '@actiontech/shared/lib/api/base/service/OAuth2'; import LoginLayout from '../Login/components/LoginLayout'; @@ -24,6 +30,8 @@ import { StorageKey, CompanyNoticeDisplayStatusEnum } from '@actiontech/dms-kit'; +import useSessionUser from '../../hooks/useSessionUser'; +import useNavigateToWorkbench from '../../hooks/useNavigateToWorkbench'; const BindUser = () => { const navigate = useTypedNavigate(); const { baseTheme } = useThemeStyleData(); @@ -35,28 +43,65 @@ const BindUser = () => { }, [extractQueries]); useBrowserVersionTips(); const loginLock = useRef(false); + + const { getSessionUserInfoAsync, getSessionUserSystemLoading } = + useSessionUser(); + + const { + navigateToWorkbenchAsync, + getAvailabilityZoneTipsAsync, + navigateToWorkbenchLoading, + getAvailabilityZoneTipsLoading + } = useNavigateToWorkbench(); + const concatToken = (token = '') => { if (!token) { return ''; } return `Bearer ${token}`; }; + const navigateToTarget = useCallback(() => { - const encodedTarget = urlParams?.target; - if (encodedTarget) { - const decoded = decodeURIComponent(encodedTarget); - const [path, targetParams] = decoded.split('?'); - if (targetParams) { - navigate(`${path}?${targetParams}`); - } else if (path.endsWith('cloud-beaver')) { - navigate(`${path}?${OPEN_CLOUD_BEAVER_URL_PARAM_NAME}=true`); + dispatch(updateIsLoggingIn(true)); + getSessionUserInfoAsync().then((shouldNavigateToWorkbench) => { + if (shouldNavigateToWorkbench) { + // #if [ee] + getAvailabilityZoneTipsAsync().then(() => { + navigateToWorkbenchAsync().then(() => { + dispatch(updateIsLoggingIn(false)); + }); + }); + // #else + navigateToWorkbenchAsync().then(() => { + dispatch(updateIsLoggingIn(false)); + }); + // #endif } else { - navigate(path); + const encodedTarget = urlParams?.target; + if (encodedTarget) { + const decoded = decodeURIComponent(encodedTarget); + const [path, targetParams] = decoded.split('?'); + if (targetParams) { + navigate(`${path}?${targetParams}`); + } else if (path.endsWith('cloud-beaver')) { + navigate(`${path}?${OPEN_CLOUD_BEAVER_URL_PARAM_NAME}=true`); + } else { + navigate(path); + } + } else { + navigate(ROUTE_PATHS.BASE.HOME); + } + dispatch(updateIsLoggingIn(false)); } - } else { - navigate(ROUTE_PATHS.BASE.HOME); - } - }, [navigate, urlParams]); + }); + }, [ + dispatch, + getSessionUserInfoAsync, + getAvailabilityZoneTipsAsync, + navigateToWorkbenchAsync, + urlParams, + navigate + ]); const login = (values: OauthLoginFormFields) => { const oauth2Token = urlParams?.oauth2_token; loginLock.current = true; @@ -67,7 +112,13 @@ const BindUser = () => { duration: 0 }); loginLock.current = false; - navigate(ROUTE_PATHS.BASE.LOGIN.index.path); + // 使用startTransition的原因如下: + // login 函数是表单的 onFinish 回调,属于同步用户交互事件 + // navigate 可能会触发懒加载组件(Suspense) + // React 18 不允许在同步事件中直接触发 Suspense,否则会抛出错误 + startTransition(() => { + navigate(ROUTE_PATHS.BASE.LOGIN.index.path); + }); return; } OAuth2.BindOauth2User({ @@ -134,9 +185,15 @@ const BindUser = () => { urlParams?.error, urlParams?.user_exist ]); + const isLoading = + loginLock.current || + getSessionUserSystemLoading || + getAvailabilityZoneTipsLoading || + navigateToWorkbenchLoading; + return ( -
+ { block htmlType="submit" className="login-btn" - loading={loginLock.current} + loading={isLoading} > {t('dmsLogin.oauth.submitButton')} diff --git a/packages/base/src/page/Login/index.tsx b/packages/base/src/page/Login/index.tsx index e5faf933a..2e6bfecd0 100644 --- a/packages/base/src/page/Login/index.tsx +++ b/packages/base/src/page/Login/index.tsx @@ -1,7 +1,7 @@ import { message, Form, Tabs } from 'antd'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; -import { updateToken } from '../../store/user'; +import { updateToken, updateIsLoggingIn } from '../../store/user'; import LoginLayout from './components/LoginLayout'; import { EmptyBox } from '@actiontech/dms-kit'; import { useTypedNavigate, useTypedQuery } from '@actiontech/shared'; @@ -23,6 +23,9 @@ import VerificationCodeForm from './components/VerificationCodeForm'; import OAuth2LoginForm from './components/OAuth2LoginForm'; import { DmsApi } from '@actiontech/shared/lib/api'; import { useState, useMemo, useEffect } from 'react'; +import useSessionUser from '../../hooks/useSessionUser'; +import useNavigateToWorkbench from '../../hooks/useNavigateToWorkbench'; + const Login = () => { const { t } = useTranslation(); useBrowserVersionTips(); @@ -44,6 +47,16 @@ const Login = () => { const navigate = useTypedNavigate(); const extractQueries = useTypedQuery(); + const { getSessionUserInfoAsync, getSessionUserSystemLoading } = + useSessionUser(); + + const { + navigateToWorkbenchAsync, + getAvailabilityZoneTipsAsync, + navigateToWorkbenchLoading, + getAvailabilityZoneTipsLoading + } = useNavigateToWorkbench(); + const { data: oauthConfig, run: getOauth2Tips } = useRequest( () => { return DmsApi.OAuth2Service.GetOauth2Tips().then( @@ -64,6 +77,7 @@ const Login = () => { const addSession = () => { const loginFormValues = loginForm.getFieldsValue(); const verificationCodeFormValues = verificationCodeForm.getFieldsValue(); + dispatch(updateIsLoggingIn(true)); DmsApi.SessionService.AddSession({ session: { username: loginFormValues.username, @@ -86,22 +100,39 @@ const Login = () => { token }) ); - const encodedTarget = extractQueries( - ROUTE_PATHS.BASE.LOGIN.index - )?.target; - if (encodedTarget) { - const decoded = decodeURIComponent(encodedTarget); - const [path, targetParams] = decoded.split('?'); - if (targetParams) { - navigate(`${path}?${targetParams}`); - } else if (path.endsWith('cloud-beaver')) { - navigate(`${path}?${OPEN_CLOUD_BEAVER_URL_PARAM_NAME}=true`); + getSessionUserInfoAsync().then((shouldNavigateToWorkbench) => { + if (shouldNavigateToWorkbench) { + // #if [ee] + getAvailabilityZoneTipsAsync().then(() => { + navigateToWorkbenchAsync().then(() => { + dispatch(updateIsLoggingIn(false)); + }); + }); + // #else + navigateToWorkbenchAsync().then(() => { + dispatch(updateIsLoggingIn(false)); + }); + // #endif } else { - navigate(path); + const encodedTarget = extractQueries( + ROUTE_PATHS.BASE.LOGIN.index + )?.target; + if (encodedTarget) { + const decoded = decodeURIComponent(encodedTarget); + const [path, targetParams] = decoded.split('?'); + if (targetParams) { + navigate(`${path}?${targetParams}`); + } else if (path.endsWith('cloud-beaver')) { + navigate(`${path}?${OPEN_CLOUD_BEAVER_URL_PARAM_NAME}=true`); + } else { + navigate(path); + } + } else { + navigate(ROUTE_PATHS.BASE.HOME); + } + dispatch(updateIsLoggingIn(false)); } - } else { - navigate(ROUTE_PATHS.BASE.HOME); - } + }); } // #if [ee] LocalStorageWrapper.set( @@ -169,7 +200,12 @@ const Login = () => { hidden={allowVerificationCode} form={loginForm} onSubmit={login} - loading={loading} + loading={ + loading || + getSessionUserSystemLoading || + getAvailabilityZoneTipsLoading || + navigateToWorkbenchLoading + } /> {/* #if [ee] */} diff --git a/packages/base/src/page/Nav/UserGuideModal/UserGuideModal.tsx b/packages/base/src/page/Nav/UserGuideModal/UserGuideModal.tsx index 48cf16d22..c38a4f35b 100644 --- a/packages/base/src/page/Nav/UserGuideModal/UserGuideModal.tsx +++ b/packages/base/src/page/Nav/UserGuideModal/UserGuideModal.tsx @@ -14,9 +14,7 @@ import { useDispatch } from 'react-redux'; import { updateSystemPreference } from '../../../store/user'; import UserGuideContent from './UserGuideContent'; import { Typography } from 'antd'; -import useRecentlySelectedZone from '@actiontech/dms-kit/es/features/useRecentlySelectedZone'; -import { useSelector } from 'react-redux'; -import { IReduxState } from '../../../store'; +import useNavigateToWorkbench from '../../../hooks/useNavigateToWorkbench'; const UserGuideModal: React.FC = () => { const { t } = useTranslation(); @@ -25,47 +23,10 @@ const UserGuideModal: React.FC = () => { const dispatch = useDispatch(); - const availabilityZoneTips = useSelector( - (state: IReduxState) => state.availabilityZone.availabilityZoneTips - ); - - const { availabilityZone, updateRecentlySelectedZone } = - useRecentlySelectedZone(); - const [system, setSystem] = useState(GetUserSystemEnum.MANAGEMENT); - const { loading: openCloudBeaverLoading, run: openCloudBeaver } = useRequest( - () => { - return DmsApi.CloudBeaverService.GetSQLQueryConfiguration().then( - (res) => { - if (res.data.code === ResponseCode.SUCCESS) { - return res.data.data; - } - } - ); - }, - { - onSuccess: (res) => { - if ( - res?.enable_sql_query && - res.sql_query_root_uri && - res.sql_query_root_uri !== location.pathname - ) { - // 如果当前设置了可用区 并且没有最近选择的可用区记录 则设置一个默认的可用区 - if (!!availabilityZoneTips.length && !availabilityZone) { - updateRecentlySelectedZone(availabilityZoneTips[0]); - } - - // res.sql_query_root_uri !== location.pathname 防止无限刷新 - // 因为sql_query_root_uri是不携带origin的,只有pathname。所以开发环境localhost不可以直接跳转到CB - // #if [PROD] - window.location.href = res.sql_query_root_uri; - // #endif - } - }, - ready: systemPreference === GetUserSystemEnum.WORKBENCH - } - ); + const { navigateToWorkbenchLoading, navigateToWorkbenchAsync } = + useNavigateToWorkbench(); const { loading: updateCurrentUserSystemLoading, @@ -79,7 +40,7 @@ const UserGuideModal: React.FC = () => { }).then((res) => { if (res.data.code === ResponseCode.SUCCESS) { if (system === GetUserSystemEnum.WORKBENCH) { - openCloudBeaver(); + navigateToWorkbenchAsync(); } } }), @@ -103,7 +64,7 @@ const UserGuideModal: React.FC = () => { system={system} onSystemChange={(e) => setSystem(e.target.value)} onConfirm={updateCurrentUserSystem} - loading={updateCurrentUserSystemLoading || openCloudBeaverLoading} + loading={updateCurrentUserSystemLoading || navigateToWorkbenchLoading} /> {t('dmsMenu.userGuide.description')} @@ -114,7 +75,7 @@ const UserGuideModal: React.FC = () => { size="large" onClick={updateCurrentUserSystem} className="primary-button" - loading={updateCurrentUserSystemLoading || openCloudBeaverLoading} + loading={updateCurrentUserSystemLoading || navigateToWorkbenchLoading} > {t('dmsMenu.userGuide.confirmButton')} diff --git a/packages/base/src/page/Nav/UserGuideModal/index.tsx b/packages/base/src/page/Nav/UserGuideModal/index.tsx index 6f9d48ec3..a638ee06f 100644 --- a/packages/base/src/page/Nav/UserGuideModal/index.tsx +++ b/packages/base/src/page/Nav/UserGuideModal/index.tsx @@ -1,24 +1,7 @@ -import { useMemo } from 'react'; import UserGuideModal from './UserGuideModal'; -import queryString from 'query-string'; -import { - EmptyBox, - SQL_WORKBENCH_FROM_PARAM_NAME, - ODC_WORKBENCH_NAME -} from '@actiontech/dms-kit'; const UserGuide = () => { - const isNotFormODC = useMemo(() => { - const parsedQuery = queryString.parse(location.search); - - return parsedQuery[SQL_WORKBENCH_FROM_PARAM_NAME] !== ODC_WORKBENCH_NAME; - }, []); - - return ( - - - - ); + return ; }; export default UserGuide; diff --git a/packages/base/src/store/user/index.ts b/packages/base/src/store/user/index.ts index 4c3bd4675..7042e9953 100644 --- a/packages/base/src/store/user/index.ts +++ b/packages/base/src/store/user/index.ts @@ -26,6 +26,7 @@ type UserReduxState = { isUserInfoFetched: boolean; language: SupportLanguage; systemPreference?: GetUserSystemEnum; + isLoggingIn: boolean; }; const initialState: UserReduxState = { @@ -44,7 +45,8 @@ const initialState: UserReduxState = { StorageKey.Language, DEFAULT_LANGUAGE ) as SupportLanguage, - systemPreference: undefined + systemPreference: undefined, + isLoggingIn: false }; const user = createSlice({ @@ -118,6 +120,9 @@ const user = createSlice({ }: PayloadAction<{ systemPreference?: GetUserSystemEnum }> ) => { state.systemPreference = systemPreference; + }, + updateIsLoggingIn: (state, { payload }: PayloadAction) => { + state.isLoggingIn = payload; } } }); @@ -131,7 +136,8 @@ export const { updateManagementPermissions, updateUserUid, updateUserInfoFetchStatus, - updateSystemPreference + updateSystemPreference, + updateIsLoggingIn } = user.actions; export default user.reducer; From 3adc9ea819ce075d732910dbc8fa71dd12d66547 Mon Sep 17 00:00:00 2001 From: zzyangh <799463087@qq.com> Date: Fri, 12 Dec 2025 17:24:32 +0800 Subject: [PATCH 2/2] [test]: Update unit tests --- packages/base/src/App.ce.test.tsx | 3 +- packages/base/src/App.test.tsx | 12 +- .../useNavigateToWorkbench/index.test.tsx | 100 ++++++++++++++ .../src/hooks/useSessionUser/index.test.tsx | 20 +++ .../base/src/page/BindUser/index.ce.test.tsx | 50 ++++++- .../base/src/page/BindUser/index.test.tsx | 117 +++++++++++++++- .../Login/__snapshots__/index.test.tsx.snap | 8 +- .../base/src/page/Login/index.ce.test.tsx | 37 +++++- packages/base/src/page/Login/index.test.tsx | 125 +++++++++++++++++- .../__tests__/UserGuideModal.test.tsx | 114 +++------------- .../UserGuideModal/__tests__/index.test.tsx | 15 --- packages/base/src/store/user/index.test.ts | 51 +++++-- .../base/src/testUtils/mockHooks/data.tsx | 16 +++ .../mockHooks/mockUseNavigateToWorkbench.ts | 13 ++ .../testUtils/mockHooks/mockUseSessionUser.ts | 13 ++ 15 files changed, 541 insertions(+), 153 deletions(-) create mode 100644 packages/base/src/hooks/useNavigateToWorkbench/index.test.tsx create mode 100644 packages/base/src/testUtils/mockHooks/mockUseNavigateToWorkbench.ts create mode 100644 packages/base/src/testUtils/mockHooks/mockUseSessionUser.ts diff --git a/packages/base/src/App.ce.test.tsx b/packages/base/src/App.ce.test.tsx index c7a2a8bf1..8ea0013a9 100644 --- a/packages/base/src/App.ce.test.tsx +++ b/packages/base/src/App.ce.test.tsx @@ -59,7 +59,8 @@ describe('test App ce', () => { (useSelector as jest.Mock).mockImplementation((selector) => { return selector({ user: { - token: 'AAh32ffdswt' + token: 'AAh32ffdswt', + isLoggingIn: false }, nav: { modalStatus: { diff --git a/packages/base/src/App.test.tsx b/packages/base/src/App.test.tsx index 76b405ec1..b01430f44 100644 --- a/packages/base/src/App.test.tsx +++ b/packages/base/src/App.test.tsx @@ -93,7 +93,8 @@ describe('App', () => { (useSelector as jest.Mock).mockImplementation((selector) => { return selector({ user: { - token: 'AAh32ffdswt' + token: 'AAh32ffdswt', + isLoggingIn: false }, nav: { modalStatus: { @@ -137,7 +138,8 @@ describe('App', () => { (useSelector as jest.Mock).mockImplementation((selector) => { return selector({ user: { - token: '' + token: '', + isLoggingIn: false } }); }); @@ -210,12 +212,16 @@ describe('App', () => { (useSelector as jest.Mock).mockImplementation((selector) => { return selector({ user: { - token: '' + token: '', + isLoggingIn: false }, nav: { modalStatus: { [ModalName.Company_Notice]: false } + }, + availabilityZone: { + availabilityZoneTips: [] } }); }); diff --git a/packages/base/src/hooks/useNavigateToWorkbench/index.test.tsx b/packages/base/src/hooks/useNavigateToWorkbench/index.test.tsx new file mode 100644 index 000000000..e9e86c316 --- /dev/null +++ b/packages/base/src/hooks/useNavigateToWorkbench/index.test.tsx @@ -0,0 +1,100 @@ +import { act, cleanup } from '@testing-library/react'; +import { superRenderHook } from '@actiontech/shared/lib/testUtil/superRender'; +import { useDispatch, useSelector } from 'react-redux'; +import { + baseMockApi, + createSpySuccessResponse +} from '@actiontech/shared/lib/testUtil/mockApi'; +import useNavigateToWorkbench from '.'; +import { mockUseRecentlySelectedZone } from '../../testUtils/mockHooks/mockUseRecentlySelectedZone'; +import { mockGatewayTipsData } from '@actiontech/shared/lib/testUtil/mockApi/base/gateway/data'; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), + useSelector: jest.fn() +})); + +describe('useNavigateToWorkbench', () => { + const dispatchSpy = jest.fn(); + let getGatewayTipsSpy: jest.SpyInstance; + let getSQLQueryConfigurationSpy: jest.SpyInstance; + + beforeEach(() => { + (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); + (useSelector as jest.Mock).mockImplementation((selector) => { + return selector({ + availabilityZone: { + availabilityZoneTips: mockGatewayTipsData + } + }); + }); + mockUseRecentlySelectedZone(); + getGatewayTipsSpy = baseMockApi.gateway.getGatewayTips(); + getSQLQueryConfigurationSpy = baseMockApi.cloudBeaver.getSqlQueryUrl(); + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + jest.clearAllMocks(); + cleanup(); + }); + + it('should initialize with correct default values', () => { + const { result } = superRenderHook(() => useNavigateToWorkbench()); + + expect(result.current.navigateToWorkbenchLoading).toBeFalsy(); + expect(result.current.getAvailabilityZoneTipsLoading).toBeFalsy(); + expect(typeof result.current.navigateToWorkbenchAsync).toBe('function'); + expect(typeof result.current.getAvailabilityZoneTipsAsync).toBe('function'); + }); + + it('should fetch availability zone tips successfully', async () => { + const { result } = superRenderHook(() => useNavigateToWorkbench()); + expect(result.current.getAvailabilityZoneTipsLoading).toBeFalsy(); + + act(() => { + result.current.getAvailabilityZoneTipsAsync(); + }); + expect(result.current.getAvailabilityZoneTipsLoading).toBeTruthy(); + await act(async () => jest.advanceTimersByTime(3000)); + expect(result.current.getAvailabilityZoneTipsLoading).toBeFalsy(); + + expect(getGatewayTipsSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledWith({ + type: 'availabilityZone/updateAvailabilityZoneTips', + payload: { availabilityZoneTips: mockGatewayTipsData } + }); + }); + + it('should fetch SQL query configuration successfully', async () => { + const mockUpdateRecentlySelectedZone = jest.fn(); + getSQLQueryConfigurationSpy.mockImplementation(() => + createSpySuccessResponse({ + data: { + enable_sql_query: true, + sql_query_root_uri: '/cloudbeaver' + } + }) + ); + mockUseRecentlySelectedZone({ + availabilityZone: undefined, + updateRecentlySelectedZone: mockUpdateRecentlySelectedZone + }); + const { result } = superRenderHook(() => useNavigateToWorkbench()); + expect(result.current.navigateToWorkbenchLoading).toBeFalsy(); + + act(() => { + result.current.navigateToWorkbenchAsync(); + }); + expect(result.current.navigateToWorkbenchLoading).toBeTruthy(); + await act(async () => jest.advanceTimersByTime(3000)); + expect(result.current.navigateToWorkbenchLoading).toBeFalsy(); + + expect(getSQLQueryConfigurationSpy).toHaveBeenCalledTimes(1); + expect(mockUpdateRecentlySelectedZone).toHaveBeenCalledWith( + mockGatewayTipsData[0] + ); + }); +}); diff --git a/packages/base/src/hooks/useSessionUser/index.test.tsx b/packages/base/src/hooks/useSessionUser/index.test.tsx index b25b02264..c787a7341 100644 --- a/packages/base/src/hooks/useSessionUser/index.test.tsx +++ b/packages/base/src/hooks/useSessionUser/index.test.tsx @@ -55,4 +55,24 @@ describe('useSessionUser', () => { }); expect(result.current.getSessionUserLoading).toBeFalsy(); }); + + it('should get user system data', async () => { + const { result } = superRenderHook(() => useSessionUser(), undefined, { + initStore: { + user: { uid: 'test' } + } + }); + expect(result.current.getSessionUserSystemLoading).toBeFalsy(); + expect(result.current.shouldNavigateToWorkbench).toEqual(undefined); + + await act(async () => { + result.current.getSessionUserInfoAsync(); + await jest.advanceTimersByTime(3000); + }); + expect(getUserBySessionSpy).toHaveBeenCalledTimes(1); + expect(getCurrentUserSpy).toHaveBeenCalledTimes(1); + await act(async () => jest.advanceTimersByTime(3000)); + expect(result.current.getSessionUserSystemLoading).toBeFalsy(); + expect(result.current.shouldNavigateToWorkbench).toBeFalsy(); + }); }); diff --git a/packages/base/src/page/BindUser/index.ce.test.tsx b/packages/base/src/page/BindUser/index.ce.test.tsx index 25b41458f..a1990fd3b 100644 --- a/packages/base/src/page/BindUser/index.ce.test.tsx +++ b/packages/base/src/page/BindUser/index.ce.test.tsx @@ -11,6 +11,8 @@ import { getBySelector } from '@actiontech/shared/lib/testUtil/customQuery'; import { eventEmitter } from '@actiontech/dms-kit/es/utils/EventEmitter'; import EmitterKey from '@actiontech/dms-kit/es/data/EmitterKey'; import { ROUTE_PATHS } from '@actiontech/dms-kit'; +import { mockUseSessionUser } from '../../testUtils/mockHooks/mockUseSessionUser'; +import { mockUseNavigateToWorkbench } from '../../testUtils/mockHooks/mockUseNavigateToWorkbench'; jest.mock('react-router-dom', () => { return { @@ -27,6 +29,10 @@ jest.mock('react-redux', () => ({ describe('page/BindUser-ce', () => { const navigateSpy = jest.fn(); const dispatchSpy = jest.fn(); + const getSessionUserInfoAsyncSpy = jest.fn(() => Promise.resolve(false)); + const navigateToWorkbenchAsyncSpy = jest.fn(() => Promise.resolve(undefined)); + const getAvailabilityZoneTipsAsyncSpy = jest.fn(() => Promise.resolve([])); + const customRender = (path = '/user/bind') => { return baseSuperRender(, undefined, { routerProps: { initialEntries: [path] } @@ -38,6 +44,24 @@ describe('page/BindUser-ce', () => { (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); jest.useFakeTimers(); dms.mockAllApi(); + + getSessionUserInfoAsyncSpy + .mockClear() + .mockImplementation(() => Promise.resolve(false)); + getAvailabilityZoneTipsAsyncSpy + .mockClear() + .mockImplementation(() => Promise.resolve([])); + navigateToWorkbenchAsyncSpy + .mockClear() + .mockImplementation(() => Promise.resolve(undefined)); + + mockUseSessionUser({ + getSessionUserInfoAsync: getSessionUserInfoAsyncSpy + }); + mockUseNavigateToWorkbench({ + navigateToWorkbenchAsync: navigateToWorkbenchAsyncSpy, + getAvailabilityZoneTipsAsync: getAvailabilityZoneTipsAsyncSpy + }); }); afterEach(() => { @@ -137,13 +161,22 @@ describe('page/BindUser-ce', () => { user_name: 'oauth2_admin', pwd: 'oauth2_admin' }); - expect(dispatchSpy).toHaveBeenCalledTimes(1); - expect(dispatchSpy).toHaveBeenCalledWith({ + expect(dispatchSpy).toHaveBeenCalledTimes(3); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { type: 'user/updateToken', payload: { token: 'Bearer token' } }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'user/updateIsLoggingIn', + payload: true + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(3, { + type: 'user/updateIsLoggingIn', + payload: false + }); + expect(getSessionUserInfoAsyncSpy).toHaveBeenCalledTimes(1); expect(navigateSpy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalledWith('/'); }); @@ -204,13 +237,22 @@ describe('page/BindUser-ce', () => { const search = `user_exist=true&dms_token=111111`; customRender(`/user/bind?${search}`); await act(async () => jest.advanceTimersByTime(300)); - expect(dispatchSpy).toHaveBeenCalledTimes(1); - expect(dispatchSpy).toHaveBeenCalledWith({ + expect(dispatchSpy).toHaveBeenCalledTimes(3); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { type: 'user/updateToken', payload: { token: 'Bearer 111111' } }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'user/updateIsLoggingIn', + payload: true + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(3, { + type: 'user/updateIsLoggingIn', + payload: false + }); + expect(getSessionUserInfoAsyncSpy).toHaveBeenCalledTimes(1); expect(navigateSpy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalledWith('/'); }); diff --git a/packages/base/src/page/BindUser/index.test.tsx b/packages/base/src/page/BindUser/index.test.tsx index ec1646b49..804db14b1 100644 --- a/packages/base/src/page/BindUser/index.test.tsx +++ b/packages/base/src/page/BindUser/index.test.tsx @@ -14,6 +14,8 @@ import { import { ROUTE_PATHS } from '@actiontech/dms-kit'; import BindUser from '.'; +import { mockUseSessionUser } from '../../testUtils/mockHooks/mockUseSessionUser'; +import { mockUseNavigateToWorkbench } from '../../testUtils/mockHooks/mockUseNavigateToWorkbench'; jest.mock('react-router-dom', () => { return { @@ -30,6 +32,10 @@ jest.mock('react-redux', () => ({ describe('page/BindUser-ee', () => { const navigateSpy = jest.fn(); const dispatchSpy = jest.fn(); + const getSessionUserInfoAsyncSpy = jest.fn(() => Promise.resolve(false)); + const navigateToWorkbenchAsyncSpy = jest.fn(() => Promise.resolve(undefined)); + const getAvailabilityZoneTipsAsyncSpy = jest.fn(() => Promise.resolve([])); + const customRender = (path = '/user/bind') => { return baseSuperRender(, undefined, { routerProps: { initialEntries: [path] } @@ -41,6 +47,24 @@ describe('page/BindUser-ee', () => { (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); jest.useFakeTimers(); dms.mockAllApi(); + + getSessionUserInfoAsyncSpy + .mockClear() + .mockImplementation(() => Promise.resolve(false)); + getAvailabilityZoneTipsAsyncSpy + .mockClear() + .mockImplementation(() => Promise.resolve([])); + navigateToWorkbenchAsyncSpy + .mockClear() + .mockImplementation(() => Promise.resolve(undefined)); + + mockUseSessionUser({ + getSessionUserInfoAsync: getSessionUserInfoAsyncSpy + }); + mockUseNavigateToWorkbench({ + navigateToWorkbenchAsync: navigateToWorkbenchAsyncSpy, + getAvailabilityZoneTipsAsync: getAvailabilityZoneTipsAsyncSpy + }); }); afterEach(() => { @@ -142,13 +166,22 @@ describe('page/BindUser-ee', () => { pwd: 'oauth2_admin' }); await act(async () => jest.advanceTimersByTime(300)); - expect(dispatchSpy).toHaveBeenCalledTimes(1); - expect(dispatchSpy).toHaveBeenCalledWith({ + expect(dispatchSpy).toHaveBeenCalledTimes(3); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { type: 'user/updateToken', payload: { token: 'Bearer token' } }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'user/updateIsLoggingIn', + payload: true + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(3, { + type: 'user/updateIsLoggingIn', + payload: false + }); + expect(getSessionUserInfoAsyncSpy).toHaveBeenCalledTimes(1); expect(navigateSpy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalledWith('/'); expect(LocalStorageWrapperSet).toHaveBeenCalled(); @@ -187,7 +220,22 @@ describe('page/BindUser-ee', () => { await act(async () => jest.advanceTimersByTime(3000)); expect(requestFn).toHaveBeenCalled(); await act(async () => jest.advanceTimersByTime(300)); - expect(dispatchSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledTimes(3); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { + type: 'user/updateToken', + payload: { + token: 'Bearer token' + } + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'user/updateIsLoggingIn', + payload: true + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(3, { + type: 'user/updateIsLoggingIn', + payload: false + }); + expect(getSessionUserInfoAsyncSpy).toHaveBeenCalledTimes(1); expect(navigateSpy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalledWith('/project/test'); expect(LocalStorageWrapperSet).toHaveBeenCalled(); @@ -222,7 +270,22 @@ describe('page/BindUser-ee', () => { await act(async () => jest.advanceTimersByTime(3000)); expect(requestFn).toHaveBeenCalled(); await act(async () => jest.advanceTimersByTime(300)); - expect(dispatchSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledTimes(3); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { + type: 'user/updateToken', + payload: { + token: 'Bearer token' + } + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'user/updateIsLoggingIn', + payload: true + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(3, { + type: 'user/updateIsLoggingIn', + payload: false + }); + expect(getSessionUserInfoAsyncSpy).toHaveBeenCalledTimes(1); expect(navigateSpy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalledWith('/project/test?active=overview'); expect(LocalStorageWrapperSet).toHaveBeenCalled(); @@ -257,7 +320,22 @@ describe('page/BindUser-ee', () => { await act(async () => jest.advanceTimersByTime(3000)); expect(requestFn).toHaveBeenCalled(); await act(async () => jest.advanceTimersByTime(300)); - expect(dispatchSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledTimes(3); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { + type: 'user/updateToken', + payload: { + token: 'Bearer token' + } + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'user/updateIsLoggingIn', + payload: true + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(3, { + type: 'user/updateIsLoggingIn', + payload: false + }); + expect(getSessionUserInfoAsyncSpy).toHaveBeenCalledTimes(1); expect(navigateSpy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalledWith( '/cloud-beaver?open_cloud_beaver=true' @@ -321,13 +399,22 @@ describe('page/BindUser-ee', () => { const search = `user_exist=true&dms_token=111111`; customRender(`/user/bind?${search}`); await act(async () => jest.advanceTimersByTime(300)); - expect(dispatchSpy).toHaveBeenCalledTimes(1); - expect(dispatchSpy).toHaveBeenCalledWith({ + expect(dispatchSpy).toHaveBeenCalledTimes(3); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { type: 'user/updateToken', payload: { token: 'Bearer 111111' } }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'user/updateIsLoggingIn', + payload: true + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(3, { + type: 'user/updateIsLoggingIn', + payload: false + }); + expect(getSessionUserInfoAsyncSpy).toHaveBeenCalledTimes(1); expect(navigateSpy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalledWith('/'); }); @@ -339,6 +426,22 @@ describe('page/BindUser-ee', () => { )}`; customRender(`/user/bind?${search}`); await act(async () => jest.advanceTimersByTime(300)); + expect(dispatchSpy).toHaveBeenCalledTimes(3); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { + type: 'user/updateToken', + payload: { + token: 'Bearer oauth2_token_val' + } + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'user/updateIsLoggingIn', + payload: true + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(3, { + type: 'user/updateIsLoggingIn', + payload: false + }); + expect(getSessionUserInfoAsyncSpy).toHaveBeenCalledTimes(1); expect(navigateSpy).toHaveBeenCalled(); expect(navigateSpy).toHaveBeenCalledWith('/project/test'); }); diff --git a/packages/base/src/page/Login/__snapshots__/index.test.tsx.snap b/packages/base/src/page/Login/__snapshots__/index.test.tsx.snap index 39279d83c..8769033d8 100644 --- a/packages/base/src/page/Login/__snapshots__/index.test.tsx.snap +++ b/packages/base/src/page/Login/__snapshots__/index.test.tsx.snap @@ -804,7 +804,7 @@ exports[`page/Login-ee render login success when has location search val render `; -exports[`page/Login-ee render login success when has location search val render with other search val 1`] = ` +exports[`page/Login-ee render login success when has location search val render with cloud-beaver path 1`] = `