From aacbd3d30f1f78e041308cdb02ec8147af556951 Mon Sep 17 00:00:00 2001 From: Nishant Bangarwa Date: Fri, 12 Jun 2026 20:01:04 +0530 Subject: [PATCH] fix: poll GetProject while hibernating so waking auto-redirects A hibernated project has no deployment, so the GetProject query's refetchInterval returned false and disabled polling. The only bridge out of the hibernating screen was a single refetch in RedeployProjectCTA; if it raced ahead of the backend's visible state, polling never re-armed and the UI stayed stuck on the "Waking..."/provisioning screen. Keep polling while a loaded project has no deployment so the layout auto-progresses to provisioning and then the project home once a deployment appears, whether woken from this tab or elsewhere. --- .../projects/project-query-options.spec.ts | 46 +++++++++++++++++++ .../projects/project-query-options.ts | 14 ++++-- 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 web-admin/src/features/projects/project-query-options.spec.ts diff --git a/web-admin/src/features/projects/project-query-options.spec.ts b/web-admin/src/features/projects/project-query-options.spec.ts new file mode 100644 index 000000000000..a9ca10ab2b64 --- /dev/null +++ b/web-admin/src/features/projects/project-query-options.spec.ts @@ -0,0 +1,46 @@ +import { + V1DeploymentStatus, + type V1GetProjectResponse, +} from "@rilldata/web-admin/client"; +import { RUNTIME_ACCESS_TOKEN_DEFAULT_TTL } from "@rilldata/web-common/runtime-client/constants"; +import { describe, expect, it } from "vitest"; +import { baseGetProjectQueryOptions } from "./project-query-options"; + +const refetchInterval = baseGetProjectQueryOptions.refetchInterval; + +function poll(data: V1GetProjectResponse | undefined) { + if (typeof refetchInterval !== "function") { + throw new Error("expected refetchInterval to be a function"); + } + return refetchInterval({ + state: { data }, + } as unknown as Parameters[0]); +} + +describe("baseGetProjectQueryOptions.refetchInterval", () => { + it("polls while a loaded project is hibernating (no deployment)", () => { + expect(poll({ project: { name: "p" } })).toBe(2000); + }); + + it("polls quickly while the deployment is pending", () => { + expect( + poll({ + project: { name: "p" }, + deployment: { status: V1DeploymentStatus.DEPLOYMENT_STATUS_PENDING }, + }), + ).toBe(1000); + }); + + it("refetches the JWT proactively while running", () => { + expect( + poll({ + project: { name: "p" }, + deployment: { status: V1DeploymentStatus.DEPLOYMENT_STATUS_RUNNING }, + }), + ).toBe(RUNTIME_ACCESS_TOKEN_DEFAULT_TTL / 2); + }); + + it("does not poll when there is no data (initial load or error)", () => { + expect(poll(undefined)).toBe(false); + }); +}); diff --git a/web-admin/src/features/projects/project-query-options.ts b/web-admin/src/features/projects/project-query-options.ts index 012c09240c9d..d24cf296550c 100644 --- a/web-admin/src/features/projects/project-query-options.ts +++ b/web-admin/src/features/projects/project-query-options.ts @@ -9,14 +9,15 @@ import type { CreateQueryOptions } from "@tanstack/svelte-query"; const PollTimeWhenProjectDeploymentPending = 1000; const PollTimeWhenProjectDeploymentError = 5000; const PollTimeWhenProjectDeploymentOk = RUNTIME_ACCESS_TOKEN_DEFAULT_TTL / 2; // Proactively refetch the JWT before it expires +const PollTimeWhenProjectHibernating = 2000; export const baseGetProjectQueryOptions: Partial< CreateQueryOptions > = { gcTime: Math.min(RUNTIME_ACCESS_TOKEN_DEFAULT_TTL, 1000 * 60 * 5), // Make sure we don't keep a stale JWT in the cache refetchInterval: (query) => { - const status = query.state.data?.deployment?.status; - switch (status) { + const data = query.state.data; + switch (data?.deployment?.status) { case V1DeploymentStatus.DEPLOYMENT_STATUS_PENDING: case V1DeploymentStatus.DEPLOYMENT_STATUS_UPDATING: case V1DeploymentStatus.DEPLOYMENT_STATUS_STOPPING: @@ -26,7 +27,14 @@ export const baseGetProjectQueryOptions: Partial< case V1DeploymentStatus.DEPLOYMENT_STATUS_RUNNING: return PollTimeWhenProjectDeploymentOk; default: - return false; + // A loaded project with no deployment is hibernating. Keep polling so the + // layout auto-progresses once a deployment appears, whether the wake was + // initiated from this tab or elsewhere. Without this, a wake gets stuck on + // the hibernating/"Waking..." screen if the post-wake refetch raced ahead of + // the backend's visible state and polling never re-armed. + return data?.project && !data.deployment + ? PollTimeWhenProjectHibernating + : false; } }, refetchIntervalInBackground: true, // Keep polling while the tab is hidden (e.g. deploy loader)