From 89d5b11e0463928f4b159b7e45ae20e41f48c552 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Fri, 26 May 2023 16:26:45 +1000 Subject: [PATCH] Fix "set username" screen (#6824) * Fix cloud-v2/#432 * Delay setting backend to local backend; don't list directory if user is not enabled * Add a way to debug specific dashboard paths * Fix bug * Check resources and status immediately --- .../src/authentication/providers/auth.tsx | 32 +++++++++++++------ .../src/authentication/src/components/app.tsx | 23 ++++++------- .../src/dashboard/components/dashboard.tsx | 21 +++++++++--- .../components/projectActionButton.tsx | 7 +++- 4 files changed, 56 insertions(+), 27 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/auth.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/auth.tsx index 9de5aa889dfb..4c196051f40c 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/auth.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/auth.tsx @@ -140,7 +140,6 @@ const AuthContext = react.createContext({} as AuthContextType) /** Props for an {@link AuthProvider}. */ export interface AuthProviderProps { authService: authServiceModule.AuthService - platform: platformModule.Platform /** Callback to execute once the user has authenticated successfully. */ onAuthenticated: () => void children: react.ReactNode @@ -148,13 +147,12 @@ export interface AuthProviderProps { /** A React provider for the Cognito API. */ export function AuthProvider(props: AuthProviderProps) { - const { authService, platform, children } = props + const { authService, onAuthenticated, children } = props const { cognito } = authService const { session } = sessionProvider.useSession() const { setBackend } = backendProvider.useSetBackend() const logger = loggerProvider.useLogger() const navigate = router.useNavigate() - const onAuthenticated = react.useCallback(props.onAuthenticated, []) const [initialized, setInitialized] = react.useState(false) const [userSession, setUserSession] = react.useState(null) @@ -174,7 +172,9 @@ export function AuthProvider(props: AuthProviderProps) { headers.append('Authorization', `Bearer ${accessToken}`) const client = new http.Client(headers) const backend = new remoteBackend.RemoteBackend(client, logger) - if (platform === platformModule.Platform.cloud) { + // The backend MUST be the remote backend before login is finished. + // This is because the "set username" flow requires the remote backend. + if (!initialized || userSession == null) { setBackend(backend) } const organization = await backend.usersMe().catch(() => null) @@ -326,6 +326,7 @@ export function AuthProvider(props: AuthProviderProps) { } const signOut = async () => { + setInitialized(false) await cognito.signOut() toast.success(MESSAGES.signOutSuccess) return true @@ -387,6 +388,16 @@ export function useAuth() { return react.useContext(AuthContext) } +// =============================== +// === shouldPreventNavigation === +// =============================== + +/** True if navigation should be prevented, for debugging purposes. */ +function getShouldPreventNavigation() { + const location = router.useLocation() + return new URLSearchParams(location.search).get('prevent-navigation') === 'true' +} + // ======================= // === ProtectedLayout === // ======================= @@ -394,10 +405,11 @@ export function useAuth() { /** A React Router layout route containing routes only accessible by users that are logged in. */ export function ProtectedLayout() { const { session } = useAuth() + const shouldPreventNavigation = getShouldPreventNavigation() - if (!session) { + if (!shouldPreventNavigation && !session) { return - } else if (session.type === UserSessionType.partial) { + } else if (!shouldPreventNavigation && session?.type === UserSessionType.partial) { return } else { return @@ -412,8 +424,9 @@ export function ProtectedLayout() { * in the process of registering. */ export function SemiProtectedLayout() { const { session } = useAuth() + const shouldPreventNavigation = getShouldPreventNavigation() - if (session?.type === UserSessionType.full) { + if (!shouldPreventNavigation && session?.type === UserSessionType.full) { return } else { return @@ -428,10 +441,11 @@ export function SemiProtectedLayout() { * not logged in. */ export function GuestLayout() { const { session } = useAuth() + const shouldPreventNavigation = getShouldPreventNavigation() - if (session?.type === UserSessionType.partial) { + if (!shouldPreventNavigation && session?.type === UserSessionType.partial) { return - } else if (session?.type === UserSessionType.full) { + } else if (!shouldPreventNavigation && session?.type === UserSessionType.full) { return } else { return diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/components/app.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/components/app.tsx index 34888dc6007a..a7bb605bec00 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/components/app.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/components/app.tsx @@ -39,7 +39,6 @@ import * as router from 'react-router-dom' import * as toast from 'react-hot-toast' import * as authService from '../authentication/service' -import * as localBackend from '../dashboard/localBackend' import * as platformModule from '../platform' import * as authProvider from '../authentication/providers/auth' @@ -122,8 +121,14 @@ function App(props: AppProps) { * because the {@link AppRouter} relies on React hooks, which can't be used in the same React * component as the component that defines the provider. */ function AppRouter(props: AppProps) { - const { logger, platform, showDashboard, onAuthenticated } = props + const { logger, showDashboard, onAuthenticated } = props const navigate = router.useNavigate() + // FIXME[sb]: After platform detection for Electron is merged in, `IS_DEV_MODE` should be + // set to true on `ide watch`. + if (IS_DEV_MODE) { + // @ts-expect-error This is used exclusively for debugging. + window.navigate = navigate + } const mainPageUrl = new URL(window.location.href) const memoizedAuthService = react.useMemo(() => { const authConfig = { navigate, ...props } @@ -164,20 +169,12 @@ function AppRouter(props: AppProps) { userSession={userSession} registerAuthEventListener={registerAuthEventListener} > - + {/* This is safe, because the backend is always set by the authentication flow. */} + {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} + {routes} diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx index 68467555ba9d..8f11acad8e55 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx @@ -278,9 +278,17 @@ function Dashboard(props: DashboardProps) { backendModule.Asset[] >([]) + const canListDirectory = + backend.platform !== platformModule.Platform.cloud || organization.isEnabled const directory = directoryStack[directoryStack.length - 1] const parentDirectory = directoryStack[directoryStack.length - 2] + react.useEffect(() => { + if (platform === platformModule.Platform.desktop) { + setBackend(new localBackend.LocalBackend()) + } + }, []) + react.useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { if ( @@ -398,6 +406,7 @@ function Dashboard(props: DashboardProps) { { setProject(null) }} @@ -603,10 +612,14 @@ function Dashboard(props: DashboardProps) { hooks.useAsyncEffect( null, async signal => { - const assets = await backend.listDirectory({ parentId: directoryId }) - if (!signal.aborted) { + if (canListDirectory) { + const assets = await backend.listDirectory({ parentId: directoryId }) + if (!signal.aborted) { + setIsLoadingAssets(false) + setAssets(assets) + } + } else { setIsLoadingAssets(false) - setAssets(assets) } }, [accessToken, directoryId, refresh, backend] @@ -728,7 +741,7 @@ function Dashboard(props: DashboardProps) { query={query} setQuery={setQuery} /> - {backend.platform === platformModule.Platform.cloud && !organization.isEnabled ? ( + {!canListDirectory ? (
We will review your user details and enable the cloud experience for you diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectActionButton.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectActionButton.tsx index 587c694c692d..4c410d7d78df 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectActionButton.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectActionButton.tsx @@ -43,11 +43,12 @@ export interface ProjectActionButtonProps { appRunner: AppRunner | null onClose: () => void openIde: () => void + doRefresh: () => void } /** An interactive button displaying the status of a project. */ function ProjectActionButton(props: ProjectActionButtonProps) { - const { project, onClose, appRunner, openIde } = props + const { project, onClose, appRunner, openIde, doRefresh } = props const { backend } = backendProvider.useBackend() const [state, setState] = react.useState(backendModule.ProjectState.created) @@ -101,6 +102,7 @@ function ProjectActionButton(props: ProjectActionButtonProps) { () => void checkProjectStatus(), CHECK_STATUS_INTERVAL_MS ) + void checkProjectStatus() return () => { clearInterval(handle) } @@ -132,6 +134,7 @@ function ProjectActionButton(props: ProjectActionButtonProps) { () => void checkProjectResources(), CHECK_RESOURCES_INTERVAL_MS ) + void checkProjectResources() return () => { clearInterval(handle) } @@ -159,10 +162,12 @@ function ProjectActionButton(props: ProjectActionButtonProps) { switch (backend.platform) { case platform.Platform.cloud: await backend.openProject(project.id) + doRefresh() setIsCheckingStatus(true) break case platform.Platform.desktop: await backend.openProject(project.id) + doRefresh() setState(backendModule.ProjectState.opened) setSpinnerState(SpinnerState.done) break