@@ -14,7 +14,7 @@ import {
1414 apiQueryClient ,
1515 byGroupThenName ,
1616 deleteRole ,
17- getEffectiveRole ,
17+ roleOrder ,
1818 useApiMutation ,
1919 useApiQueryClient ,
2020 usePrefetchedApiQuery ,
@@ -25,7 +25,7 @@ import {
2525import { Access24Icon } from '@oxide/design-system/icons/react'
2626
2727import { HL } from '~/components/HL'
28- import { RoleBadgeCell } from '~/components/RoleBadgeCell '
28+ import { ListPlusCell } from '~/components/ListPlusCell '
2929import {
3030 ProjectAccessAddUserSideModal ,
3131 ProjectAccessEditUserSideModal ,
@@ -34,12 +34,14 @@ import { getProjectSelector, useProjectSelector } from '~/hooks'
3434import { confirmDelete } from '~/stores/confirm-delete'
3535import { getActionsCol } from '~/table/columns/action-col'
3636import { Table } from '~/table/Table'
37+ import { Badge } from '~/ui/lib/Badge'
3738import { Button } from '~/ui/lib/Button'
3839import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3940import { PageHeader , PageTitle } from '~/ui/lib/PageHeader'
4041import { TableActions , TableEmptyBox } from '~/ui/lib/Table'
41- import { accessTypeLabel } from '~/util/access'
42- import { groupBy , isTruthy } from '~/util/array'
42+ import { TipIcon } from '~/ui/lib/TipIcon'
43+ import { accessTypeLabel , getBadgeColor } from '~/util/access'
44+ import { groupBy , isTruthy , sortBy } from '~/util/array'
4345
4446const EmptyState = ( { onClick } : { onClick : ( ) => void } ) => (
4547 < TableEmptyBox >
@@ -69,9 +71,8 @@ type UserRow = {
6971 id : string
7072 identityType : IdentityType
7173 name : string
72- siloRole : RoleKey | undefined
7374 projectRole : RoleKey | undefined
74- effectiveRole : RoleKey
75+ roleBadges : { roleSource : string ; roleName : RoleKey } [ ]
7576}
7677
7778const colHelper = createColumnHelper < UserRow > ( )
@@ -92,26 +93,23 @@ export function ProjectAccessPage() {
9293 const rows = useMemo ( ( ) => {
9394 return groupBy ( siloRows . concat ( projectRows ) , ( u ) => u . id )
9495 . map ( ( [ userId , userAssignments ] ) => {
95- const siloRole = userAssignments . find ( ( a ) => a . roleSource === 'silo' ) ?. roleName
96- const projectRole = userAssignments . find (
97- ( a ) => a . roleSource === 'project'
98- ) ?. roleName
96+ const { name, identityType } = userAssignments [ 0 ]
9997
100- const roles = [ siloRole , projectRole ] . filter ( isTruthy )
98+ const siloAccessRow = userAssignments . find ( ( a ) => a . roleSource === 'silo' )
99+ const projectAccessRow = userAssignments . find ( ( a ) => a . roleSource === 'project' )
101100
102- const { name, identityType } = userAssignments [ 0 ]
101+ const roleBadges = sortBy (
102+ [ siloAccessRow , projectAccessRow ] . filter ( isTruthy ) ,
103+ ( r ) => roleOrder [ r . roleName ] // sorts strongest role first
104+ )
103105
104- const row : UserRow = {
106+ return {
105107 id : userId ,
106108 identityType,
107109 name,
108- siloRole,
109- projectRole,
110- // we know there has to be at least one
111- effectiveRole : getEffectiveRole ( roles ) ! ,
112- }
113-
114- return row
110+ projectRole : projectAccessRow ?. roleName ,
111+ roleBadges,
112+ } satisfies UserRow
115113 } )
116114 . sort ( byGroupThenName )
117115 } , [ siloRows , projectRows ] )
@@ -132,14 +130,27 @@ export function ProjectAccessPage() {
132130 header : 'Type' ,
133131 cell : ( props ) => accessTypeLabel ( props . getValue ( ) ) ,
134132 } ) ,
135- colHelper . accessor ( 'siloRole' , {
136- header : 'Silo role' ,
137- cell : RoleBadgeCell ,
138- } ) ,
139- colHelper . accessor ( 'projectRole' , {
140- header : 'Project role' ,
141- cell : RoleBadgeCell ,
133+ colHelper . accessor ( 'roleBadges' , {
134+ header : ( ) => (
135+ < span className = "inline-flex items-center" >
136+ Role
137+ < TipIcon className = "ml-2" >
138+ A user or group's effective role for this project is the strongest role
139+ on either the silo or project.
140+ </ TipIcon >
141+ </ span >
142+ ) ,
143+ cell : ( props ) => (
144+ < ListPlusCell tooltipTitle = "Other roles" >
145+ { props . getValue ( ) . map ( ( { roleName, roleSource } ) => (
146+ < Badge key = { roleSource } color = { getBadgeColor ( roleName ) } >
147+ { roleSource } .{ roleName }
148+ </ Badge >
149+ ) ) }
150+ </ ListPlusCell >
151+ ) ,
142152 } ) ,
153+
143154 // TODO: tooltips on disabled elements explaining why
144155 getActionsCol ( ( row : UserRow ) => [
145156 {
0 commit comments