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}