feat: add members settings page for organizations (#13817)

This commit is contained in:
Kayla Washburn-Love
2024-07-16 12:25:36 -06:00
committed by GitHub
parent b697c6939a
commit a3f40d5ef8
18 changed files with 408 additions and 90 deletions
+16 -10
View File
@@ -2340,7 +2340,7 @@ const docTemplate = `{
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.OrganizationMemberWithName"
"$ref": "#/definitions/codersdk.OrganizationMemberWithUserData"
}
}
}
@@ -2467,9 +2467,6 @@ const docTemplate = `{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Members"
],
@@ -2492,11 +2489,8 @@ const docTemplate = `{
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.OrganizationMember"
}
"204": {
"description": "No Content"
}
}
}
@@ -10500,13 +10494,25 @@ const docTemplate = `{
}
}
},
"codersdk.OrganizationMemberWithName": {
"codersdk.OrganizationMemberWithUserData": {
"type": "object",
"properties": {
"avatar_url": {
"type": "string"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"global_roles": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.SlimRole"
}
},
"name": {
"type": "string"
},
"organization_id": {
"type": "string",
"format": "uuid"
+16 -8
View File
@@ -2044,7 +2044,7 @@
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.OrganizationMemberWithName"
"$ref": "#/definitions/codersdk.OrganizationMemberWithUserData"
}
}
}
@@ -2159,7 +2159,6 @@
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Members"],
"summary": "Remove organization member",
"operationId": "remove-organization-member",
@@ -2180,11 +2179,8 @@
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.OrganizationMember"
}
"204": {
"description": "No Content"
}
}
}
@@ -9446,13 +9442,25 @@
}
}
},
"codersdk.OrganizationMemberWithName": {
"codersdk.OrganizationMemberWithUserData": {
"type": "object",
"properties": {
"avatar_url": {
"type": "string"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"global_roles": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.SlimRole"
}
},
"name": {
"type": "string"
},
"organization_id": {
"type": "string",
"format": "uuid"
+22 -19
View File
@@ -166,25 +166,7 @@ func User(user database.User, organizationIDs []uuid.UUID) codersdk.User {
convertedUser := codersdk.User{
ReducedUser: ReducedUser(user),
OrganizationIDs: organizationIDs,
Roles: make([]codersdk.SlimRole, 0, len(user.RBACRoles)),
}
for _, roleName := range user.RBACRoles {
// TODO: Currently the api only returns site wide roles.
// Should it return organization roles?
rbacRole, err := rbac.RoleByName(rbac.RoleIdentifier{
Name: roleName,
OrganizationID: uuid.Nil,
})
if err == nil {
convertedUser.Roles = append(convertedUser.Roles, SlimRole(rbacRole))
} else {
// TODO: Fix this for custom roles to display the actual display_name
// Requires plumbing either a cached role value, or the db.
convertedUser.Roles = append(convertedUser.Roles, codersdk.SlimRole{
Name: roleName,
})
}
Roles: SlimRolesFromNames(user.RBACRoles),
}
return convertedUser
@@ -537,6 +519,27 @@ func SlimRole(role rbac.Role) codersdk.SlimRole {
}
}
func SlimRolesFromNames(names []string) []codersdk.SlimRole {
convertedRoles := make([]codersdk.SlimRole, 0, len(names))
for _, name := range names {
convertedRoles = append(convertedRoles, SlimRoleFromName(name))
}
return convertedRoles
}
func SlimRoleFromName(name string) codersdk.SlimRole {
rbacRole, err := rbac.RoleByName(rbac.RoleIdentifier{Name: name})
var convertedRole codersdk.SlimRole
if err == nil {
convertedRole = SlimRole(rbacRole)
} else {
convertedRole = codersdk.SlimRole{Name: name}
}
return convertedRole
}
func RBACRole(role rbac.Role) codersdk.Role {
slim := SlimRole(role)
+7 -1
View File
@@ -4283,7 +4283,7 @@ func (q *sqlQuerier) InsertOrganizationMember(ctx context.Context, arg InsertOrg
const organizationMembers = `-- name: OrganizationMembers :many
SELECT
organization_members.user_id, organization_members.organization_id, organization_members.created_at, organization_members.updated_at, organization_members.roles,
users.username
users.username, users.avatar_url, users.name, users.rbac_roles as "global_roles"
FROM
organization_members
INNER JOIN
@@ -4311,6 +4311,9 @@ type OrganizationMembersParams struct {
type OrganizationMembersRow struct {
OrganizationMember OrganizationMember `db:"organization_member" json:"organization_member"`
Username string `db:"username" json:"username"`
AvatarURL string `db:"avatar_url" json:"avatar_url"`
Name string `db:"name" json:"name"`
GlobalRoles pq.StringArray `db:"global_roles" json:"global_roles"`
}
// Arguments are optional with uuid.Nil to ignore.
@@ -4333,6 +4336,9 @@ func (q *sqlQuerier) OrganizationMembers(ctx context.Context, arg OrganizationMe
&i.OrganizationMember.UpdatedAt,
pq.Array(&i.OrganizationMember.Roles),
&i.Username,
&i.AvatarURL,
&i.Name,
&i.GlobalRoles,
); err != nil {
return nil, err
}
@@ -5,7 +5,7 @@
-- - Use both to get a specific org member row
SELECT
sqlc.embed(organization_members),
users.username
users.username, users.avatar_url, users.name, users.rbac_roles as "global_roles"
FROM
organization_members
INNER JOIN
+16 -8
View File
@@ -83,15 +83,15 @@ func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request)
// @Summary Remove organization member
// @ID remove-organization-member
// @Security CoderSessionToken
// @Produce json
// @Tags Members
// @Param organization path string true "Organization ID"
// @Param user path string true "User ID, name, or me"
// @Success 200 {object} codersdk.OrganizationMember
// @Success 204
// @Router /organizations/{organization}/members/{user} [delete]
func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
apiKey = httpmw.APIKey(r)
organization = httpmw.OrganizationParam(r)
member = httpmw.OrganizationMemberParam(r)
auditor = api.Auditor.Load()
@@ -106,6 +106,11 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
aReq.Old = member.OrganizationMember.Auditable(member.Username)
defer commitAudit()
if member.UserID == apiKey.UserID {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{Message: "cannot remove self from an organization"})
return
}
err := api.Database.DeleteOrganizationMember(ctx, database.DeleteOrganizationMemberParams{
OrganizationID: organization.ID,
UserID: member.UserID,
@@ -120,7 +125,7 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
}
aReq.New = database.AuditableOrganizationMember{}
httpapi.Write(ctx, rw, http.StatusOK, "organization member removed")
rw.WriteHeader(http.StatusNoContent)
}
// @Summary List organization members
@@ -129,7 +134,7 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
// @Produce json
// @Tags Members
// @Param organization path string true "Organization ID"
// @Success 200 {object} []codersdk.OrganizationMemberWithName
// @Success 200 {object} []codersdk.OrganizationMemberWithUserData
// @Router /organizations/{organization}/members [get]
func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) {
var (
@@ -150,7 +155,7 @@ func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) {
return
}
resp, err := convertOrganizationMemberRows(ctx, api.Database, members)
resp, err := convertOrganizationMembersWithUserData(ctx, api.Database, members)
if err != nil {
httpapi.InternalServerError(rw, err)
return
@@ -294,7 +299,7 @@ func convertOrganizationMembers(ctx context.Context, db database.Store, mems []d
return converted, nil
}
func convertOrganizationMemberRows(ctx context.Context, db database.Store, rows []database.OrganizationMembersRow) ([]codersdk.OrganizationMemberWithName, error) {
func convertOrganizationMembersWithUserData(ctx context.Context, db database.Store, rows []database.OrganizationMembersRow) ([]codersdk.OrganizationMemberWithUserData, error) {
members := make([]database.OrganizationMember, 0)
for _, row := range rows {
members = append(members, row.OrganizationMember)
@@ -308,10 +313,13 @@ func convertOrganizationMemberRows(ctx context.Context, db database.Store, rows
return nil, xerrors.Errorf("conversion failed, mismatch slice lengths")
}
converted := make([]codersdk.OrganizationMemberWithName, 0)
converted := make([]codersdk.OrganizationMemberWithUserData, 0)
for i := range convertedMembers {
converted = append(converted, codersdk.OrganizationMemberWithName{
converted = append(converted, codersdk.OrganizationMemberWithUserData{
Username: rows[i].Username,
AvatarURL: rows[i].AvatarURL,
Name: rows[i].Name,
GlobalRoles: db2sdk.SlimRolesFromNames(rows[i].GlobalRoles),
OrganizationMember: convertedMembers[i],
})
}
+1 -1
View File
@@ -185,6 +185,6 @@ func TestRemoveMember(t *testing.T) {
})
}
func onlyIDs(u codersdk.OrganizationMemberWithName) uuid.UUID {
func onlyIDs(u codersdk.OrganizationMemberWithUserData) uuid.UUID {
return u.UserID
}