Repository navigation
fix(mobile): optimistic cloud workspace connect + last-active sort #7889
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,4 @@ | ||
| export { | ||
| getWorkspaceActivityTime, | ||
| sortDashboardSidebarProjectChildren, | ||
| sortDashboardSidebarProjects, | ||
| } from "./sortDashboardSidebarProjects"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,17 +2,18 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; | |
| import { useEffect, useMemo } from "react"; | ||
| import { AppState } from "react-native"; | ||
| import type { CloudWorkspaceRow } from "@/hooks/useCloudWorkspaces"; | ||
| import { wakeSandboxAccess } from "@/lib/sandbox-access"; | ||
| import { addressSandboxAccess, wakeSandboxAccess } from "@/lib/sandbox-access"; | ||
|
|
||
| /** | ||
| * Each mint wakes, which is also what extends the sandbox's session: it stops | ||
| * after hours of not being asked, and the next wake brings it back. Same | ||
| * cadence as the desktop's open workspace. | ||
| * A wake is also what extends the sandbox's session: it stops after hours of | ||
| * not being asked, and the next wake brings it back. Same cadence as the | ||
| * desktop's open workspace. | ||
| */ | ||
| const KEEPALIVE_MS = 10 * 60 * 1000; | ||
| const RETRY_MS = 30_000; | ||
|
|
||
| const SANDBOX_ACCESS_QUERY_KEY = ["cloud", "sandbox-access"] as const; | ||
| const SANDBOX_ADDRESS_QUERY_KEY = ["cloud", "sandbox-address"] as const; | ||
| const SANDBOX_WAKE_QUERY_KEY = ["cloud", "sandbox-wake"] as const; | ||
|
|
||
| export interface SandboxTarget { | ||
| /** The cloud workspace's id, which is also its host id. */ | ||
|
|
@@ -23,20 +24,22 @@ export interface SandboxTarget { | |
|
|
||
| export interface SandboxAccessValue { | ||
| target: SandboxTarget | null; | ||
| /** False until the workspace has been addressed once. */ | ||
| isReady: boolean; | ||
| /** True while the last attempt to address it failed; attempts continue. */ | ||
| isError: boolean; | ||
| /** True until the sandbox has been woken since this workspace opened. */ | ||
| isWaking: boolean; | ||
| retry: () => void; | ||
| } | ||
|
|
||
| /** | ||
| * Keeps the open cloud workspace addressed and awake. | ||
| * Keeps the open cloud workspace addressed and awake, the way desktop's | ||
| * SandboxAccessProvider does: the workspace is used at its last known address | ||
| * straight away, as if its box were up, and waking runs beside that rather | ||
| * than in front of it. The wake hands back a fresh address, since a resumed | ||
| * box can answer on a new domain. | ||
| * | ||
| * A sandbox has no host row and no stable URL — it is reachable only through | ||
| * a ticket this brokers. Only the workspace on screen is addressed: every | ||
| * mint wakes its sandbox, and waking the rest of a list would resume and bill | ||
| * sandboxes nobody is looking at. | ||
| * Only the workspace on screen is woken: waking the rest of a list would | ||
| * resume and bill sandboxes nobody is looking at. | ||
| * | ||
| * iOS freezes JS timers in the background, so the interval alone would come | ||
| * back to a stopped sandbox after a long sleep; returning to the foreground | ||
|
|
@@ -51,15 +54,34 @@ export function useSandboxAccess( | |
| // else, and a provisioning workspace asking for a ticket every few seconds | ||
| // would be a retry loop against a guaranteed rejection. | ||
| const ready = cloud?.status === "ready" ? cloud : null; | ||
| const addressKey = [...SANDBOX_ADDRESS_QUERY_KEY, ready?.id ?? null] as const; | ||
|
|
||
| const query = useQuery({ | ||
| queryKey: [...SANDBOX_ACCESS_QUERY_KEY, ready?.id ?? null] as const, | ||
| const address = useQuery({ | ||
| queryKey: addressKey, | ||
| enabled: ready !== null, | ||
| // The wake below re-mints every few minutes and writes its grant here. | ||
| staleTime: Number.POSITIVE_INFINITY, | ||
| networkMode: "always" as const, | ||
| queryFn: async () => { | ||
| const access = await addressSandboxAccess( | ||
| (ready as CloudWorkspaceRow).id, | ||
| ); | ||
| return { url: access.url }; | ||
| }, | ||
| refetchInterval: (current) => (current.state.data ? false : RETRY_MS), | ||
| }); | ||
|
|
||
| const wake = useQuery({ | ||
| queryKey: [...SANDBOX_WAKE_QUERY_KEY, ready?.id ?? null] as const, | ||
| // The API client batches calls made in the same tick, which would hold | ||
| // the address back until the wake returns. | ||
| enabled: ready !== null && address.data !== undefined, | ||
| // Every sheet on the workspace mounts this; a mount is not a reason to wake. | ||
| staleTime: KEEPALIVE_MS, | ||
| networkMode: "always" as const, | ||
| queryFn: async () => { | ||
| const access = await wakeSandboxAccess((ready as CloudWorkspaceRow).id); | ||
| queryClient.setQueryData(addressKey, { url: access.url }); | ||
| return { url: access.url }; | ||
| }, | ||
| // No refetchIntervalInBackground: that flag is about browser window | ||
|
|
@@ -73,13 +95,13 @@ export function useSandboxAccess( | |
| const subscription = AppState.addEventListener("change", (state) => { | ||
| if (state !== "active") return; | ||
| void queryClient.invalidateQueries({ | ||
| queryKey: SANDBOX_ACCESS_QUERY_KEY, | ||
| queryKey: SANDBOX_WAKE_QUERY_KEY, | ||
| }); | ||
| }); | ||
| return () => subscription.remove(); | ||
| }, [queryClient]); | ||
|
|
||
| const url = query.data?.url ?? null; | ||
| const url = address.data?.url ?? null; | ||
| return useMemo<SandboxAccessValue>( | ||
| () => ({ | ||
| target: | ||
|
|
@@ -90,10 +112,13 @@ export function useSandboxAccess( | |
| url, | ||
| } | ||
| : null, | ||
| isReady: ready === null || query.isFetched, | ||
| isError: query.isError, | ||
| retry: () => void query.refetch(), | ||
| isError: address.isError, | ||
| isWaking: ready !== null && wake.data === undefined, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Track completion of each wake, not the presence of cached wake data. After one successful wake, 🤖 Prompt for AI Agents |
||
| retry: () => { | ||
| void address.refetch(); | ||
| void wake.refetch(); | ||
|
Comment on lines
+115
to
+119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: set -eu
printf '%s\n' '--- useSandboxAccess ---'
sed -n '1,190p' apps/mobile/hooks/useSandboxAccess/useSandboxAccess.ts
printf '%s\n' '--- useWorkspaceHost ---'
sed -n '1,150p' apps/mobile/hooks/useWorkspaceHost/useWorkspaceHost.ts
printf '%s\n' '--- WorkspaceScreen consumer ---'
sed -n '1000,1065p' 'apps/mobile/screens/(authenticated)/workspace/[id]/WorkspaceScreen/WorkspaceScreen.tsx'
printf '%s\n' '--- relevant PR diff ---'
git diff --unified=25 8a9f712c187a9c2a2980b86849857058f983764b 7c475030d9b162ba46131038e8f58db33957b31f -- apps/mobile/hooks/useSandboxAccess/useSandboxAccess.ts apps/mobile/hooks/useWorkspaceHost/useWorkspaceHost.ts 'apps/mobile/screens/(authenticated)/workspace/[id]/WorkspaceScreen/WorkspaceScreen.tsx'Repository: superset-sh/superset Length of output: 39913 🏁 Script executed: set -eu
sed -n '1,190p' apps/mobile/hooks/useSandboxAccess/useSandboxAccess.ts
sed -n '1,150p' apps/mobile/hooks/useWorkspaceHost/useWorkspaceHost.ts
sed -n '1000,1065p' 'apps/mobile/screens/(authenticated)/workspace/[id]/WorkspaceScreen/WorkspaceScreen.tsx'
git diff --unified=25 8a9f712c187a9c2a2980b86849857058f983764b 7c475030d9b162ba46131038e8f58db33957b31f -- apps/mobile/hooks/useSandboxAccess/useSandboxAccess.ts apps/mobile/hooks/useWorkspaceHost/useWorkspaceHost.ts 'apps/mobile/screens/(authenticated)/workspace/[id]/WorkspaceScreen/WorkspaceScreen.tsx'Repository: superset-sh/superset Length of output: 39805 Gate the synthesized host with the combined sandbox error. On a first wake failure, Report the wake error only when no wake data exists. Then use that raw combined error before creating Suggested fixdiff --git a/apps/mobile/hooks/useSandboxAccess/useSandboxAccess.ts b/apps/mobile/hooks/useSandboxAccess/useSandboxAccess.ts
@@
- isError: address.isError,
+ isError:
+ address.isError || (wake.isError && wake.data === undefined),
@@
- [ready, url, address.isError, address.refetch, wake.data, wake.refetch],
+ [
+ ready,
+ url,
+ address.isError,
+ address.refetch,
+ wake.data,
+ wake.isError,
+ wake.refetch,
+ ],
);
}
diff --git a/apps/mobile/hooks/useWorkspaceHost/useWorkspaceHost.ts b/apps/mobile/hooks/useWorkspaceHost/useWorkspaceHost.ts
@@
- const host: OrgHost | null = sandbox
+ const host: OrgHost | null = sandbox && !sandboxUnreachable
? {
organizationId: cloud.organizationId,
machineId: cloud.id,
@@
workspace,
host,
cloud,
- sandboxUnreachable: !host && sandboxUnreachable,
+ sandboxUnreachable,🤖 Prompt for AI Agents |
||
| }, | ||
| }), | ||
| [ready, url, query.isFetched, query.isError, query.refetch], | ||
| [ready, url, address.isError, address.refetch, wake.data, wake.refetch], | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| export { | ||
| addressSandboxAccess, | ||
| clearSandboxAccess, | ||
| ensureSandboxAccess, | ||
| getSandboxAccess, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Start waking without waiting for the address response.
The wake query stays disabled until
address.dataexists. A slow address request therefore delays the wake by its full duration. This defeats the PR’s stated goal of starting the wake alongside address acquisition. Schedule the requests independently while keeping the wake request from batching with and delaying the address response.🤖 Prompt for AI Agents