From 47096b6553b8d1004242b9530b8e5c81a0245068 Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Mon, 22 Dec 2025 12:11:48 -0500 Subject: [PATCH 01/14] fix: canvas to org navigation --- web-admin/src/routes/+layout.ts | 8 ---- web-admin/src/routes/-/embed/+layout.svelte | 49 ++++++++++++--------- web-admin/src/routes/-/embed/+layout.ts | 10 ----- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/web-admin/src/routes/+layout.ts b/web-admin/src/routes/+layout.ts index 539ea2d20a7..2a4917187e3 100644 --- a/web-admin/src/routes/+layout.ts +++ b/web-admin/src/routes/+layout.ts @@ -136,14 +136,6 @@ export const load = async ({ params, url, route, depends }) => { runtime: runtimeData, } = await fetchProjectDeploymentDetails(organization, project, token); - await runtime.setRuntime( - queryClient, - runtimeData.host ?? "", - runtimeData.instanceId, - runtimeData.jwt?.token, - runtimeData.jwt?.authContext, - ); - return { user, organizationPermissions, diff --git a/web-admin/src/routes/-/embed/+layout.svelte b/web-admin/src/routes/-/embed/+layout.svelte index 86636872ca7..6af876fc44c 100644 --- a/web-admin/src/routes/-/embed/+layout.svelte +++ b/web-admin/src/routes/-/embed/+layout.svelte @@ -19,13 +19,15 @@ } from "@rilldata/web-common/lib/rpc"; import ErrorPage from "@rilldata/web-common/components/ErrorPage.svelte"; import type { PageData } from "./$types"; + import RuntimeProvider from "@rilldata/web-common/runtime-client/RuntimeProvider.svelte"; export let data: PageData; const { instanceId, - missingRequireParams, navigationEnabled, + runtimeHost, + accessToken, } = data; const { dashboardChat } = featureFlags; @@ -109,25 +111,32 @@ fatal /> {:else} - {#if showTopBar} -
- -
- {/if} + + {#if showTopBar} +
+ +
+ {/if} -
-
- +
+
+ +
+ {#if $dashboardChat && activeResource?.kind === ResourceKind.Explore} + + {/if}
- {#if $dashboardChat && activeResource?.kind === ResourceKind.Explore} - - {/if} -
+ {/if} diff --git a/web-admin/src/routes/-/embed/+layout.ts b/web-admin/src/routes/-/embed/+layout.ts index 58f66473381..e7e440f7586 100644 --- a/web-admin/src/routes/-/embed/+layout.ts +++ b/web-admin/src/routes/-/embed/+layout.ts @@ -2,8 +2,6 @@ import { EmbedStore } from "@rilldata/web-common/features/embeds/embed-store.ts" import { removeEmbedParams } from "@rilldata/web-admin/features/embeds/init-embed-public-api.ts"; import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors.ts"; import { redirect } from "@sveltejs/kit"; -import { runtime } from "@rilldata/web-common/runtime-client/runtime-store.ts"; -import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient.ts"; export const load = async ({ url }) => { const embedStore = EmbedStore.getInstance(); @@ -36,14 +34,6 @@ export const load = async ({ url }) => { visibleExplores, } = embedStore; - await runtime.setRuntime( - queryClient, - runtimeHost, - instanceId, - accessToken, - "embed", - ); - return { instanceId, runtimeHost, From 92d6be5f65e6d9570ade2056172c01ac9598feef Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Mon, 22 Dec 2025 12:23:22 -0500 Subject: [PATCH 02/14] remove unused import --- web-admin/src/routes/+layout.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web-admin/src/routes/+layout.ts b/web-admin/src/routes/+layout.ts index 2a4917187e3..ef9d4f9f761 100644 --- a/web-admin/src/routes/+layout.ts +++ b/web-admin/src/routes/+layout.ts @@ -23,7 +23,6 @@ import { fetchProjectDeploymentDetails } from "@rilldata/web-admin/features/proj import { getOrgWithBearerToken } from "@rilldata/web-admin/features/public-urls/get-org-with-bearer-token"; import { initPosthog } from "@rilldata/web-common/lib/analytics/posthog"; import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient.js"; -import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; import { error, redirect, type Page } from "@sveltejs/kit"; import { isAxiosError } from "axios"; import { Settings } from "luxon"; From 2e7d42299bbdf6edfc0d71eb94d8722da45fa3f8 Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Mon, 22 Dec 2025 13:45:24 -0500 Subject: [PATCH 03/14] add test --- web-admin/tests/README.md | 2 +- web-admin/tests/canvas.spec.ts | 19 +++++++++++ web-admin/tests/setup/setup.ts | 62 ++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/web-admin/tests/README.md b/web-admin/tests/README.md index 43058531dbf..555554d68d0 100644 --- a/web-admin/tests/README.md +++ b/web-admin/tests/README.md @@ -11,7 +11,7 @@ Playwright recommends that global setup takes place in a dedicated Playwright "p 1. Starts a fresh instance of Rill Cloud 2. Logs-in via the e2e-admin@rilldata.com user (which has been pre-populated in our Auth0 staging database) 3. Creates an organization named `e2e` -4. Deploys the OpenRTB project +4. Deploys the OpenRTB and AdBids projects 5. Waits for data ingestion and asserts when the primary dashboard is ready-to-go ```bash diff --git a/web-admin/tests/canvas.spec.ts b/web-admin/tests/canvas.spec.ts index fae68017dc5..63c50d1c6ca 100644 --- a/web-admin/tests/canvas.spec.ts +++ b/web-admin/tests/canvas.spec.ts @@ -15,4 +15,23 @@ test.describe("Canvases", () => { page.getByText("Advertising Spend Overall $3,900"), ).toBeVisible(); }); + + test("can switch projects from canvas", async ({ page }) => { + // Navigate to canvas + await page.goto("/e2e/openrtb/-/dashboards"); + await page + .getByRole("link", { name: "Bids Canvas Dashboard" }) + .first() + .click(); + + // navigate via breadcrumbs to another project + await page + .getByRole("button", { name: "Breadcrumb dropdown" }) + .first() + .click(); + + await page.getByRole("link", { name: "AdBids" }).click(); + + await expect(page.getByText("Adbids Canvas Dashboard")).toBeVisible(); + }); }); diff --git a/web-admin/tests/setup/setup.ts b/web-admin/tests/setup/setup.ts index 09f68cbdb7f..74b52b727e1 100644 --- a/web-admin/tests/setup/setup.ts +++ b/web-admin/tests/setup/setup.ts @@ -261,4 +261,66 @@ setup.describe("global setup", () => { }), ).toContainText("Last refreshed", { timeout: 15_000 }); }); + + setup("should deploy the AdBids project", async ({ adminPage }) => { + // increase project quota for the organization + const { stdout: quotaUpdateStdout } = await execAsync( + `rill sudo quota set --org ${RILL_ORG_NAME} --projects 10`, + ); + expect(quotaUpdateStdout).toContain(`Projects: 10`); + + // Deploy the AdBids project + const { match } = await spawnAndMatch( + "rill", + [ + "deploy", + "--path", + "../web-common/tests/projects/AdBids", + "--project", + "AdBids", + "--archive", + "--interactive=false", + ], + /https?:\/\/[^\s]+/, + ); + + // Navigate to the project URL and expect to see the successful deployment + const url = match[0]; + await adminPage.goto(url); + await expect( + adminPage.getByRole("link", { name: RILL_ORG_NAME }), + ).toBeVisible(); // Organization breadcrumb + await expect(adminPage.getByRole("link", { name: "AdBids" })).toBeVisible(); // Project breadcrumb + + // Expect to land on the project home page + await adminPage.waitForURL(`/${RILL_ORG_NAME}/AdBids`); + // Temporary fix to wait for the project to be ready. + // TODO: add a refetch to the project API + await expect + .poll( + async () => { + await adminPage.reload(); + return adminPage.getByLabel("Project title").textContent(); + }, + { intervals: Array(4).fill(30_000), timeout: 120_000 }, + ) + .toContain(`Welcome to Untitled Rill Project`); + + // Navigate to the dashboards page to validate the deployment + await adminPage.getByRole("link", { name: "Dashboards" }).click(); + await adminPage.waitForURL("**/-/dashboards"); + + // Wait for the project to be ready + await expect(adminPage.getByLabel("Container title")).toHaveText( + "Project dashboards", + ); + + // Check that the dashboards are listed + await expect( + adminPage.getByRole("link", { name: "Adbids Canvas Dashboard" }).first(), + ).toBeVisible(); + await expect( + adminPage.getByRole("link", { name: "Adbids dashboard" }), + ).toBeVisible(); + }); }); From b4e434b4bb19ecccf64fbecf3b04453bd0b8508b Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Mon, 22 Dec 2025 16:14:34 -0500 Subject: [PATCH 04/14] test fix --- web-admin/tests/setup/setup.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web-admin/tests/setup/setup.ts b/web-admin/tests/setup/setup.ts index 74b52b727e1..ce2ddd7dd07 100644 --- a/web-admin/tests/setup/setup.ts +++ b/web-admin/tests/setup/setup.ts @@ -290,7 +290,9 @@ setup.describe("global setup", () => { await expect( adminPage.getByRole("link", { name: RILL_ORG_NAME }), ).toBeVisible(); // Organization breadcrumb - await expect(adminPage.getByRole("link", { name: "AdBids" })).toBeVisible(); // Project breadcrumb + await expect( + adminPage.getByRole("link", { name: "AdBids", exact: true }), + ).toBeVisible(); // Project breadcrumb // Expect to land on the project home page await adminPage.waitForURL(`/${RILL_ORG_NAME}/AdBids`); From ede5105491d398bb1a4ea221ab7ba548de556344 Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Tue, 23 Dec 2025 17:35:55 -0500 Subject: [PATCH 05/14] cleanup --- .../features/dashboards/listing/selectors.ts | 1 + web-admin/src/routes/+layout.ts | 1 + .../routes/-/embed/canvas/[name]/+page.svelte | 8 ++++--- .../[project]/-/open-query/+page.ts | 5 +++-- .../[project]/canvas/[dashboard]/+page.svelte | 8 ++++--- .../canvas/CanvasInitialization.svelte | 2 ++ .../features/canvas/CanvasLoadingState.svelte | 6 +++--- .../src/features/canvas/CanvasProvider.svelte | 2 ++ web-common/src/features/canvas/selector.ts | 2 +- .../src/features/connectors/code-utils.ts | 2 +- .../features/explore-mappers/open-query.ts | 21 ++++++++++++------- .../routes/(viz)/canvas/[name]/+page.svelte | 8 ++++--- 12 files changed, 42 insertions(+), 24 deletions(-) diff --git a/web-admin/src/features/dashboards/listing/selectors.ts b/web-admin/src/features/dashboards/listing/selectors.ts index 7f28a786b57..3a546c84320 100644 --- a/web-admin/src/features/dashboards/listing/selectors.ts +++ b/web-admin/src/features/dashboards/listing/selectors.ts @@ -42,6 +42,7 @@ export function useDashboards( select: (data) => { return data.resources.filter((res) => res.canvas || res.explore); }, + enabled: !!instanceId, refetchInterval: createSmartRefetchInterval, }, }); diff --git a/web-admin/src/routes/+layout.ts b/web-admin/src/routes/+layout.ts index ef9d4f9f761..6f30244d33f 100644 --- a/web-admin/src/routes/+layout.ts +++ b/web-admin/src/routes/+layout.ts @@ -23,6 +23,7 @@ import { fetchProjectDeploymentDetails } from "@rilldata/web-admin/features/proj import { getOrgWithBearerToken } from "@rilldata/web-admin/features/public-urls/get-org-with-bearer-token"; import { initPosthog } from "@rilldata/web-common/lib/analytics/posthog"; import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient.js"; +import { runtime } from "@rilldata/web-common/runtime-client/runtime-store.js"; import { error, redirect, type Page } from "@sveltejs/kit"; import { isAxiosError } from "axios"; import { Settings } from "luxon"; diff --git a/web-admin/src/routes/-/embed/canvas/[name]/+page.svelte b/web-admin/src/routes/-/embed/canvas/[name]/+page.svelte index a2ca814dd0b..ceffe8243ce 100644 --- a/web-admin/src/routes/-/embed/canvas/[name]/+page.svelte +++ b/web-admin/src/routes/-/embed/canvas/[name]/+page.svelte @@ -8,6 +8,8 @@ $: ({ canvasName, instanceId, navigationEnabled } = data); - - - +{#key instanceId} + + + +{/key} diff --git a/web-admin/src/routes/[organization]/[project]/-/open-query/+page.ts b/web-admin/src/routes/[organization]/[project]/-/open-query/+page.ts index e686e560d6c..f07610ec7fd 100644 --- a/web-admin/src/routes/[organization]/[project]/-/open-query/+page.ts +++ b/web-admin/src/routes/[organization]/[project]/-/open-query/+page.ts @@ -3,12 +3,13 @@ import type { PageLoad } from "./$types"; export const load: PageLoad = async ({ params, url, parent }) => { // Only proceed once the runtime in parent is ready - await parent(); + const parentData = await parent(); // Get the organization and project from the URL const organization = params.organization; const project = params.project; + const instanceId = parentData.runtime.instanceId; // Open the query (this'll redirect to the relevant Explore page) - await openQuery({ url, organization, project }); + await openQuery({ url, organization, project, instanceId }); }; diff --git a/web-admin/src/routes/[organization]/[project]/canvas/[dashboard]/+page.svelte b/web-admin/src/routes/[organization]/[project]/canvas/[dashboard]/+page.svelte index 0d51586e43a..6342cd40fc4 100644 --- a/web-admin/src/routes/[organization]/[project]/canvas/[dashboard]/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/canvas/[dashboard]/+page.svelte @@ -13,6 +13,8 @@ $: ({ instanceId } = $runtime); - - - +{#key instanceId} + + + +{/key} diff --git a/web-common/src/features/canvas/CanvasInitialization.svelte b/web-common/src/features/canvas/CanvasInitialization.svelte index a03bc7d2fbd..5b44bca537e 100644 --- a/web-common/src/features/canvas/CanvasInitialization.svelte +++ b/web-common/src/features/canvas/CanvasInitialization.svelte @@ -85,6 +85,7 @@ fetchedCanvas, isReconciling, existingStore, + instanceId, ); $: ready = !!resolvedStore; @@ -146,6 +147,7 @@ fetchedCanvas: V1ResolveCanvasResponse | undefined, isReconciling: boolean, existingStore: CanvasStore | undefined, + instanceId: string, ) { if (fetchedCanvas && !isReconciling) { const metricsViews: Record = {}; diff --git a/web-common/src/features/canvas/CanvasLoadingState.svelte b/web-common/src/features/canvas/CanvasLoadingState.svelte index 0ffc39bdecb..d852ea47c87 100644 --- a/web-common/src/features/canvas/CanvasLoadingState.svelte +++ b/web-common/src/features/canvas/CanvasLoadingState.svelte @@ -10,9 +10,7 @@
- {#if ready} - - {:else if errorMessage} + {#if errorMessage} {:else if isLoading} + {:else if ready} + {:else}
+ + diff --git a/web-common/src/features/canvas/selector.ts b/web-common/src/features/canvas/selector.ts index ca581c5d792..b42eac3c4e1 100644 --- a/web-common/src/features/canvas/selector.ts +++ b/web-common/src/features/canvas/selector.ts @@ -125,7 +125,7 @@ export function useCanvas( }; }, - enabled: !!canvasName, + enabled: !!canvasName && !!instanceId, ...queryOptions, }, }, diff --git a/web-common/src/features/connectors/code-utils.ts b/web-common/src/features/connectors/code-utils.ts index edb0bcb4e06..197b7150774 100644 --- a/web-common/src/features/connectors/code-utils.ts +++ b/web-common/src/features/connectors/code-utils.ts @@ -134,7 +134,7 @@ export async function updateDotEnvWithSecrets( try { const file = await queryClient.fetchQuery({ queryKey: getRuntimeServiceGetFileQueryKey(instanceId, { path: ".env" }), - queryFn: () => runtimeServiceGetFile(instanceId, { path: ".env" }), + queryFn: () => runtilayout.meServiceGetFile(instanceId, { path: ".env" }), }); blob = file.blob || ""; } catch (error) { diff --git a/web-common/src/features/explore-mappers/open-query.ts b/web-common/src/features/explore-mappers/open-query.ts index 5818e5eccf8..44900ff85ce 100644 --- a/web-common/src/features/explore-mappers/open-query.ts +++ b/web-common/src/features/explore-mappers/open-query.ts @@ -10,16 +10,16 @@ import { runtimeServiceListResources, } from "@rilldata/web-common/runtime-client"; import type { Schema as MetricsResolverQuery } from "@rilldata/web-common/runtime-client/gen/resolvers/metrics/schema.ts"; -import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; import { error, redirect } from "@sveltejs/kit"; -import { get } from "svelte/store"; export async function openQuery({ url, organization, project, + instanceId, }: { url: URL; + instanceId: string; organization?: string; project?: string; }) { @@ -49,10 +49,17 @@ export async function openQuery({ } // Find an explore dashboard that uses this metrics view - const exploreName = await findExploreForMetricsView(metricsViewName); + const exploreName = await findExploreForMetricsView( + metricsViewName, + instanceId, + ); // Convert query to ExploreState - const exploreState = await convertQueryToExploreState(query, exploreName); + const exploreState = await convertQueryToExploreState( + query, + exploreName, + instanceId, + ); // Generate the final explore URL exploreURL = await generateExploreLink( @@ -82,9 +89,8 @@ export async function openQuery({ */ async function findExploreForMetricsView( metricsViewName: string, + instanceId: string, ): Promise { - const instanceId = get(runtime).instanceId; - // List all explore resources const exploreResources = await queryClient.fetchQuery({ queryKey: getRuntimeServiceListResourcesQueryKey(instanceId, { @@ -119,9 +125,8 @@ async function findExploreForMetricsView( async function convertQueryToExploreState( query: MetricsResolverQuery, exploreName: string, + instanceId: string, ): Promise> { - const instanceId = get(runtime).instanceId; - // Get explore and metrics view specs const getExploreResponse = await queryClient.fetchQuery({ queryKey: getRuntimeServiceGetExploreQueryKey(instanceId, { diff --git a/web-local/src/routes/(viz)/canvas/[name]/+page.svelte b/web-local/src/routes/(viz)/canvas/[name]/+page.svelte index ea60dfe8220..83f6c69c117 100644 --- a/web-local/src/routes/(viz)/canvas/[name]/+page.svelte +++ b/web-local/src/routes/(viz)/canvas/[name]/+page.svelte @@ -10,6 +10,8 @@ $: ({ canvasName } = data); - - - +{#key instanceId} + + + +{/key} From 28e156452ccdb4edf0747da2fdce88cedf659c05 Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Tue, 23 Dec 2025 17:38:42 -0500 Subject: [PATCH 06/14] unintended change --- web-common/src/features/connectors/code-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-common/src/features/connectors/code-utils.ts b/web-common/src/features/connectors/code-utils.ts index 197b7150774..edb0bcb4e06 100644 --- a/web-common/src/features/connectors/code-utils.ts +++ b/web-common/src/features/connectors/code-utils.ts @@ -134,7 +134,7 @@ export async function updateDotEnvWithSecrets( try { const file = await queryClient.fetchQuery({ queryKey: getRuntimeServiceGetFileQueryKey(instanceId, { path: ".env" }), - queryFn: () => runtilayout.meServiceGetFile(instanceId, { path: ".env" }), + queryFn: () => runtimeServiceGetFile(instanceId, { path: ".env" }), }); blob = file.blob || ""; } catch (error) { From 84254b2898746d11a9aad788285c17ae6e2a0f6a Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Tue, 23 Dec 2025 17:39:00 -0500 Subject: [PATCH 07/14] remove comment --- web-common/src/features/canvas/CanvasProvider.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/web-common/src/features/canvas/CanvasProvider.svelte b/web-common/src/features/canvas/CanvasProvider.svelte index 1e87b5f1819..deca07ed3a4 100644 --- a/web-common/src/features/canvas/CanvasProvider.svelte +++ b/web-common/src/features/canvas/CanvasProvider.svelte @@ -8,7 +8,6 @@ export let projectId: string | undefined = undefined; - - From 13710b2963e68e3abb6384211759c05a940f1d84 Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Tue, 23 Dec 2025 17:39:35 -0500 Subject: [PATCH 08/14] revert change --- web-common/src/features/canvas/CanvasLoadingState.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web-common/src/features/canvas/CanvasLoadingState.svelte b/web-common/src/features/canvas/CanvasLoadingState.svelte index d852ea47c87..0ffc39bdecb 100644 --- a/web-common/src/features/canvas/CanvasLoadingState.svelte +++ b/web-common/src/features/canvas/CanvasLoadingState.svelte @@ -10,7 +10,9 @@
- {#if errorMessage} + {#if ready} + + {:else if errorMessage} {:else if isLoading} - {:else if ready} - {:else}
Date: Tue, 23 Dec 2025 17:47:28 -0500 Subject: [PATCH 09/14] remove import --- web-admin/src/routes/+layout.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web-admin/src/routes/+layout.ts b/web-admin/src/routes/+layout.ts index 6f30244d33f..ef9d4f9f761 100644 --- a/web-admin/src/routes/+layout.ts +++ b/web-admin/src/routes/+layout.ts @@ -23,7 +23,6 @@ import { fetchProjectDeploymentDetails } from "@rilldata/web-admin/features/proj import { getOrgWithBearerToken } from "@rilldata/web-admin/features/public-urls/get-org-with-bearer-token"; import { initPosthog } from "@rilldata/web-common/lib/analytics/posthog"; import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient.js"; -import { runtime } from "@rilldata/web-common/runtime-client/runtime-store.js"; import { error, redirect, type Page } from "@sveltejs/kit"; import { isAxiosError } from "axios"; import { Settings } from "luxon"; From d8e011578d1e25a6ff86fe43f41e4a9a649209ce Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Tue, 23 Dec 2025 22:40:10 -0500 Subject: [PATCH 10/14] type fix --- web-local/src/routes/(viz)/-/open-query/+page.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web-local/src/routes/(viz)/-/open-query/+page.ts b/web-local/src/routes/(viz)/-/open-query/+page.ts index 7b927a008af..1f7f3f9b4b4 100644 --- a/web-local/src/routes/(viz)/-/open-query/+page.ts +++ b/web-local/src/routes/(viz)/-/open-query/+page.ts @@ -1,6 +1,10 @@ import { openQuery } from "@rilldata/web-common/features/explore-mappers/open-query"; +import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; +import { get } from "svelte/store"; export async function load({ url }) { + const instanceId = get(runtime).instanceId; + // Open the query (this'll redirect to the relevant Explore page) - await openQuery({ url }); + await openQuery({ url, instanceId }); } From 8bab081112a82088e61e3e0bed0c83a5a3646963 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Wed, 24 Dec 2025 11:13:11 +0530 Subject: [PATCH 11/14] Fix issues with other parts of runtime --- .../[project]/-/alerts/[alert]/+layout.ts | 2 +- .../[project]/-/open-query/+page.ts | 4 +- .../[project]/-/reports/[report]/+layout.ts | 2 +- .../features/explore-mappers/open-query.ts | 160 ++++++++++++++---- web-common/src/runtime-client/http-client.ts | 3 +- .../src/runtime-client/query-options.ts | 29 +++- .../src/routes/(viz)/-/open-query/+page.ts | 4 +- 7 files changed, 156 insertions(+), 48 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/+layout.ts index e5b659c85e5..921436f721e 100644 --- a/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/+layout.ts @@ -9,7 +9,7 @@ export async function load({ params, parent }) { const alertData = await queryClient .fetchQuery( - getRuntimeServiceGetResourceQueryOptions(runtime.instanceId, { + getRuntimeServiceGetResourceQueryOptions(runtime, { "name.kind": ResourceKind.Alert, "name.name": params.alert, }), diff --git a/web-admin/src/routes/[organization]/[project]/-/open-query/+page.ts b/web-admin/src/routes/[organization]/[project]/-/open-query/+page.ts index f07610ec7fd..114286d7d87 100644 --- a/web-admin/src/routes/[organization]/[project]/-/open-query/+page.ts +++ b/web-admin/src/routes/[organization]/[project]/-/open-query/+page.ts @@ -8,8 +8,8 @@ export const load: PageLoad = async ({ params, url, parent }) => { // Get the organization and project from the URL const organization = params.organization; const project = params.project; - const instanceId = parentData.runtime.instanceId; + const runtime = parentData.runtime; // Open the query (this'll redirect to the relevant Explore page) - await openQuery({ url, organization, project, instanceId }); + await openQuery({ url, organization, project, runtime }); }; diff --git a/web-admin/src/routes/[organization]/[project]/-/reports/[report]/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/reports/[report]/+layout.ts index 84239f1d323..80f70692a77 100644 --- a/web-admin/src/routes/[organization]/[project]/-/reports/[report]/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/reports/[report]/+layout.ts @@ -9,7 +9,7 @@ export async function load({ params, parent }) { const reportData = await queryClient .fetchQuery( - getRuntimeServiceGetResourceQueryOptions(runtime.instanceId, { + getRuntimeServiceGetResourceQueryOptions(runtime, { "name.kind": ResourceKind.Report, "name.name": params.report, }), diff --git a/web-common/src/features/explore-mappers/open-query.ts b/web-common/src/features/explore-mappers/open-query.ts index 44900ff85ce..c8c8b4bdde2 100644 --- a/web-common/src/features/explore-mappers/open-query.ts +++ b/web-common/src/features/explore-mappers/open-query.ts @@ -1,25 +1,35 @@ import type { ExploreState } from "@rilldata/web-common/features/dashboards/stores/explore-state"; import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors.js"; -import { generateExploreLink } from "@rilldata/web-common/features/explore-mappers/generate-explore-link"; +import { getUrlForExplore } from "@rilldata/web-common/features/explore-mappers/generate-explore-link"; import { mapMetricsResolverQueryToDashboard } from "@rilldata/web-common/features/explore-mappers/map-metrics-resolver-query-to-dashboard.ts"; import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient"; import { + getQueryServiceMetricsViewTimeRangeQueryKey, getRuntimeServiceGetExploreQueryKey, getRuntimeServiceListResourcesQueryKey, - runtimeServiceGetExplore, - runtimeServiceListResources, + type V1ExploreSpec, + type V1GetExploreResponse, + type V1ListResourcesResponse, + type V1MetricsViewSpec, + type V1MetricsViewTimeRangeResponse, } from "@rilldata/web-common/runtime-client"; import type { Schema as MetricsResolverQuery } from "@rilldata/web-common/runtime-client/gen/resolvers/metrics/schema.ts"; import { error, redirect } from "@sveltejs/kit"; +import type { Runtime } from "@rilldata/web-common/runtime-client/runtime-store.ts"; +import httpClient from "@rilldata/web-common/runtime-client/http-client.ts"; +import { getTimeControlState } from "@rilldata/web-common/features/dashboards/time-controls/time-control-store.ts"; +import { convertPartialExploreStateToUrlParams } from "@rilldata/web-common/features/dashboards/url-state/convert-partial-explore-state-to-url-params.ts"; +import { createLinkError } from "@rilldata/web-common/features/explore-mappers/explore-validation.ts"; +import { ExploreLinkErrorType } from "@rilldata/web-common/features/explore-mappers/types.ts"; export async function openQuery({ url, organization, project, - instanceId, + runtime, }: { url: URL; - instanceId: string; + runtime: Runtime; organization?: string; project?: string; }) { @@ -50,20 +60,28 @@ export async function openQuery({ // Find an explore dashboard that uses this metrics view const exploreName = await findExploreForMetricsView( + runtime, metricsViewName, - instanceId, + ); + + const { metricsViewSpec, exploreSpec } = await getExploreSpecs( + runtime, + exploreName, ); // Convert query to ExploreState - const exploreState = await convertQueryToExploreState( + const exploreState = mapMetricsResolverQueryToDashboard( + metricsViewSpec, + exploreSpec, query, - exploreName, - instanceId, ); // Generate the final explore URL exploreURL = await generateExploreLink( + runtime, exploreState, + metricsViewSpec, + exploreSpec, exploreName, organization, project, @@ -88,20 +106,27 @@ export async function openQuery({ * TODO: try to find an explore that has as many measures/dimensions in the query */ async function findExploreForMetricsView( + runtime: Runtime, metricsViewName: string, - instanceId: string, ): Promise { // List all explore resources const exploreResources = await queryClient.fetchQuery({ - queryKey: getRuntimeServiceListResourcesQueryKey(instanceId, { + queryKey: getRuntimeServiceListResourcesQueryKey(runtime.instanceId, { kind: ResourceKind.Explore, }), queryFn: ({ signal }) => - runtimeServiceListResources( - instanceId, - { kind: ResourceKind.Explore }, + httpClient({ + url: `/v1/instances/${runtime.instanceId}/resources`, + method: "GET", + params: { kind: ResourceKind.Explore }, signal, - ), + baseUrl: runtime.host, + headers: runtime.jwt + ? { + Authorization: `Bearer ${runtime.jwt?.token}`, + } + : undefined, + }), }); // Look for an explore that references this metrics view @@ -119,27 +144,25 @@ async function findExploreForMetricsView( ); } -/** - * Convert Query directly to ExploreState without going through URL parameters. - */ -async function convertQueryToExploreState( - query: MetricsResolverQuery, - exploreName: string, - instanceId: string, -): Promise> { +async function getExploreSpecs(runtime: Runtime, exploreName: string) { // Get explore and metrics view specs const getExploreResponse = await queryClient.fetchQuery({ - queryKey: getRuntimeServiceGetExploreQueryKey(instanceId, { + queryKey: getRuntimeServiceGetExploreQueryKey(runtime.instanceId, { name: exploreName, }), queryFn: ({ signal }) => - runtimeServiceGetExplore( - instanceId, - { - name: exploreName, - }, + httpClient({ + url: `/v1/instances/${runtime.instanceId}/resources/explore`, + method: "GET", + params: { name: exploreName }, signal, - ), + baseUrl: runtime.host, + headers: runtime.jwt + ? { + Authorization: `Bearer ${runtime.jwt?.token}`, + } + : undefined, + }), }); const exploreResource = getExploreResponse.explore; const metricsViewResource = getExploreResponse.metricsView; @@ -155,8 +178,79 @@ async function convertQueryToExploreState( const metricsViewSpec = metricsViewResource?.metricsView?.state?.validSpec; const exploreSpec = exploreResource.explore.state.validSpec; - const partialExploreState: Partial = - mapMetricsResolverQueryToDashboard(metricsViewSpec, exploreSpec, query); + return { metricsViewSpec, exploreSpec }; +} - return partialExploreState; +/** + * Generates the explore page URL with proper search parameters + */ +async function generateExploreLink( + runtime: Runtime, + exploreState: Partial, + metricsViewSpec: V1MetricsViewSpec, + exploreSpec: V1ExploreSpec, + exploreName: string, + organization?: string | undefined, + project?: string | undefined, +): Promise { + try { + // Build base URL + const url = getUrlForExplore(exploreName, organization, project); + + const metricsViewName = exploreSpec.metricsView; + let fullTimeRange: V1MetricsViewTimeRangeResponse | undefined; + if (metricsViewSpec.timeDimension && metricsViewName) { + fullTimeRange = await queryClient.fetchQuery({ + queryFn: ({ signal }) => + httpClient({ + url: `/v1/instances/${runtime.instanceId}/queries/metrics-views/${metricsViewName}/time-range-summary`, + method: "POST", + headers: { + "Content-Type": "application/json", + ...(runtime.jwt + ? { + Authorization: `Bearer ${runtime.jwt?.token}`, + } + : {}), + }, + data: {}, + signal, + baseUrl: runtime.host, + }), + queryKey: getQueryServiceMetricsViewTimeRangeQueryKey( + runtime.instanceId, + metricsViewName, + {}, + ), + staleTime: Infinity, + gcTime: Infinity, + }); + } + + // This is just for an initial redirect. + // DashboardStateDataLoader will handle compression etc. during init + // So no need to use getCleanedUrlParamsForGoto + const searchParams = convertPartialExploreStateToUrlParams( + exploreSpec, + exploreState, + getTimeControlState( + metricsViewSpec, + exploreSpec, + fullTimeRange?.timeRangeSummary, + exploreState, + ), + ); + + searchParams.forEach((value, key) => { + url.searchParams.set(key, value); + }); + + return url.toString(); + } catch (error) { + throw createLinkError( + ExploreLinkErrorType.TRANSFORMATION_ERROR, + `Failed to generate explore link: ${error.message}`, + error, + ); + } } diff --git a/web-common/src/runtime-client/http-client.ts b/web-common/src/runtime-client/http-client.ts index b51c75331ea..6a5fcb1aa6e 100644 --- a/web-common/src/runtime-client/http-client.ts +++ b/web-common/src/runtime-client/http-client.ts @@ -23,9 +23,10 @@ export const httpClient = async ( ): Promise => { // Naive request interceptors + const interceptedConfig = { ...config }; // Set host const host = get(runtime).host; - const interceptedConfig = { ...config, baseUrl: host }; + if (host) interceptedConfig.baseUrl = host; // Set JWT let jwt = get(runtime).jwt; diff --git a/web-common/src/runtime-client/query-options.ts b/web-common/src/runtime-client/query-options.ts index baab2c7e692..bbc7a29cd45 100644 --- a/web-common/src/runtime-client/query-options.ts +++ b/web-common/src/runtime-client/query-options.ts @@ -1,18 +1,31 @@ import type { FetchQueryOptions } from "@tanstack/svelte-query"; -import type { RuntimeServiceGetResourceParams } from "./gen/index.schemas"; +import type { + RuntimeServiceGetResourceParams, + V1GetResourceResponse, +} from "./gen/index.schemas"; import type { RuntimeServiceGetResourceQueryResult } from "./gen/runtime-service/runtime-service"; -import { - getRuntimeServiceGetResourceQueryKey, - runtimeServiceGetResource, -} from "./gen/runtime-service/runtime-service"; +import { getRuntimeServiceGetResourceQueryKey } from "./gen/runtime-service/runtime-service"; +import httpClient from "@rilldata/web-common/runtime-client/http-client.ts"; +import type { Runtime } from "@rilldata/web-common/runtime-client/runtime-store.ts"; export function getRuntimeServiceGetResourceQueryOptions( - instanceId: string, + runtime: Runtime, params: RuntimeServiceGetResourceParams, ) { return >{ - queryKey: getRuntimeServiceGetResourceQueryKey(instanceId, params), - queryFn: () => runtimeServiceGetResource(instanceId, params), + queryKey: getRuntimeServiceGetResourceQueryKey(runtime.instanceId, params), + queryFn: () => + httpClient({ + url: `/v1/instances/${runtime.instanceId}/resource`, + method: "GET", + params, + baseUrl: runtime.host, + headers: runtime.jwt + ? { + Authorization: `Bearer ${runtime.jwt?.token}`, + } + : undefined, + }), staleTime: Infinity, }; } diff --git a/web-local/src/routes/(viz)/-/open-query/+page.ts b/web-local/src/routes/(viz)/-/open-query/+page.ts index 1f7f3f9b4b4..41913ceaffa 100644 --- a/web-local/src/routes/(viz)/-/open-query/+page.ts +++ b/web-local/src/routes/(viz)/-/open-query/+page.ts @@ -3,8 +3,8 @@ import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; import { get } from "svelte/store"; export async function load({ url }) { - const instanceId = get(runtime).instanceId; + const rt = get(runtime); // Open the query (this'll redirect to the relevant Explore page) - await openQuery({ url, instanceId }); + await openQuery({ url, runtime: rt }); } From a2ecdcf41cbeed334cdb9206235f08300d2aa956 Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Wed, 24 Dec 2025 11:26:27 -0500 Subject: [PATCH 12/14] fix baseUrl issue --- .../src/runtime-client/http-request-queue/HttpRequestQueue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-common/src/runtime-client/http-request-queue/HttpRequestQueue.ts b/web-common/src/runtime-client/http-request-queue/HttpRequestQueue.ts index 8b2a9fe4850..5158ecb3ab8 100644 --- a/web-common/src/runtime-client/http-request-queue/HttpRequestQueue.ts +++ b/web-common/src/runtime-client/http-request-queue/HttpRequestQueue.ts @@ -51,7 +51,7 @@ export class HttpRequestQueue { public add(requestOptions: FetchWrapperOptions) { // prepend after parsing to make parsing faster - requestOptions.url = `${requestOptions?.baseUrl}${requestOptions.url}`; + requestOptions.url = `${requestOptions?.baseUrl ?? ""}${requestOptions.url}`; const urlMatch = UrlExtractorRegex.exec(requestOptions.url); From e5698e396b5fea9788a00cfec248bb97f81267ba Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Thu, 25 Dec 2025 10:56:11 +0530 Subject: [PATCH 13/14] Fix waiting for feature flags --- .../[organization]/[project]/-/ai/+layout.ts | 11 +++----- web-common/src/features/feature-flags.ts | 27 ++++++++++++++++++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/ai/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/ai/+layout.ts index 93caf27dbb2..d06fb2b190a 100644 --- a/web-admin/src/routes/[organization]/[project]/-/ai/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/ai/+layout.ts @@ -2,9 +2,8 @@ import { getLastConversationId, setLastConversationId, } from "@rilldata/web-common/features/chat/layouts/fullpage/fullpage-store"; -import { featureFlags } from "@rilldata/web-common/features/feature-flags.js"; +import { getFeatureFlags } from "@rilldata/web-common/features/feature-flags.js"; import { redirect } from "@sveltejs/kit"; -import { get } from "svelte/store"; export const load = async ({ params: { organization, project, conversationId }, @@ -13,15 +12,13 @@ export const load = async ({ parent, }) => { // Wait for the feature flags to load - await parent(); + const { runtime } = await parent(); - // There is a potential race condition where feature flags from instance is not loaded yet. - // So wait until it is ready before checking for "chat" - await featureFlags.ready; + const fetchedFeatureFlags = await getFeatureFlags(runtime); // Redirect to `/-/dashboards` if chat feature is disabled // NOTE: In the future, we'll use user-level `ai` permissions for more granular access control - const chatEnabled = get(featureFlags.chat); + const chatEnabled = Boolean(fetchedFeatureFlags.chat); if (!chatEnabled) { throw redirect(307, `/${organization}/${project}/-/dashboards`); } diff --git a/web-common/src/features/feature-flags.ts b/web-common/src/features/feature-flags.ts index 9d736fcea3a..a838158aef6 100644 --- a/web-common/src/features/feature-flags.ts +++ b/web-common/src/features/feature-flags.ts @@ -2,9 +2,12 @@ import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryCl import { writable } from "svelte/store"; import { createRuntimeServiceGetInstance, + getRuntimeServiceGetInstanceQueryKey, + type V1GetInstanceResponse, type V1InstanceFeatureFlags, } from "../runtime-client"; -import { runtime } from "../runtime-client/runtime-store"; +import { type Runtime, runtime } from "../runtime-client/runtime-store"; +import httpClient from "@rilldata/web-common/runtime-client/http-client.ts"; class FeatureFlag { private _internal = false; @@ -123,3 +126,25 @@ class FeatureFlags { } export const featureFlags = new FeatureFlags(); + +export async function getFeatureFlags(runtime: Runtime) { + const instanceResp = await queryClient.fetchQuery({ + queryKey: getRuntimeServiceGetInstanceQueryKey( + runtime.instanceId, + undefined, + ), + queryFn: () => + httpClient({ + url: `/v1/instances/${runtime.instanceId}`, + method: "GET", + baseUrl: runtime.host, + headers: runtime.jwt + ? { + Authorization: `Bearer ${runtime.jwt?.token}`, + } + : undefined, + }), + }); + + return instanceResp.instance?.featureFlags ?? {}; +} From c9fe27e72f813c6a7e58201a5b02019199ce4d9e Mon Sep 17 00:00:00 2001 From: Brian Holmes Date: Tue, 6 Jan 2026 16:14:19 -0500 Subject: [PATCH 14/14] remove conditional --- .../workspaces/CanvasWorkspace.svelte | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/web-common/src/features/workspaces/CanvasWorkspace.svelte b/web-common/src/features/workspaces/CanvasWorkspace.svelte index 42459b480d5..2dfdaac1a9d 100644 --- a/web-common/src/features/workspaces/CanvasWorkspace.svelte +++ b/web-common/src/features/workspaces/CanvasWorkspace.svelte @@ -121,20 +121,18 @@ {lineBasedRuntimeErrors} /> {:else if selectedView === "viz"} - {#if ready} - - - - {/if} + + + {/if}