Skip to content

Commit a3f40d5

Browse files
authored
feat: add members settings page for organizations (coder#13817)
1 parent b697c69 commit a3f40d5

File tree

18 files changed

+408
-90
lines changed

18 files changed

+408
-90
lines changed

cli/organizationmembers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func (r *RootCmd) assignOrganizationRoles(orgContext *OrganizationContext) *serp
137137

138138
func (r *RootCmd) listOrganizationMembers(orgContext *OrganizationContext) *serpent.Command {
139139
formatter := cliui.NewOutputFormatter(
140-
cliui.TableFormat([]codersdk.OrganizationMemberWithName{}, []string{"username", "organization_roles"}),
140+
cliui.TableFormat([]codersdk.OrganizationMemberWithUserData{}, []string{"username", "organization_roles"}),
141141
cliui.JSONFormat(),
142142
)
143143

coderd/apidoc/docs.go

Lines changed: 16 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 16 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/db2sdk/db2sdk.go

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -166,25 +166,7 @@ func User(user database.User, organizationIDs []uuid.UUID) codersdk.User {
166166
convertedUser := codersdk.User{
167167
ReducedUser: ReducedUser(user),
168168
OrganizationIDs: organizationIDs,
169-
Roles: make([]codersdk.SlimRole, 0, len(user.RBACRoles)),
170-
}
171-
172-
for _, roleName := range user.RBACRoles {
173-
// TODO: Currently the api only returns site wide roles.
174-
// Should it return organization roles?
175-
rbacRole, err := rbac.RoleByName(rbac.RoleIdentifier{
176-
Name: roleName,
177-
OrganizationID: uuid.Nil,
178-
})
179-
if err == nil {
180-
convertedUser.Roles = append(convertedUser.Roles, SlimRole(rbacRole))
181-
} else {
182-
// TODO: Fix this for custom roles to display the actual display_name
183-
// Requires plumbing either a cached role value, or the db.
184-
convertedUser.Roles = append(convertedUser.Roles, codersdk.SlimRole{
185-
Name: roleName,
186-
})
187-
}
169+
Roles: SlimRolesFromNames(user.RBACRoles),
188170
}
189171

190172
return convertedUser
@@ -537,6 +519,27 @@ func SlimRole(role rbac.Role) codersdk.SlimRole {
537519
}
538520
}
539521

522+
func SlimRolesFromNames(names []string) []codersdk.SlimRole {
523+
convertedRoles := make([]codersdk.SlimRole, 0, len(names))
524+
525+
for _, name := range names {
526+
convertedRoles = append(convertedRoles, SlimRoleFromName(name))
527+
}
528+
529+
return convertedRoles
530+
}
531+
532+
func SlimRoleFromName(name string) codersdk.SlimRole {
533+
rbacRole, err := rbac.RoleByName(rbac.RoleIdentifier{Name: name})
534+
var convertedRole codersdk.SlimRole
535+
if err == nil {
536+
convertedRole = SlimRole(rbacRole)
537+
} else {
538+
convertedRole = codersdk.SlimRole{Name: name}
539+
}
540+
return convertedRole
541+
}
542+
540543
func RBACRole(role rbac.Role) codersdk.Role {
541544
slim := SlimRole(role)
542545

coderd/database/queries.sql.go

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/organizationmembers.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
-- - Use both to get a specific org member row
66
SELECT
77
sqlc.embed(organization_members),
8-
users.username
8+
users.username, users.avatar_url, users.name, users.rbac_roles as "global_roles"
99
FROM
1010
organization_members
1111
INNER JOIN

coderd/members.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,15 @@ func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request)
8383
// @Summary Remove organization member
8484
// @ID remove-organization-member
8585
// @Security CoderSessionToken
86-
// @Produce json
8786
// @Tags Members
8887
// @Param organization path string true "Organization ID"
8988
// @Param user path string true "User ID, name, or me"
90-
// @Success 200 {object} codersdk.OrganizationMember
89+
// @Success 204
9190
// @Router /organizations/{organization}/members/{user} [delete]
9291
func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request) {
9392
var (
9493
ctx = r.Context()
94+
apiKey = httpmw.APIKey(r)
9595
organization = httpmw.OrganizationParam(r)
9696
member = httpmw.OrganizationMemberParam(r)
9797
auditor = api.Auditor.Load()
@@ -106,6 +106,11 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
106106
aReq.Old = member.OrganizationMember.Auditable(member.Username)
107107
defer commitAudit()
108108

109+
if member.UserID == apiKey.UserID {
110+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{Message: "cannot remove self from an organization"})
111+
return
112+
}
113+
109114
err := api.Database.DeleteOrganizationMember(ctx, database.DeleteOrganizationMemberParams{
110115
OrganizationID: organization.ID,
111116
UserID: member.UserID,
@@ -120,7 +125,7 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
120125
}
121126

122127
aReq.New = database.AuditableOrganizationMember{}
123-
httpapi.Write(ctx, rw, http.StatusOK, "organization member removed")
128+
rw.WriteHeader(http.StatusNoContent)
124129
}
125130

126131
// @Summary List organization members
@@ -129,7 +134,7 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
129134
// @Produce json
130135
// @Tags Members
131136
// @Param organization path string true "Organization ID"
132-
// @Success 200 {object} []codersdk.OrganizationMemberWithName
137+
// @Success 200 {object} []codersdk.OrganizationMemberWithUserData
133138
// @Router /organizations/{organization}/members [get]
134139
func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) {
135140
var (
@@ -150,7 +155,7 @@ func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) {
150155
return
151156
}
152157

153-
resp, err := convertOrganizationMemberRows(ctx, api.Database, members)
158+
resp, err := convertOrganizationMembersWithUserData(ctx, api.Database, members)
154159
if err != nil {
155160
httpapi.InternalServerError(rw, err)
156161
return
@@ -294,7 +299,7 @@ func convertOrganizationMembers(ctx context.Context, db database.Store, mems []d
294299
return converted, nil
295300
}
296301

297-
func convertOrganizationMemberRows(ctx context.Context, db database.Store, rows []database.OrganizationMembersRow) ([]codersdk.OrganizationMemberWithName, error) {
302+
func convertOrganizationMembersWithUserData(ctx context.Context, db database.Store, rows []database.OrganizationMembersRow) ([]codersdk.OrganizationMemberWithUserData, error) {
298303
members := make([]database.OrganizationMember, 0)
299304
for _, row := range rows {
300305
members = append(members, row.OrganizationMember)
@@ -308,10 +313,13 @@ func convertOrganizationMemberRows(ctx context.Context, db database.Store, rows
308313
return nil, xerrors.Errorf("conversion failed, mismatch slice lengths")
309314
}
310315

311-
converted := make([]codersdk.OrganizationMemberWithName, 0)
316+
converted := make([]codersdk.OrganizationMemberWithUserData, 0)
312317
for i := range convertedMembers {
313-
converted = append(converted, codersdk.OrganizationMemberWithName{
318+
converted = append(converted, codersdk.OrganizationMemberWithUserData{
314319
Username: rows[i].Username,
320+
AvatarURL: rows[i].AvatarURL,
321+
Name: rows[i].Name,
322+
GlobalRoles: db2sdk.SlimRolesFromNames(rows[i].GlobalRoles),
315323
OrganizationMember: convertedMembers[i],
316324
})
317325
}

coderd/members_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,6 @@ func TestRemoveMember(t *testing.T) {
185185
})
186186
}
187187

188-
func onlyIDs(u codersdk.OrganizationMemberWithName) uuid.UUID {
188+
func onlyIDs(u codersdk.OrganizationMemberWithUserData) uuid.UUID {
189189
return u.UserID
190190
}

codersdk/organizations.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@ type OrganizationMember struct {
6666
Roles []SlimRole `table:"organization_roles" json:"roles"`
6767
}
6868

69-
type OrganizationMemberWithName struct {
70-
Username string `table:"username,default_sort" json:"username"`
69+
type OrganizationMemberWithUserData struct {
70+
Username string `table:"username,default_sort" json:"username"`
71+
Name string `table:"name" json:"name"`
72+
AvatarURL string `json:"avatar_url"`
73+
GlobalRoles []SlimRole `json:"global_roles"`
7174
OrganizationMember `table:"m,recursive_inline"`
7275
}
7376

codersdk/users.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,14 +402,14 @@ func (c *Client) DeleteOrganizationMember(ctx context.Context, organizationID uu
402402
return err
403403
}
404404
defer res.Body.Close()
405-
if res.StatusCode != http.StatusOK {
405+
if res.StatusCode != http.StatusNoContent {
406406
return ReadBodyAsError(res)
407407
}
408408
return nil
409409
}
410410

411411
// OrganizationMembers lists all members in an organization
412-
func (c *Client) OrganizationMembers(ctx context.Context, organizationID uuid.UUID) ([]OrganizationMemberWithName, error) {
412+
func (c *Client) OrganizationMembers(ctx context.Context, organizationID uuid.UUID) ([]OrganizationMemberWithUserData, error) {
413413
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/members/", organizationID), nil)
414414
if err != nil {
415415
return nil, err
@@ -418,7 +418,7 @@ func (c *Client) OrganizationMembers(ctx context.Context, organizationID uuid.UU
418418
if res.StatusCode != http.StatusOK {
419419
return nil, ReadBodyAsError(res)
420420
}
421-
var members []OrganizationMemberWithName
421+
var members []OrganizationMemberWithUserData
422422
return members, json.NewDecoder(res.Body).Decode(&members)
423423
}
424424

0 commit comments

Comments
 (0)