Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions web-common/src/features/canvas/CanvasPreviewCTAs.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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;
</script>

{#if $dashboardChat}
<ChatToggle />
{/if}
{#if !$readOnly}
{#if hasAnyContent}
<div class="flex gap-2 flex-shrink-0 ml-auto">
<Button type="secondary" href={`/files${canvasFilePath}`}>Edit</Button>
{#if hasSecurityPolicy}
<ViewAsButton />
{/if}
{#if $dashboardChat}
<ChatToggle />
{/if}
{#if !$readOnly}
<Button type="secondary" href={`/files${canvasFilePath}`}>Edit</Button>
{/if}
</div>
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -39,5 +42,6 @@ export async function updateDevJWT(
}
}

return invalidateAllMetricsViews(queryClient, client.instanceId);
await invalidateAllMetricsViews(queryClient, client.instanceId);
return invalidateCanvasQueries(queryClient, client.instanceId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
},
},
},
Expand Down
18 changes: 18 additions & 0 deletions web-common/src/runtime-client/invalidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
53 changes: 42 additions & 11 deletions web-local/src/routes/(viz)/canvas/[name]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,57 @@
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();

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);
</script>

{#key `${runtimeClient.instanceId}::${canvasName}`}
<div class="flex h-full overflow-hidden">
<div class="flex-1 overflow-hidden">
<CanvasProvider
{canvasName}
instanceId={runtimeClient.instanceId}
showBanner
>
<CanvasDashboardEmbed {canvasName} />
</CanvasProvider>
{#if mockUserHasNoAccess}
<ErrorPage
statusCode={extractErrorStatusCode($canvasQuery.error)}
header="This user can't access this dashboard"
body="The security policy for this dashboard may make contents invisible to you. If you deploy this dashboard, {$selectedMockUserStore?.email} will see a 404."
/>
{:else if isCanvasNotFound}
<ErrorPage statusCode={404} header="Dashboard not found" />
{:else}
<div class="flex h-full overflow-hidden">
<div class="flex-1 overflow-hidden">
<CanvasProvider
{canvasName}
instanceId={runtimeClient.instanceId}
showBanner
>
<CanvasDashboardEmbed {canvasName} />
</CanvasProvider>
</div>
<DashboardChat kind={ResourceKind.Canvas} />
</div>
<DashboardChat kind={ResourceKind.Canvas} />
</div>
{/if}
{/key}
Loading