Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "View as" button to Rill Cloud dashboards #3047

Merged
merged 44 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
270e030
Refactor `UserButton`
ericpgreen2 Sep 1, 2023
0d6b7ef
Add conditional 'View as' menu item
ericpgreen2 Sep 1, 2023
2354e53
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 6, 2023
9831e18
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 6, 2023
349b418
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 6, 2023
5bec5aa
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 7, 2023
aac65ad
Add `svelte-headlessui` and `svelte-popperjs`
ericpgreen2 Sep 8, 2023
770d813
Add "View As" popover
ericpgreen2 Sep 8, 2023
e136526
Acquire and set JWT
ericpgreen2 Sep 8, 2023
e07810f
Remove calls to `ListFiles` API
ericpgreen2 Sep 9, 2023
d5c1d26
Fix `svelte-check` w/ explicit type
ericpgreen2 Sep 11, 2023
d37bd4f
Add comment
ericpgreen2 Sep 11, 2023
ca4cc58
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 11, 2023
e282ec4
Patch `MenuItem` component
ericpgreen2 Sep 11, 2023
37e03d7
Remove File API usage
ericpgreen2 Sep 11, 2023
941248c
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 12, 2023
d74f876
Handle chip removal
ericpgreen2 Sep 12, 2023
c0b9051
Fix z-index on chip
ericpgreen2 Sep 12, 2023
b839ef4
Clear error when switching users
ericpgreen2 Sep 12, 2023
1b66017
Switch back to admin's JWT
ericpgreen2 Sep 12, 2023
adda499
Handle 404 from `GetCatalog`
ericpgreen2 Sep 12, 2023
e326883
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 12, 2023
7ee9b93
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 12, 2023
742e663
Add workaround for metrics view 401s; clean up
ericpgreen2 Sep 12, 2023
8ac38ef
Clear mimicked user after navigation
ericpgreen2 Sep 12, 2023
1063b9c
Bugfix
ericpgreen2 Sep 12, 2023
3056801
Make the whole chip clickable
ericpgreen2 Sep 12, 2023
3abb9bb
Add checkmark to denote active user
ericpgreen2 Sep 12, 2023
046796c
Remove comment
ericpgreen2 Sep 13, 2023
57ab726
Edit 404 copy
ericpgreen2 Sep 13, 2023
88a69f3
Better variable names
ericpgreen2 Sep 13, 2023
8ec4ee6
Merge branch 'main' into cloud-ui-remove-file-api
ericpgreen2 Sep 13, 2023
9475286
Merge branch 'cloud-ui-remove-file-api' into partner-dashboards-rill-…
ericpgreen2 Sep 13, 2023
0126cbe
Move code to `features/view-as-user`
ericpgreen2 Sep 13, 2023
ff4c407
Factor out a `ViewAsUserMenuItem` component
ericpgreen2 Sep 13, 2023
be82da5
Use consistent "viewed as" terminology
ericpgreen2 Sep 13, 2023
543bf3b
Use consistent "view as" terminology (cont.)
ericpgreen2 Sep 13, 2023
c50735f
Clean up actions
ericpgreen2 Sep 13, 2023
b8a9bb8
Nits
ericpgreen2 Sep 13, 2023
cdbadd9
Bug fix
ericpgreen2 Sep 13, 2023
0891f61
Use query cache
ericpgreen2 Sep 13, 2023
a4e72ae
Remove unneccessary query observer
ericpgreen2 Sep 13, 2023
302d95b
Handle case when admin's JWT gets refreshed
ericpgreen2 Sep 13, 2023
e24d6ab
Merge branch 'main' into partner-dashboards-rill-cloud-ux
ericpgreen2 Sep 13, 2023
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
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions web-admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"devDependencies": {
"@fontsource/fira-mono": "^4.5.0",
"@playwright/test": "^1.25.0",
"@rgossiaux/svelte-headlessui": "^2.0.0",
"@sveltejs/adapter-static": "^1.0.0",
"@sveltejs/kit": "^1.5.0",
"@rilldata/svelte-query": "^4.29.20-0.0.1",
Expand All @@ -34,6 +35,7 @@
"prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.48.0",
"svelte-check": "^3.0.3",
"svelte-popperjs": "^1.3.2",
"svelte-preprocess": "^4.10.6",
"tailwindcss": "^3.2.7",
"tslib": "^2.3.1",
Expand Down
84 changes: 65 additions & 19 deletions web-admin/src/components/authentication/UserButton.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
<script lang="ts">
import SimpleActionMenu from "@rilldata/web-common/components/menu/wrappers/SimpleActionMenu.svelte";
import { page } from "$app/stores";
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
import { MenuItem } from "@rilldata/web-common/components/menu";
import Menu from "@rilldata/web-common/components/menu/core/Menu.svelte";
import { createPopperActions } from "svelte-popperjs";
import { createAdminServiceGetCurrentUser } from "../../client";
import { ADMIN_URL } from "../../client/http-client";
import ViewAsUserMenuItem from "../../features/view-as-user/ViewAsUserMenuItem.svelte";
import ProjectAccessControls from "../projects/ProjectAccessControls.svelte";

const user = createAdminServiceGetCurrentUser();

Expand All @@ -15,23 +25,59 @@
}

const isDev = process.env.NODE_ENV === "development";

// Position the Menu popover
const [popperRef1, popperContent1] = createPopperActions();
const popperOptions1 = {
placement: "bottom-end",
strategy: "fixed",
modifiers: [{ name: "offset", options: { offset: [0, 4] } }],
};

// Position the View As User popover
const [popperRef2, popperContent2] = createPopperActions();
</script>

<SimpleActionMenu
options={[
{ main: "Logout", callback: handleLogOut },
{ main: "Documentation", callback: handleDocumentation },
]}
let:toggleMenu
minWidth="0px"
distance={4}
>
<img
src={$user.data?.user?.photoUrl}
alt="avatar"
class="h-7 rounded-full cursor-pointer"
referrerpolicy={isDev ? "no-referrer" : ""}
on:click={toggleMenu}
on:keydown={toggleMenu}
/>
</SimpleActionMenu>
<Popover class="relative" let:close={close1}>
<PopoverButton use={[popperRef1]}>
<img
src={$user.data?.user?.photoUrl}
alt="avatar"
class="h-7 rounded-full cursor-pointer"
referrerpolicy={isDev ? "no-referrer" : ""}
/>
</PopoverButton>
<PopoverPanel
use={[popperRef2, [popperContent1, popperOptions1]]}
class="max-w-fit absolute z-[1000]"
>
<Menu minWidth="0px" focusOnMount={false}>
{#if $page.params.organization && $page.params.project && $page.params.dashboard}
<ProjectAccessControls
organization={$page.params.organization}
project={$page.params.project}
>
<svelte:fragment slot="manage-project">
<ViewAsUserMenuItem
popperContent={popperContent2}
on:select-user={() => close1(undefined)}
/>
</svelte:fragment>
</ProjectAccessControls>
{/if}

<MenuItem
on:select={() => {
// handleClose();
handleLogOut();
}}>Logout</MenuItem
>
<MenuItem
on:select={() => {
// handleClose();
handleDocumentation();
}}>Documentation</MenuItem
>
</Menu>
</PopoverPanel>
</Popover>
53 changes: 32 additions & 21 deletions web-admin/src/components/errors/error-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,42 @@ import { ADMIN_URL } from "../../client/http-client";
import { ErrorStoreState, errorStore } from "./error-store";

export function globalErrorCallback(error: AxiosError): void {
// If Unauthorized, redirect to login page
if (error.response.status === 401) {
goto(`${ADMIN_URL}/auth/login?redirect=${window.origin}`);
return;
const isProjectPage = get(page).route.id === "/[organization]/[project]";
const isDashboardPage =
get(page).route.id === "/[organization]/[project]/[dashboard]";

// Special handling for some errors on the Project page
if (isProjectPage) {
// If "repository not found", ignore the error and show the page
if (
error.response.status === 400 &&
(error.response.data as RpcStatus).message === "repository not found"
) {
return;
}
}

// If on a Project page, and "repository not found", ignore the error and show the page
const isProjectPage = get(page).route.id === "/[organization]/[project]";
if (
isProjectPage &&
error.response.status === 400 &&
(error.response.data as RpcStatus).message === "repository not found"
) {
return;
// Special handling for some errors on the Dashboard page
if (isDashboardPage) {
// If a dashboard wasn't found, let +page.svelte handle the error.
// Because the project may be reconciling, in which case we want to show a loading spinner not a 404.
if (
error.response.status === 404 &&
(error.response.data as RpcStatus).message === "not found"
) {
return;
}

// When a JWT doesn't permit access to a metrics view, the metrics view APIs return 401s.
// In this scenario, `GetCatalog` returns a 404. We ignore the 401s so we can show the 404.
if (error.response.status === 401) {
return;
}
}

// If on a Dashboard page, and "entry not found" (i.e. a dashboard wasn't found),
// ignore the error here, so the page can handle it.
const isDashboardPage =
get(page).route.id === "/[organization]/[project]/[dashboard]";
if (
isDashboardPage &&
error.response.status === 400 &&
(error.response.data as RpcStatus).message === "entry not found"
) {
// If Unauthorized, redirect to login page
if (error.response.status === 401) {
goto(`${ADMIN_URL}/auth/login?redirect=${window.origin}`);
return;
}

Expand Down
5 changes: 5 additions & 0 deletions web-admin/src/components/navigation/TopNavigationBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte";
import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte";
import { createAdminServiceGetCurrentUser } from "../../client";
import ViewAsUserChip from "../../features/view-as-user/ViewAsUserChip.svelte";
import { viewAsUserStore } from "../../features/view-as-user/viewAsUserStore";
import SignIn from "../authentication/SignIn.svelte";
import UserButton from "../authentication/UserButton.svelte";
import { isErrorStoreEmpty } from "../errors/error-store";
Expand Down Expand Up @@ -38,6 +40,9 @@
<div />
{/if}
<div class="flex gap-x-3 items-center">
{#if $viewAsUserStore}
<ViewAsUserChip />
{/if}
<a
class="font-medium"
href="https://discord.com/invite/ngVV4KzEGv?utm_source=rill&utm_medium=rill-cloud-nav"
Expand Down
67 changes: 67 additions & 0 deletions web-admin/src/features/view-as-user/ViewAsUserChip.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script lang="ts">
import { page } from "$app/stores";
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
import { IconSpaceFixer } from "@rilldata/web-common/components/button";
import { Chip } from "@rilldata/web-common/components/chip";
import CaretDownIcon from "@rilldata/web-common/components/icons/CaretDownIcon.svelte";
import { useQueryClient } from "@tanstack/svelte-query";
import { createPopperActions } from "svelte-popperjs";
import { errorStore } from "../../components/errors/error-store";
import { clearViewedAsUserWithinProject } from "./clearViewedAsUser";
import ViewAsUserPopover from "./ViewAsUserPopover.svelte";
import { viewAsUserStore } from "./viewAsUserStore";

// Position the popover
const [popperRef, popperContent] = createPopperActions();
const popperOptions = {
placement: "bottom-start",
strategy: "fixed",
modifiers: [{ name: "offset", options: { offset: [0, 4] } }],
};

const queryClient = useQueryClient();
$: org = $page.params.organization;
$: project = $page.params.project;
</script>

<Popover let:close let:open>
<PopoverButton use={[popperRef]}>
<Chip
removable
on:remove={async () => {
await clearViewedAsUserWithinProject(queryClient, org, project);
errorStore.reset();
}}
active={open}
>
<div slot="body">
<div class="flex gap-x-2">
<div>
Viewing as <span class="font-bold">{$viewAsUserStore.email}</span>
</div>
<div class="flex items-center">
<IconSpaceFixer pullRight>
<div class="transition-transform" class:-rotate-180={open}>
<CaretDownIcon size="14px" />
</div>
</IconSpaceFixer>
</div>
</div>
</div>
<svelte:fragment slot="remove-tooltip">
<slot name="remove-tooltip-content">Clear view</slot>
</svelte:fragment>
</Chip>
</PopoverButton>
<PopoverPanel use={[[popperContent, popperOptions]]} class="z-[1000]">
<ViewAsUserPopover
organization={$page.params.organization}
project={$page.params.project}
on:select={() => close(undefined)}
/>
</PopoverPanel>
</Popover>
44 changes: 44 additions & 0 deletions web-admin/src/features/view-as-user/ViewAsUserMenuItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
import { page } from "$app/stores";
import type { Modifier } from "@popperjs/core";
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
import CaretDownIcon from "@rilldata/web-common/components/icons/CaretDownIcon.svelte";
import { MenuItem } from "@rilldata/web-common/components/menu";
import { createEventDispatcher } from "svelte";
import type { ContentAction } from "svelte-popperjs";
import ViewAsUserPopover from "../../features/view-as-user/ViewAsUserPopover.svelte";

export let popperContent: ContentAction<Partial<Modifier<any, any>>>;

const popperOptions = {
placement: "left-start",
strategy: "fixed",
modifiers: [{ name: "offset", options: { offset: [0, 4] } }],
};

const dispatch = createEventDispatcher();
</script>

<Popover>
<PopoverButton class="w-full text-left">
<MenuItem animateSelect={false}>
View as
<CaretDownIcon
className="transform -rotate-90"
slot="right"
size="14px"
/>
</MenuItem>
</PopoverButton>
<PopoverPanel use={[[popperContent, popperOptions]]}>
<ViewAsUserPopover
organization={$page.params.organization}
project={$page.params.project}
on:select={() => dispatch("select-user")}
/>
</PopoverPanel>
</Popover>
Loading