-
Notifications
You must be signed in to change notification settings - Fork 178
Groups page project column #8883
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
base: main
Are you sure you want to change the base?
Changes from 8 commits
d147506
8ab307c
65211d5
37c47af
72a8e8a
13fa798
608d4a9
11b0e2e
fc0203a
9704472
bdf69a4
0f8d16a
dea1a98
7ee383f
c468596
482218b
e51e778
6a40817
62c6822
311d4eb
c48ee92
386877d
0b9e443
a4a913f
48911b9
312def3
5d87609
e9b78f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| <script lang="ts"> | ||
| import { onMount } from "svelte"; | ||
| import * as Dropdown from "@rilldata/web-common/components/dropdown-menu"; | ||
| import { | ||
| adminServiceListProjectsForOrganization, | ||
| adminServiceListProjectMemberUsergroups, | ||
| } from "@rilldata/web-admin/client"; | ||
| import CaretDownIcon from "@rilldata/web-common/components/icons/CaretDownIcon.svelte"; | ||
| import CaretUpIcon from "@rilldata/web-common/components/icons/CaretUpIcon.svelte"; | ||
|
|
||
| export let organization: string; | ||
| export let groupName: string; | ||
|
|
||
| interface ProjectWithRole { | ||
| id: string; | ||
| name: string; | ||
| roleName: string; | ||
| } | ||
|
|
||
| let isDropdownOpen = false; | ||
| let isPending = true; | ||
| let accessibleProjects: ProjectWithRole[] = []; | ||
| let hasLoaded = false; | ||
|
|
||
| async function loadProjectsForGroup() { | ||
| if (hasLoaded) return; | ||
|
|
||
| isPending = true; | ||
|
|
||
| try { | ||
| const projectsResponse = | ||
| await adminServiceListProjectsForOrganization(organization); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use a tanstack query. This direct call will not cache the results. |
||
| const allProjects = projectsResponse.projects ?? []; | ||
|
|
||
| const projectAccessResults = await Promise.all( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this really the best way to get projects? We should add a new API that fetches all projects accessible by the user group.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @begelundmuller, another one here! |
||
| allProjects.map(async (project) => { | ||
| try { | ||
| const usergroupsResponse = | ||
| await adminServiceListProjectMemberUsergroups( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
| organization, | ||
| project.name ?? "", | ||
| ); | ||
| const members = usergroupsResponse.members ?? []; | ||
| const groupMember = members.find((m) => m.groupName === groupName); | ||
| if (groupMember) { | ||
| return { | ||
| project: { | ||
| id: project.id ?? "", | ||
| name: project.name ?? "", | ||
| roleName: groupMember.roleName ?? "", | ||
| }, | ||
| hasAccess: true, | ||
| }; | ||
| } | ||
| return { project: null, hasAccess: false }; | ||
| } catch { | ||
| return { project: null, hasAccess: false }; | ||
| } | ||
| }), | ||
| ); | ||
|
|
||
| accessibleProjects = projectAccessResults | ||
| .filter((r) => r.hasAccess && r.project) | ||
| .map((r) => r.project as ProjectWithRole); | ||
| hasLoaded = true; | ||
| } catch { | ||
| // Ignore errors, will show "No projects" | ||
| } finally { | ||
| isPending = false; | ||
| } | ||
| } | ||
|
|
||
| onMount(() => { | ||
| void loadProjectsForGroup(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }); | ||
|
|
||
| $: projectCount = accessibleProjects.length; | ||
| $: hasProjects = projectCount > 0; | ||
|
|
||
| function getProjectUrl(projectName: string) { | ||
| return `/${organization}/${projectName}/-/share`; | ||
| } | ||
|
|
||
| function formatRoleName(roleName: string): string { | ||
| return roleName.charAt(0).toUpperCase() + roleName.slice(1).toLowerCase(); | ||
| } | ||
| </script> | ||
|
|
||
| {#if hasLoaded && hasProjects} | ||
| <Dropdown.Root bind:open={isDropdownOpen}> | ||
| <Dropdown.Trigger | ||
| class="flex flex-row gap-1 items-center rounded-sm {isDropdownOpen | ||
| ? 'bg-gray-200' | ||
| : 'hover:bg-surface-hover'} px-2 py-1" | ||
| > | ||
| <span class="capitalize"> | ||
| {projectCount} Project{projectCount !== 1 ? "s" : ""} | ||
| </span> | ||
| {#if isDropdownOpen} | ||
| <CaretUpIcon size="12px" /> | ||
| {:else} | ||
| <CaretDownIcon size="12px" /> | ||
| {/if} | ||
| </Dropdown.Trigger> | ||
| <Dropdown.Content align="start"> | ||
| {#each accessibleProjects as project (project.id)} | ||
| <Dropdown.Item | ||
| href={getProjectUrl(project.name)} | ||
| class="flex items-center justify-between gap-4" | ||
| > | ||
| <span class="truncate">{project.name}</span> | ||
| <span class="text-fg-secondary text-xs shrink-0" | ||
| >{formatRoleName(project.roleName)}</span | ||
| > | ||
| </Dropdown.Item> | ||
| {/each} | ||
| </Dropdown.Content> | ||
| </Dropdown.Root> | ||
| {:else if isPending} | ||
| <div class="rounded-sm px-2 py-1 text-fg-secondary">Loading...</div> | ||
| {:else} | ||
| <div class="rounded-sm px-2 py-1 text-fg-secondary">No projects</div> | ||
| {/if} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are adding all users in serial vs here where we are adding in parallel. We should do this in serial as well to keep things consistent. Also add a TODO to make sure we handle partial updates like group is created but only one project got added.