Truncate access review roles with badge list

Long role strings in the access review table broke row layout when
drivers joined many roles into one comma-separated value. Expose
roles as a string array in GraphQL by splitting the stored role at
the API layer, and render the first three roles as badges with a
"+X more" popover for the rest.

Closes ENG-459.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-06-12 18:23:04 +02:00
parent bf20ca1a90
commit 8094e7cfd0
80 changed files with 768 additions and 378 deletions

View File

@@ -40,7 +40,7 @@ func TestCursorDriver(t *testing.T) {
member := records[0]
assert.Equal(t, "jane@example.com", member.Email)
assert.Equal(t, "Jane Doe", member.FullName)
assert.Equal(t, "Member", member.Role)
assert.Equal(t, []string{"Member"}, member.Roles)
assert.False(t, member.IsAdmin)
// The Cursor Admin API returns the member id as a string; it is used
// verbatim as the stable ExternalID.
@@ -49,7 +49,7 @@ func TestCursorDriver(t *testing.T) {
assert.True(t, *member.Active)
owner := records[1]
assert.Equal(t, "Owner", owner.Role)
assert.Equal(t, []string{"Owner"}, owner.Roles)
assert.True(t, owner.IsAdmin)
require.NotNil(t, owner.Active)
assert.True(t, *owner.Active)
@@ -57,7 +57,7 @@ func TestCursorDriver(t *testing.T) {
// A removed member (role "removed", isRemoved true) is still returned,
// flagged inactive rather than dropped, per the AccountRecord contract.
removed := records[2]
assert.Equal(t, "Removed", removed.Role)
assert.Equal(t, []string{"Removed"}, removed.Roles)
assert.False(t, removed.IsAdmin)
require.NotNil(t, removed.Active)
assert.False(t, *removed.Active)
@@ -66,32 +66,33 @@ func TestCursorDriver(t *testing.T) {
// carry role "removed" while isRemoved is still false. The role alone
// must mark the account inactive.
removedByRole := records[3]
assert.Equal(t, "Removed", removedByRole.Role)
assert.Equal(t, []string{"Removed"}, removedByRole.Roles)
assert.False(t, removedByRole.IsAdmin)
require.NotNil(t, removedByRole.Active)
assert.False(t, *removedByRole.Active)
}
func TestCursorRole(t *testing.T) {
func TestCursorRoles(t *testing.T) {
t.Parallel()
cases := []struct {
in string
want string
want []string
isAdmin bool
}{
{"owner", "Owner", true},
{"free-owner", "Owner", true},
{"member", "Member", false},
{"removed", "Removed", false},
{"unknown_future_role", "unknown_future_role", false},
{"owner", []string{"Owner"}, true},
{"free-owner", []string{"Owner"}, true},
{"member", []string{"Member"}, false},
{"removed", []string{"Removed"}, false},
{"unknown_future_role", []string{"unknown_future_role"}, false},
{"", []string{}, false},
}
for _, c := range cases {
t.Run(c.in, func(t *testing.T) {
t.Parallel()
assert.Equal(t, c.want, cursorRole(c.in))
assert.Equal(t, c.want, cursorRoles(c.in))
assert.Equal(t, c.isAdmin, cursorIsAdmin(c.in))
})
}