diff --git a/web-common/src/features/canvas/CanvasPreviewCTAs.svelte b/web-common/src/features/canvas/CanvasPreviewCTAs.svelte
index b22945466a0..d66e99c571b 100644
--- a/web-common/src/features/canvas/CanvasPreviewCTAs.svelte
+++ b/web-common/src/features/canvas/CanvasPreviewCTAs.svelte
@@ -4,6 +4,11 @@
import { useRuntimeClient } from "../../runtime-client/v2";
import { featureFlags } from "../feature-flags";
import ChatToggle from "@rilldata/web-common/features/chat/layouts/sidebar/ChatToggle.svelte";
+ import ViewAsButton from "../dashboards/granular-access-policies/ViewAsButton.svelte";
+ import {
+ useDashboardPolicyCheck,
+ useRillYamlPolicyCheck,
+ } from "../dashboards/granular-access-policies/useSecurityPolicyCheck";
const client = useRuntimeClient();
@@ -12,14 +17,34 @@
$: canvasQuery = useCanvas(client, canvasName);
$: canvasFilePath = $canvasQuery.data?.filePath ?? "";
+ $: canvasPolicyCheck = useDashboardPolicyCheck(client, canvasFilePath);
+ $: rillYamlPolicyCheck = useRillYamlPolicyCheck(client);
+
+ // Check if any metrics view referenced by this canvas has security rules
+ $: referencedMetricsViewsHavePolicy = Object.values(
+ $canvasQuery.data?.metricsViews ?? {},
+ ).some((mv) => (mv?.state?.validSpec?.securityRules?.length ?? 0) > 0);
+
+ $: hasSecurityPolicy =
+ $canvasPolicyCheck.data ||
+ $rillYamlPolicyCheck.data ||
+ referencedMetricsViewsHavePolicy;
+
const { dashboardChat, readOnly } = featureFlags;
+
+ $: hasAnyContent = hasSecurityPolicy || $dashboardChat || !$readOnly;
-{#if $dashboardChat}
-
-{/if}
-{#if !$readOnly}
+{#if hasAnyContent}
-
+ {#if hasSecurityPolicy}
+
+ {/if}
+ {#if $dashboardChat}
+
+ {/if}
+ {#if !$readOnly}
+
+ {/if}
{/if}
diff --git a/web-common/src/features/dashboards/granular-access-policies/updateDevJWT.ts b/web-common/src/features/dashboards/granular-access-policies/updateDevJWT.ts
index 9b05b73d016..6af3f206662 100644
--- a/web-common/src/features/dashboards/granular-access-policies/updateDevJWT.ts
+++ b/web-common/src/features/dashboards/granular-access-policies/updateDevJWT.ts
@@ -4,7 +4,10 @@ import {
} from "@rilldata/web-common/features/dashboards/granular-access-policies/stores";
import type { MockUser } from "@rilldata/web-common/features/dashboards/granular-access-policies/useMockUsers";
import { runtimeServiceIssueDevJWT } from "@rilldata/web-common/runtime-client";
-import { invalidateAllMetricsViews } from "@rilldata/web-common/runtime-client/invalidation";
+import {
+ invalidateAllMetricsViews,
+ invalidateCanvasQueries,
+} from "@rilldata/web-common/runtime-client/invalidation";
import type { RuntimeClient } from "@rilldata/web-common/runtime-client/v2";
import type { QueryClient } from "@tanstack/svelte-query";
@@ -39,5 +42,6 @@ export async function updateDevJWT(
}
}
- return invalidateAllMetricsViews(queryClient, client.instanceId);
+ await invalidateAllMetricsViews(queryClient, client.instanceId);
+ return invalidateCanvasQueries(queryClient, client.instanceId);
}
diff --git a/web-common/src/features/dashboards/granular-access-policies/useSecurityPolicyCheck.ts b/web-common/src/features/dashboards/granular-access-policies/useSecurityPolicyCheck.ts
index 074f2ccdfdc..6930dd8a072 100644
--- a/web-common/src/features/dashboards/granular-access-policies/useSecurityPolicyCheck.ts
+++ b/web-common/src/features/dashboards/granular-access-policies/useSecurityPolicyCheck.ts
@@ -38,7 +38,12 @@ export function useRillYamlPolicyCheck(client: RuntimeClient) {
const yamlObj = parse(data.blob);
const exploresSecurityPolicy = yamlObj?.explores?.security;
const metricsViewsSecurityPolicy = yamlObj?.metricsViews?.security;
- return !!exploresSecurityPolicy || !!metricsViewsSecurityPolicy;
+ const canvasesSecurityPolicy = yamlObj?.canvases?.security;
+ return (
+ !!exploresSecurityPolicy ||
+ !!metricsViewsSecurityPolicy ||
+ !!canvasesSecurityPolicy
+ );
},
},
},
diff --git a/web-common/src/runtime-client/invalidation.ts b/web-common/src/runtime-client/invalidation.ts
index 189d3cffddc..8cd1c050fc8 100644
--- a/web-common/src/runtime-client/invalidation.ts
+++ b/web-common/src/runtime-client/invalidation.ts
@@ -154,6 +154,24 @@ export async function invalidateAllMetricsViews(
});
}
+export async function invalidateCanvasQueries(
+ queryClient: QueryClient,
+ instanceId: string,
+) {
+ return queryClient.invalidateQueries({
+ predicate: (query: Query) => {
+ const key = query.queryKey;
+ // Format: ["QueryService", "resolveCanvas" | "resolveComponent", instanceId, ...]
+ return (
+ key[0] === "QueryService" &&
+ typeof key[1] === "string" &&
+ (key[1] === "resolveCanvas" || key[1] === "resolveComponent") &&
+ key[2] === instanceId
+ );
+ },
+ });
+}
+
export async function invalidateProfilingQueries(
queryClient: QueryClient,
name: string,
diff --git a/web-local/src/routes/(viz)/canvas/[name]/+page.svelte b/web-local/src/routes/(viz)/canvas/[name]/+page.svelte
index e9a960b4067..ca2fc569cc3 100644
--- a/web-local/src/routes/(viz)/canvas/[name]/+page.svelte
+++ b/web-local/src/routes/(viz)/canvas/[name]/+page.svelte
@@ -4,6 +4,15 @@
import CanvasProvider from "@rilldata/web-common/features/canvas/CanvasProvider.svelte";
import DashboardChat from "@rilldata/web-common/features/chat/DashboardChat.svelte";
import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors.ts";
+ import ErrorPage from "@rilldata/web-common/components/ErrorPage.svelte";
+ import { resetSelectedMockUserAfterNavigate } from "@rilldata/web-common/features/dashboards/granular-access-policies/resetSelectedMockUserAfterNavigate";
+ import { selectedMockUserStore } from "@rilldata/web-common/features/dashboards/granular-access-policies/stores";
+ import { useCanvas } from "@rilldata/web-common/features/canvas/selector";
+ import {
+ isNotFoundError,
+ extractErrorStatusCode,
+ } from "@rilldata/web-common/lib/errors";
+ import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient";
import type { PageData } from "./$types";
const runtimeClient = useRuntimeClient();
@@ -11,19 +20,41 @@
export let data: PageData;
$: ({ canvasName } = data);
+
+ resetSelectedMockUserAfterNavigate(queryClient, runtimeClient);
+
+ $: canvasQuery = useCanvas(runtimeClient, canvasName);
+
+ $: mockUserHasNoAccess =
+ $selectedMockUserStore && isNotFoundError($canvasQuery.error);
+
+ $: isCanvasNotFound =
+ !$canvasQuery.data &&
+ $canvasQuery.isError &&
+ isNotFoundError($canvasQuery.error);
{#key `${runtimeClient.instanceId}::${canvasName}`}
-
-
-
-
-
+ {#if mockUserHasNoAccess}
+
+ {:else if isCanvasNotFound}
+
+ {:else}
+
-
-
+ {/if}
{/key}