General.utils.ts109 lines · main
| 1 | import type { |
| 2 | OrganizationRole, |
| 3 | OrganizationRolesResponse, |
| 4 | } from '@/data/organization-members/organization-roles-query' |
| 5 | import type { OrganizationMember } from '@/data/organizations/organization-members-query' |
| 6 | |
| 7 | export interface ProjectAccessMember { |
| 8 | id: string |
| 9 | displayName?: string |
| 10 | email: string |
| 11 | role?: string |
| 12 | } |
| 13 | |
| 14 | interface SummarizeProjectAccessParams { |
| 15 | organizationMembers: OrganizationMember[] |
| 16 | roles: OrganizationRolesResponse | undefined |
| 17 | projectRef?: string |
| 18 | hasLimitedVisibility: boolean |
| 19 | currentUserId?: string |
| 20 | maxVisibleMembers?: number |
| 21 | } |
| 22 | |
| 23 | export interface ProjectAccessSummary { |
| 24 | projectMembers: ProjectAccessMember[] |
| 25 | visibleMembers: ProjectAccessMember[] |
| 26 | hiddenMembersCount: number |
| 27 | projectMemberCount: number |
| 28 | organizationMemberCount: number |
| 29 | shouldShowOrgComparison: boolean |
| 30 | hasOrganizationWideAccess: boolean |
| 31 | } |
| 32 | |
| 33 | const getRoleDisplayName = (roleName: string | undefined) => { |
| 34 | if (!roleName) return undefined |
| 35 | return roleName.split('_')[0] |
| 36 | } |
| 37 | |
| 38 | const roleAppliesToProject = (role: OrganizationRole | undefined, projectRef?: string) => { |
| 39 | if (!role) return false |
| 40 | if (role.projects.length === 0) return true |
| 41 | if (!projectRef) return false |
| 42 | return role.projects.some((project) => project.ref === projectRef) |
| 43 | } |
| 44 | |
| 45 | export const summarizeProjectAccess = ({ |
| 46 | organizationMembers, |
| 47 | roles, |
| 48 | projectRef, |
| 49 | hasLimitedVisibility, |
| 50 | currentUserId, |
| 51 | maxVisibleMembers = 12, |
| 52 | }: SummarizeProjectAccessParams): ProjectAccessSummary => { |
| 53 | const allRoles = [...(roles?.org_scoped_roles ?? []), ...(roles?.project_scoped_roles ?? [])] |
| 54 | const rolesById = new Map(allRoles.map((role) => [role.id, role])) |
| 55 | |
| 56 | const normalizedMembers = organizationMembers.filter( |
| 57 | (member) => !member.invited_id && typeof member.gotrue_id === 'string' && !!member.primary_email |
| 58 | ) |
| 59 | |
| 60 | const membersWithProjectAccess = normalizedMembers |
| 61 | .filter((member) => |
| 62 | member.role_ids.some((roleId) => roleAppliesToProject(rolesById.get(roleId), projectRef)) |
| 63 | ) |
| 64 | .sort((a, b) => { |
| 65 | const isCurrentUserA = !!currentUserId && a.gotrue_id === currentUserId |
| 66 | const isCurrentUserB = !!currentUserId && b.gotrue_id === currentUserId |
| 67 | |
| 68 | if (isCurrentUserA && !isCurrentUserB) return -1 |
| 69 | if (!isCurrentUserA && isCurrentUserB) return 1 |
| 70 | return (a.primary_email ?? '').localeCompare(b.primary_email ?? '') |
| 71 | }) |
| 72 | |
| 73 | const projectMembers = membersWithProjectAccess.map((member) => { |
| 74 | const matchingRoleNames = member.role_ids |
| 75 | .map((roleId) => rolesById.get(roleId)) |
| 76 | .filter((role) => roleAppliesToProject(role, projectRef)) |
| 77 | .map((role) => getRoleDisplayName(role?.name)) |
| 78 | .filter((name): name is string => typeof name === 'string' && name.length > 0) |
| 79 | |
| 80 | const uniqueRoleNames = [...new Set(matchingRoleNames)] |
| 81 | const hasDisplayName = |
| 82 | typeof member.username === 'string' && |
| 83 | typeof member.primary_email === 'string' && |
| 84 | member.username !== member.primary_email |
| 85 | |
| 86 | return { |
| 87 | id: member.gotrue_id as string, |
| 88 | displayName: hasDisplayName ? member.username : undefined, |
| 89 | email: member.primary_email as string, |
| 90 | role: uniqueRoleNames.length > 0 ? uniqueRoleNames.join(', ') : undefined, |
| 91 | } |
| 92 | }) |
| 93 | |
| 94 | const projectMemberCount = projectMembers.length |
| 95 | const organizationMemberCount = normalizedMembers.length |
| 96 | const shouldShowOrgComparison = !hasLimitedVisibility && organizationMemberCount > 0 |
| 97 | const hasOrganizationWideAccess = |
| 98 | shouldShowOrgComparison && projectMemberCount === organizationMemberCount |
| 99 | |
| 100 | return { |
| 101 | projectMembers, |
| 102 | visibleMembers: projectMembers.slice(0, maxVisibleMembers), |
| 103 | hiddenMembersCount: Math.max(projectMemberCount - maxVisibleMembers, 0), |
| 104 | projectMemberCount, |
| 105 | organizationMemberCount, |
| 106 | shouldShowOrgComparison, |
| 107 | hasOrganizationWideAccess, |
| 108 | } |
| 109 | } |