From 598f1035499f92edd10d29a8f106c0a51a93f56b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 26 Jul 2023 13:01:48 -0400 Subject: [PATCH 01/19] feat: add display_name field to groups This is a non-unique human friendly group name for display purposes. This means a display name can be used instead of using an environment var to remap groups with OIDC names to Coder names. Now groups can retain the OIDC name for mapping, and use a display name for display purposes. --- coderd/apidoc/docs.go | 3 +++ coderd/apidoc/swagger.json | 3 +++ coderd/database/dbfake/dbfake.go | 2 ++ coderd/database/dump.sql | 5 ++++- coderd/database/models.go | 2 ++ coderd/database/queries.sql.go | 30 +++++++++++++++++++++--------- coderd/database/queries/groups.sql | 12 +++++++----- codersdk/groups.go | 3 +++ docs/admin/audit-logs.md | 2 +- docs/api/enterprise.md | 9 +++++++++ docs/api/schemas.md | 3 +++ enterprise/audit/table.go | 1 + enterprise/coderd/groups.go | 15 +++++++++++++++ site/src/api/typesGenerated.ts | 2 ++ 14 files changed, 76 insertions(+), 16 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 81dbdcfa60b52..e14516bebfbf0 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -8121,6 +8121,9 @@ const docTemplate = `{ "avatar_url": { "type": "string" }, + "display_name": { + "type": "string" + }, "id": { "type": "string", "format": "uuid" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 383b7d7a2eee5..27f3702d8839a 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -7289,6 +7289,9 @@ "avatar_url": { "type": "string" }, + "display_name": { + "type": "string" + }, "id": { "type": "string", "format": "uuid" diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 21b026bf2c782..0b2015ea7d9e0 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -3500,6 +3500,7 @@ func (q *FakeQuerier) InsertGroup(_ context.Context, arg database.InsertGroupPar group := database.Group{ ID: arg.ID, Name: arg.Name, + DisplayName: arg.DisplayName, OrganizationID: arg.OrganizationID, AvatarURL: arg.AvatarURL, QuotaAllowance: arg.QuotaAllowance, @@ -4297,6 +4298,7 @@ func (q *FakeQuerier) UpdateGroupByID(_ context.Context, arg database.UpdateGrou for i, group := range q.groups { if group.ID == arg.ID { + group.DisplayName = arg.DisplayName group.Name = arg.Name group.AvatarURL = arg.AvatarURL group.QuotaAllowance = arg.QuotaAllowance diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 3dfe832c621ad..a9c86e10b1057 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -286,9 +286,12 @@ CREATE TABLE groups ( name text NOT NULL, organization_id uuid NOT NULL, avatar_url text DEFAULT ''::text NOT NULL, - quota_allowance integer DEFAULT 0 NOT NULL + quota_allowance integer DEFAULT 0 NOT NULL, + display_name text DEFAULT ''::text NOT NULL ); +COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friendly group name that user can set. This is not required to be unique.'; + CREATE TABLE licenses ( id integer NOT NULL, uploaded_at timestamp with time zone NOT NULL, diff --git a/coderd/database/models.go b/coderd/database/models.go index d8b044443ffe8..230796149645f 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1422,6 +1422,8 @@ type Group struct { OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` AvatarURL string `db:"avatar_url" json:"avatar_url"` QuotaAllowance int32 `db:"quota_allowance" json:"quota_allowance"` + // Display name is a custom, human-friendly group name that user can set. This is not required to be unique. + DisplayName string `db:"display_name" json:"display_name"` } type GroupMember struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 95ad36d9593ca..b248da5a59256 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1180,7 +1180,7 @@ func (q *sqlQuerier) DeleteGroupByID(ctx context.Context, id uuid.UUID) error { const getGroupByID = `-- name: GetGroupByID :one SELECT - id, name, organization_id, avatar_url, quota_allowance + id, name, organization_id, avatar_url, quota_allowance, display_name FROM groups WHERE @@ -1198,13 +1198,14 @@ func (q *sqlQuerier) GetGroupByID(ctx context.Context, id uuid.UUID) (Group, err &i.OrganizationID, &i.AvatarURL, &i.QuotaAllowance, + &i.DisplayName, ) return i, err } const getGroupByOrgAndName = `-- name: GetGroupByOrgAndName :one SELECT - id, name, organization_id, avatar_url, quota_allowance + id, name, organization_id, avatar_url, quota_allowance, display_name FROM groups WHERE @@ -1229,13 +1230,14 @@ func (q *sqlQuerier) GetGroupByOrgAndName(ctx context.Context, arg GetGroupByOrg &i.OrganizationID, &i.AvatarURL, &i.QuotaAllowance, + &i.DisplayName, ) return i, err } const getGroupsByOrganizationID = `-- name: GetGroupsByOrganizationID :many SELECT - id, name, organization_id, avatar_url, quota_allowance + id, name, organization_id, avatar_url, quota_allowance, display_name FROM groups WHERE @@ -1259,6 +1261,7 @@ func (q *sqlQuerier) GetGroupsByOrganizationID(ctx context.Context, organization &i.OrganizationID, &i.AvatarURL, &i.QuotaAllowance, + &i.DisplayName, ); err != nil { return nil, err } @@ -1280,7 +1283,7 @@ INSERT INTO groups ( organization_id ) VALUES - ($1, 'Everyone', $1) RETURNING id, name, organization_id, avatar_url, quota_allowance + ($1, 'Everyone', $1) RETURNING id, name, organization_id, avatar_url, quota_allowance, display_name ` // We use the organization_id as the id @@ -1295,6 +1298,7 @@ func (q *sqlQuerier) InsertAllUsersGroup(ctx context.Context, organizationID uui &i.OrganizationID, &i.AvatarURL, &i.QuotaAllowance, + &i.DisplayName, ) return i, err } @@ -1303,17 +1307,19 @@ const insertGroup = `-- name: InsertGroup :one INSERT INTO groups ( id, name, + display_name, organization_id, avatar_url, quota_allowance ) VALUES - ($1, $2, $3, $4, $5) RETURNING id, name, organization_id, avatar_url, quota_allowance + ($1, $2, $3, $4, $5, $6) RETURNING id, name, organization_id, avatar_url, quota_allowance, display_name ` type InsertGroupParams struct { ID uuid.UUID `db:"id" json:"id"` Name string `db:"name" json:"name"` + DisplayName string `db:"display_name" json:"display_name"` OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` AvatarURL string `db:"avatar_url" json:"avatar_url"` QuotaAllowance int32 `db:"quota_allowance" json:"quota_allowance"` @@ -1323,6 +1329,7 @@ func (q *sqlQuerier) InsertGroup(ctx context.Context, arg InsertGroupParams) (Gr row := q.db.QueryRowContext(ctx, insertGroup, arg.ID, arg.Name, + arg.DisplayName, arg.OrganizationID, arg.AvatarURL, arg.QuotaAllowance, @@ -1334,6 +1341,7 @@ func (q *sqlQuerier) InsertGroup(ctx context.Context, arg InsertGroupParams) (Gr &i.OrganizationID, &i.AvatarURL, &i.QuotaAllowance, + &i.DisplayName, ) return i, err } @@ -1343,15 +1351,17 @@ UPDATE groups SET name = $1, - avatar_url = $2, - quota_allowance = $3 + display_name = $2, + avatar_url = $3, + quota_allowance = $4 WHERE - id = $4 -RETURNING id, name, organization_id, avatar_url, quota_allowance + id = $5 +RETURNING id, name, organization_id, avatar_url, quota_allowance, display_name ` type UpdateGroupByIDParams struct { Name string `db:"name" json:"name"` + DisplayName string `db:"display_name" json:"display_name"` AvatarURL string `db:"avatar_url" json:"avatar_url"` QuotaAllowance int32 `db:"quota_allowance" json:"quota_allowance"` ID uuid.UUID `db:"id" json:"id"` @@ -1360,6 +1370,7 @@ type UpdateGroupByIDParams struct { func (q *sqlQuerier) UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error) { row := q.db.QueryRowContext(ctx, updateGroupByID, arg.Name, + arg.DisplayName, arg.AvatarURL, arg.QuotaAllowance, arg.ID, @@ -1371,6 +1382,7 @@ func (q *sqlQuerier) UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDPar &i.OrganizationID, &i.AvatarURL, &i.QuotaAllowance, + &i.DisplayName, ) return i, err } diff --git a/coderd/database/queries/groups.sql b/coderd/database/queries/groups.sql index a80a4c22567bb..9b258414887d7 100644 --- a/coderd/database/queries/groups.sql +++ b/coderd/database/queries/groups.sql @@ -34,12 +34,13 @@ AND INSERT INTO groups ( id, name, + display_name, organization_id, avatar_url, quota_allowance ) VALUES - ($1, $2, $3, $4, $5) RETURNING *; + ($1, $2, $3, $4, $5, $6) RETURNING *; -- We use the organization_id as the id -- for simplicity since all users is @@ -57,11 +58,12 @@ VALUES UPDATE groups SET - name = $1, - avatar_url = $2, - quota_allowance = $3 + name = @name, + display_name = @display_name, + avatar_url = @avatar_url, + quota_allowance = @quota_allowance WHERE - id = $4 + id = @id RETURNING *; -- name: DeleteGroupByID :exec diff --git a/codersdk/groups.go b/codersdk/groups.go index 3d9495eb074c2..189f4fc413cd6 100644 --- a/codersdk/groups.go +++ b/codersdk/groups.go @@ -12,6 +12,7 @@ import ( type CreateGroupRequest struct { Name string `json:"name"` + DisplayName string `json:"display_name"` AvatarURL string `json:"avatar_url"` QuotaAllowance int `json:"quota_allowance"` } @@ -19,6 +20,7 @@ type CreateGroupRequest struct { type Group struct { ID uuid.UUID `json:"id" format:"uuid"` Name string `json:"name"` + DisplayName string `json:"display_name"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` Members []User `json:"members"` AvatarURL string `json:"avatar_url"` @@ -98,6 +100,7 @@ type PatchGroupRequest struct { AddUsers []string `json:"add_users"` RemoveUsers []string `json:"remove_users"` Name string `json:"name"` + DisplayName string `db:"display_name" json:"display_name"` AvatarURL *string `json:"avatar_url"` QuotaAllowance *int `json:"quota_allowance"` } diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index 214c96b2940f5..55985066f3c0e 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -13,7 +13,7 @@ We track the following resources: | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| | AuditOAuthConvertState
|
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| -| Group
create, write, delete |
FieldTracked
avatar_urltrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
| +| Group
create, write, delete |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
| | GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| | License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| | Template
write, delete |
FieldTracked
active_version_idtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
inactivity_ttltrue
locked_ttltrue
max_ttltrue
nametrue
organization_idfalse
provisionertrue
restart_requirement_days_of_weektrue
restart_requirement_weekstrue
updated_atfalse
user_acltrue
| diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 0eb076461a3be..f55b936bd28c8 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -174,6 +174,7 @@ curl -X GET http://coder-server:8080/api/v2/groups/{group} \ ```json { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -234,6 +235,7 @@ curl -X DELETE http://coder-server:8080/api/v2/groups/{group} \ ```json { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -294,6 +296,7 @@ curl -X PATCH http://coder-server:8080/api/v2/groups/{group} \ ```json { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -429,6 +432,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups [ { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -470,6 +474,7 @@ Status Code **200** | --------------------- | ---------------------------------------------------- | -------- | ------------ | ----------- | | `[array item]` | array | false | | | | `» avatar_url` | string | false | | | +| `» display_name` | string | false | | | | `» id` | string(uuid) | false | | | | `» members` | array | false | | | | `»» avatar_url` | string(uri) | false | | | @@ -540,6 +545,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/groups ```json { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -601,6 +607,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups/ ```json { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -1171,6 +1178,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl/available \ "groups": [ { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -1234,6 +1242,7 @@ Status Code **200** | `[array item]` | array | false | | | | `» groups` | array | false | | | | `»» avatar_url` | string | false | | | +| `»» display_name` | string | false | | | | `»» id` | string(uuid) | false | | | | `»» members` | array | false | | | | `»»» avatar_url` | string(uri) | false | | | diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 8ca036217530b..30c8768a37783 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -762,6 +762,7 @@ "groups": [ { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -2928,6 +2929,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { "avatar_url": "string", + "display_name": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "members": [ { @@ -2959,6 +2961,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ----------------- | --------------------------------------- | -------- | ------------ | ----------- | | `avatar_url` | string | false | | | +| `display_name` | string | false | | | | `id` | string | false | | | | `members` | array of [codersdk.User](#codersdkuser) | false | | | | `name` | string | false | | | diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 27bcb48081fd5..46b51e72fb85c 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -151,6 +151,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ &database.AuditableGroup{}: { "id": ActionTrack, "name": ActionTrack, + "display_name": ActionTrack, "organization_id": ActionIgnore, // Never changes. "avatar_url": ActionTrack, "quota_allowance": ActionTrack, diff --git a/enterprise/coderd/groups.go b/enterprise/coderd/groups.go index 79fba137f81e8..cd57f473d3b6e 100644 --- a/enterprise/coderd/groups.go +++ b/enterprise/coderd/groups.go @@ -53,9 +53,14 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request) return } + if req.DisplayName == "" { + req.DisplayName = req.Name + } + group, err := api.Database.InsertGroup(ctx, database.InsertGroupParams{ ID: uuid.New(), Name: req.Name, + DisplayName: req.DisplayName, OrganizationID: org.ID, AvatarURL: req.AvatarURL, QuotaAllowance: int32(req.QuotaAllowance), @@ -125,6 +130,11 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { req.Name = "" } + // Do not update the display name if it is the same. + if req.DisplayName == group.DisplayName { + req.DisplayName = "" + } + users := make([]string, 0, len(req.AddUsers)+len(req.RemoveUsers)) users = append(users, req.AddUsers...) users = append(users, req.RemoveUsers...) @@ -177,6 +187,7 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { ID: group.ID, AvatarURL: group.AvatarURL, Name: group.Name, + DisplayName: group.DisplayName, QuotaAllowance: group.QuotaAllowance, } @@ -190,6 +201,9 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { if req.QuotaAllowance != nil { updateGroupParams.QuotaAllowance = int32(*req.QuotaAllowance) } + if req.DisplayName != "" { + updateGroupParams.DisplayName = req.DisplayName + } group, err = tx.UpdateGroupByID(ctx, updateGroupParams) if err != nil { @@ -398,6 +412,7 @@ func convertGroup(g database.Group, users []database.User) codersdk.Group { return codersdk.Group{ ID: g.ID, Name: g.Name, + DisplayName: g.DisplayName, OrganizationID: g.OrganizationID, AvatarURL: g.AvatarURL, QuotaAllowance: int(g.QuotaAllowance), diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 2a7a05cbddd15..f79fad21c2d55 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -507,6 +507,7 @@ export interface GitSSHKey { export interface Group { readonly id: string readonly name: string + readonly display_name: string readonly organization_id: string readonly members: User[] readonly avatar_url: string @@ -665,6 +666,7 @@ export interface PatchGroupRequest { readonly add_users: string[] readonly remove_users: string[] readonly name: string + readonly display_name: string readonly avatar_url?: string readonly quota_allowance?: number } From 712f81a9916da372f7da0b579ed4f3ac9b47d358 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 26 Jul 2023 13:13:49 -0400 Subject: [PATCH 02/19] add missing migrations --- .../migrations/000142_group_display_name.down.sql | 6 ++++++ .../migrations/000142_group_display_name.up.sql | 11 +++++++++++ 2 files changed, 17 insertions(+) create mode 100644 coderd/database/migrations/000142_group_display_name.down.sql create mode 100644 coderd/database/migrations/000142_group_display_name.up.sql diff --git a/coderd/database/migrations/000142_group_display_name.down.sql b/coderd/database/migrations/000142_group_display_name.down.sql new file mode 100644 index 0000000000000..b04850449fedc --- /dev/null +++ b/coderd/database/migrations/000142_group_display_name.down.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE groups + DROP COLUMN display_name; + +COMMIT; diff --git a/coderd/database/migrations/000142_group_display_name.up.sql b/coderd/database/migrations/000142_group_display_name.up.sql new file mode 100644 index 0000000000000..6b6ba22ad939b --- /dev/null +++ b/coderd/database/migrations/000142_group_display_name.up.sql @@ -0,0 +1,11 @@ +BEGIN; + +ALTER TABLE groups + ADD COLUMN display_name TEXT NOT NULL DEFAULT ''; + +COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friendly group name that user can set. This is not required to be unique.'; + +-- Use the name as the display name for all existing groups +UPDATE groups SET display_name = name; + +COMMIT; From 8cfc0663e746fccce41275f764498a8d8adf90ad Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 26 Jul 2023 15:32:53 -0400 Subject: [PATCH 03/19] show display name on ui, show name if it differs on the group page --- coderd/apidoc/docs.go | 3 +++ coderd/apidoc/swagger.json | 3 +++ docs/api/enterprise.md | 1 + docs/api/schemas.md | 2 ++ enterprise/coderd/groups.go | 1 + site/src/pages/GroupsPage/GroupPage.tsx | 17 +++++++++++++---- site/src/pages/GroupsPage/GroupsPageView.tsx | 4 ++-- .../pages/GroupsPage/SettingsGroupPageView.tsx | 2 ++ .../TemplatePermissionsPageView.tsx | 4 ++-- site/src/testHelpers/entities.ts | 1 + site/src/utils/groups.ts | 1 + site/src/xServices/groups/editGroupXService.ts | 2 +- site/src/xServices/groups/groupXService.ts | 2 ++ 13 files changed, 34 insertions(+), 9 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index e14516bebfbf0..9c36ceacb5db5 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -7135,6 +7135,9 @@ const docTemplate = `{ "avatar_url": { "type": "string" }, + "display_name": { + "type": "string" + }, "name": { "type": "string" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 27f3702d8839a..42c30728fe7b9 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6355,6 +6355,9 @@ "avatar_url": { "type": "string" }, + "display_name": { + "type": "string" + }, "name": { "type": "string" }, diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index f55b936bd28c8..84932ec90ecb0 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -526,6 +526,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/groups ```json { "avatar_url": "string", + "display_name": "string", "name": "string", "quota_allowance": 0 } diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 30c8768a37783..9c89cdc46b2aa 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1418,6 +1418,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { "avatar_url": "string", + "display_name": "string", "name": "string", "quota_allowance": 0 } @@ -1428,6 +1429,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ----------------- | ------- | -------- | ------------ | ----------- | | `avatar_url` | string | false | | | +| `display_name` | string | false | | | | `name` | string | false | | | | `quota_allowance` | integer | false | | | diff --git a/enterprise/coderd/groups.go b/enterprise/coderd/groups.go index cd57f473d3b6e..0630be3ddd54a 100644 --- a/enterprise/coderd/groups.go +++ b/enterprise/coderd/groups.go @@ -53,6 +53,7 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request) return } + // Default the name and display name to the same. if req.DisplayName == "" { req.DisplayName = req.Name } diff --git a/site/src/pages/GroupsPage/GroupPage.tsx b/site/src/pages/GroupsPage/GroupPage.tsx index 26299317b0565..545db03360824 100644 --- a/site/src/pages/GroupsPage/GroupPage.tsx +++ b/site/src/pages/GroupsPage/GroupPage.tsx @@ -33,6 +33,7 @@ import { pageTitle } from "utils/page" import { groupMachine } from "xServices/groups/groupXService" import { Maybe } from "components/Conditionals/Maybe" import { makeStyles } from "@mui/styles" +import { PaginationStatus } from "components/PaginationStatus/PaginationStatus" const AddGroupMember: React.FC<{ isLoading: boolean @@ -101,7 +102,7 @@ export const GroupPage: React.FC = () => { return ( <> - {pageTitle(group?.name ?? "Loading...")} + {pageTitle(group?.display_name ?? "Loading...")} @@ -127,13 +128,14 @@ export const GroupPage: React.FC = () => { } > - {group?.name} + {group?.display_name} - {group?.members.length} members + {/* Show the name if it differs from the display name. */} + {group?.display_name !== group?.name ? group?.name : ""}{" "} - + { }} /> + + diff --git a/site/src/pages/GroupsPage/GroupsPageView.tsx b/site/src/pages/GroupsPage/GroupsPageView.tsx index b14bc7fdf6e85..39b3db374aebf 100644 --- a/site/src/pages/GroupsPage/GroupsPageView.tsx +++ b/site/src/pages/GroupsPage/GroupsPageView.tsx @@ -137,11 +137,11 @@ export const GroupsPageView: FC = ({ } - title={group.name} + title={group.display_name} subtitle={`${group.members.length} members`} /> diff --git a/site/src/pages/GroupsPage/SettingsGroupPageView.tsx b/site/src/pages/GroupsPage/SettingsGroupPageView.tsx index f5c85958942f0..1064dee57cba2 100644 --- a/site/src/pages/GroupsPage/SettingsGroupPageView.tsx +++ b/site/src/pages/GroupsPage/SettingsGroupPageView.tsx @@ -15,6 +15,7 @@ import { Stack } from "components/Stack/Stack" type FormData = { name: string + display_name: string avatar_url: string quota_allowance: number } @@ -34,6 +35,7 @@ const UpdateGroupForm: FC<{ const form = useFormik({ initialValues: { name: group.name, + display_name: group.display_name, avatar_url: group.avatar_url, quota_allowance: group.quota_allowance, }, diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx index c42b78cba26f3..62472707513eb 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx @@ -253,11 +253,11 @@ export const TemplatePermissionsPageView: FC< } - title={group.name} + title={group.display_name} subtitle={getGroupSubtitle(group)} /> diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index ad7259c05a76b..45e666f7596cf 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -1658,6 +1658,7 @@ export const MockWorkspaceQuota: TypesGen.WorkspaceQuota = { export const MockGroup: TypesGen.Group = { id: "fbd2116a-8961-4954-87ae-e4575bd29ce0", name: "Front-End", + display_name: "Front-End", avatar_url: "https://example.com", organization_id: MockOrganization.id, members: [MockUser, MockUser2], diff --git a/site/src/utils/groups.ts b/site/src/utils/groups.ts index f96a9f3545cd6..28b7a07d5c90a 100644 --- a/site/src/utils/groups.ts +++ b/site/src/utils/groups.ts @@ -3,6 +3,7 @@ import { Group } from "api/typesGenerated" export const everyOneGroup = (organizationId: string): Group => ({ id: organizationId, name: "Everyone", + display_name: "Everyone", organization_id: organizationId, members: [], avatar_url: "", diff --git a/site/src/xServices/groups/editGroupXService.ts b/site/src/xServices/groups/editGroupXService.ts index 25104dbb62ca1..6aceaa84e36cb 100644 --- a/site/src/xServices/groups/editGroupXService.ts +++ b/site/src/xServices/groups/editGroupXService.ts @@ -23,7 +23,7 @@ export const editGroupMachine = createMachine( }, events: {} as { type: "UPDATE" - data: { name: string; avatar_url: string; quota_allowance: number } + data: { display_name: string, name: string; avatar_url: string; quota_allowance: number } }, }, tsTypes: {} as import("./editGroupXService.typegen").Typegen0, diff --git a/site/src/xServices/groups/groupXService.ts b/site/src/xServices/groups/groupXService.ts index 66d9b482d1022..d9aa63fa23f4f 100644 --- a/site/src/xServices/groups/groupXService.ts +++ b/site/src/xServices/groups/groupXService.ts @@ -183,6 +183,7 @@ export const groupMachine = createMachine( return patchGroup(group.id, { name: "", + display_name: "", add_users: [userId], remove_users: [], }) @@ -194,6 +195,7 @@ export const groupMachine = createMachine( return patchGroup(group.id, { name: "", + display_name: "", add_users: [], remove_users: [userId], }) From 15f19faeffa99ff3d88ba28c060974be83ed6264 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 26 Jul 2023 15:40:29 -0400 Subject: [PATCH 04/19] Fix insert all users group --- coderd/database/dbfake/dbfake.go | 1 + coderd/database/dbgen/dbgen.go | 4 +++- coderd/database/queries/groups.sql | 3 ++- .../UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx | 4 ++-- site/src/pages/GroupsPage/GroupsPageView.stories.tsx | 7 +++++++ 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 0b2015ea7d9e0..b88a326c64954 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -3381,6 +3381,7 @@ func (q *FakeQuerier) InsertAllUsersGroup(ctx context.Context, orgID uuid.UUID) return q.InsertGroup(ctx, database.InsertGroupParams{ ID: orgID, Name: database.AllUsersGroup, + DisplayName: database.AllUsersGroup, OrganizationID: orgID, }) } diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 6b123cf4f3677..35e6986849bac 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -272,9 +272,11 @@ func OrganizationMember(t testing.TB, db database.Store, orig database.Organizat } func Group(t testing.TB, db database.Store, orig database.Group) database.Group { + name := takeFirst(orig.Name, namesgenerator.GetRandomName(1)) group, err := db.InsertGroup(genCtx, database.InsertGroupParams{ ID: takeFirst(orig.ID, uuid.New()), - Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)), + Name: name, + DisplayName: takeFirst(orig.DisplayName, name), OrganizationID: takeFirst(orig.OrganizationID, uuid.New()), AvatarURL: takeFirst(orig.AvatarURL, "https://logo.example.com"), QuotaAllowance: takeFirst(orig.QuotaAllowance, 0), diff --git a/coderd/database/queries/groups.sql b/coderd/database/queries/groups.sql index 9b258414887d7..eb17952374b2d 100644 --- a/coderd/database/queries/groups.sql +++ b/coderd/database/queries/groups.sql @@ -49,10 +49,11 @@ VALUES INSERT INTO groups ( id, name, + display_name, organization_id ) VALUES - (sqlc.arg(organization_id), 'Everyone', sqlc.arg(organization_id)) RETURNING *; + (sqlc.arg(organization_id), 'Everyone', 'Everyone', sqlc.arg(organization_id)) RETURNING *; -- name: UpdateGroupByID :one UPDATE diff --git a/site/src/components/UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx b/site/src/components/UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx index 5a1e2f08aa45e..43e5c432848fc 100644 --- a/site/src/components/UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx +++ b/site/src/components/UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx @@ -71,7 +71,7 @@ export const UserOrGroupAutocomplete: React.FC< }} isOptionEqualToValue={(option, value) => option.id === value.id} getOptionLabel={(option) => - isGroup(option) ? option.name : option.email + isGroup(option) ? option.display_name : option.email } renderOption={(props, option) => { const isOptionGroup = isGroup(option) @@ -79,7 +79,7 @@ export const UserOrGroupAutocomplete: React.FC< return ( diff --git a/site/src/pages/GroupsPage/GroupsPageView.stories.tsx b/site/src/pages/GroupsPage/GroupsPageView.stories.tsx index cc5456207ed6b..c82baac4d5c01 100644 --- a/site/src/pages/GroupsPage/GroupsPageView.stories.tsx +++ b/site/src/pages/GroupsPage/GroupsPageView.stories.tsx @@ -25,6 +25,13 @@ WithGroups.args = { isTemplateRBACEnabled: true, } +export const WithDisplayGroup = Template.bind({}) +WithGroups.args = { + groups: [{ ...MockGroup, name: "front-end" }], + canCreateGroup: true, + isTemplateRBACEnabled: true, +} + export const EmptyGroup = Template.bind({}) EmptyGroup.args = { groups: [], From 532aaa105038007f5e3b881e38c981ba3ccb6b1c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 26 Jul 2023 16:01:52 -0400 Subject: [PATCH 05/19] display name defaults to empty string --- coderd/database/dbfake/dbfake.go | 2 +- .../migrations/000142_group_display_name.up.sql | 3 --- coderd/database/queries/groups.sql | 3 +-- enterprise/cli/grouplist.go | 2 ++ enterprise/coderd/groups.go | 16 ++++++++++------ .../pages/GroupsPage/SettingsGroupPageView.tsx | 16 +++++++++++++++- 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index b88a326c64954..e44816c74a4bf 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -3381,7 +3381,7 @@ func (q *FakeQuerier) InsertAllUsersGroup(ctx context.Context, orgID uuid.UUID) return q.InsertGroup(ctx, database.InsertGroupParams{ ID: orgID, Name: database.AllUsersGroup, - DisplayName: database.AllUsersGroup, + DisplayName: "", OrganizationID: orgID, }) } diff --git a/coderd/database/migrations/000142_group_display_name.up.sql b/coderd/database/migrations/000142_group_display_name.up.sql index 6b6ba22ad939b..e7b6d69bfc0b2 100644 --- a/coderd/database/migrations/000142_group_display_name.up.sql +++ b/coderd/database/migrations/000142_group_display_name.up.sql @@ -5,7 +5,4 @@ ALTER TABLE groups COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friendly group name that user can set. This is not required to be unique.'; --- Use the name as the display name for all existing groups -UPDATE groups SET display_name = name; - COMMIT; diff --git a/coderd/database/queries/groups.sql b/coderd/database/queries/groups.sql index eb17952374b2d..9b258414887d7 100644 --- a/coderd/database/queries/groups.sql +++ b/coderd/database/queries/groups.sql @@ -49,11 +49,10 @@ VALUES INSERT INTO groups ( id, name, - display_name, organization_id ) VALUES - (sqlc.arg(organization_id), 'Everyone', 'Everyone', sqlc.arg(organization_id)) RETURNING *; + (sqlc.arg(organization_id), 'Everyone', sqlc.arg(organization_id)) RETURNING *; -- name: UpdateGroupByID :one UPDATE diff --git a/enterprise/cli/grouplist.go b/enterprise/cli/grouplist.go index f164589c49e21..51c36c4ba8ac9 100644 --- a/enterprise/cli/grouplist.go +++ b/enterprise/cli/grouplist.go @@ -67,6 +67,7 @@ type groupTableRow struct { // For table output: Name string `json:"-" table:"name,default_sort"` + DisplayName string `json:"-" table:"display_name"` OrganizationID uuid.UUID `json:"-" table:"organization_id"` Members []string `json:"-" table:"members"` AvatarURL string `json:"-" table:"avatar_url"` @@ -81,6 +82,7 @@ func groupsToRows(groups ...codersdk.Group) []groupTableRow { } rows = append(rows, groupTableRow{ Name: group.Name, + DisplayName: group.DisplayName, OrganizationID: group.OrganizationID, AvatarURL: group.AvatarURL, Members: members, diff --git a/enterprise/coderd/groups.go b/enterprise/coderd/groups.go index 0630be3ddd54a..cc9928d3c6c54 100644 --- a/enterprise/coderd/groups.go +++ b/enterprise/coderd/groups.go @@ -131,11 +131,6 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { req.Name = "" } - // Do not update the display name if it is the same. - if req.DisplayName == group.DisplayName { - req.DisplayName = "" - } - users := make([]string, 0, len(req.AddUsers)+len(req.RemoveUsers)) users = append(users, req.AddUsers...) users = append(users, req.RemoveUsers...) @@ -205,6 +200,10 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { if req.DisplayName != "" { updateGroupParams.DisplayName = req.DisplayName } + // If the names are identical, then remove the display name. + if req.DisplayName == req.Name { + updateGroupParams.DisplayName = "" + } group, err = tx.UpdateGroupByID(ctx, updateGroupParams) if err != nil { @@ -410,10 +409,15 @@ func convertGroup(g database.Group, users []database.User) codersdk.Group { for _, user := range users { orgs[user.ID] = []uuid.UUID{g.OrganizationID} } + // Always default to the group name if the display name is empty + displayName := g.DisplayName + if displayName == "" { + displayName = g.Name + } return codersdk.Group{ ID: g.ID, Name: g.Name, - DisplayName: g.DisplayName, + DisplayName: displayName, OrganizationID: g.OrganizationID, AvatarURL: g.AvatarURL, QuotaAllowance: int(g.QuotaAllowance), diff --git a/site/src/pages/GroupsPage/SettingsGroupPageView.tsx b/site/src/pages/GroupsPage/SettingsGroupPageView.tsx index 1064dee57cba2..1721cc50abdbc 100644 --- a/site/src/pages/GroupsPage/SettingsGroupPageView.tsx +++ b/site/src/pages/GroupsPage/SettingsGroupPageView.tsx @@ -35,7 +35,9 @@ const UpdateGroupForm: FC<{ const form = useFormik({ initialValues: { name: group.name, - display_name: group.display_name, + // If these are equal, keep the display name blank. A blank display name means + // default to using the name. + display_name: group.display_name === group.name ? "" : group.display_name, avatar_url: group.avatar_url, quota_allowance: group.quota_allowance, }, @@ -57,6 +59,18 @@ const UpdateGroupForm: FC<{ fullWidth label="Name" /> + {/* We might want to always show this at some point, but for now only show if + the display name differs from the original name. */} + {group.name !== group.display_name && ( + + )} Date: Wed, 26 Jul 2023 16:02:59 -0400 Subject: [PATCH 06/19] Simplify display name patch logic --- enterprise/coderd/groups.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/enterprise/coderd/groups.go b/enterprise/coderd/groups.go index cc9928d3c6c54..0ef47d3e460c7 100644 --- a/enterprise/coderd/groups.go +++ b/enterprise/coderd/groups.go @@ -200,10 +200,6 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { if req.DisplayName != "" { updateGroupParams.DisplayName = req.DisplayName } - // If the names are identical, then remove the display name. - if req.DisplayName == req.Name { - updateGroupParams.DisplayName = "" - } group, err = tx.UpdateGroupByID(ctx, updateGroupParams) if err != nil { From 2debc91c0443343f3ba46756526eface20baa00e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 26 Jul 2023 16:28:03 -0400 Subject: [PATCH 07/19] Add display name to edit group UI --- codersdk/groups.go | 2 +- docs/cli/groups_list.md | 10 +++---- enterprise/coderd/groups.go | 9 ++----- site/src/api/typesGenerated.ts | 3 ++- .../pages/GroupsPage/CreateGroupPageView.tsx | 1 + .../GroupsPage/SettingsGroupPageView.tsx | 27 +++++++++---------- 6 files changed, 23 insertions(+), 29 deletions(-) diff --git a/codersdk/groups.go b/codersdk/groups.go index 189f4fc413cd6..c04267e4e0eb2 100644 --- a/codersdk/groups.go +++ b/codersdk/groups.go @@ -100,7 +100,7 @@ type PatchGroupRequest struct { AddUsers []string `json:"add_users"` RemoveUsers []string `json:"remove_users"` Name string `json:"name"` - DisplayName string `db:"display_name" json:"display_name"` + DisplayName *string `json:"display_name"` AvatarURL *string `json:"avatar_url"` QuotaAllowance *int `json:"quota_allowance"` } diff --git a/docs/cli/groups_list.md b/docs/cli/groups_list.md index 3766de273abf4..5f9e184f3995d 100644 --- a/docs/cli/groups_list.md +++ b/docs/cli/groups_list.md @@ -14,12 +14,12 @@ coder groups list [flags] ### -c, --column -| | | -| ------- | ---------------------------------------------------- | -| Type | string-array | -| Default | name,organization id,members,avatar url | +| | | +| ------- | ----------------------------------------------------------------- | +| Type | string-array | +| Default | name,display name,organization id,members,avatar url | -Columns to display in table output. Available columns: name, organization id, members, avatar url. +Columns to display in table output. Available columns: name, display name, organization id, members, avatar url. ### -o, --output diff --git a/enterprise/coderd/groups.go b/enterprise/coderd/groups.go index 0ef47d3e460c7..932fd0054a12e 100644 --- a/enterprise/coderd/groups.go +++ b/enterprise/coderd/groups.go @@ -53,11 +53,6 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request) return } - // Default the name and display name to the same. - if req.DisplayName == "" { - req.DisplayName = req.Name - } - group, err := api.Database.InsertGroup(ctx, database.InsertGroupParams{ ID: uuid.New(), Name: req.Name, @@ -197,8 +192,8 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { if req.QuotaAllowance != nil { updateGroupParams.QuotaAllowance = int32(*req.QuotaAllowance) } - if req.DisplayName != "" { - updateGroupParams.DisplayName = req.DisplayName + if req.DisplayName != nil { + updateGroupParams.DisplayName = *req.DisplayName } group, err = tx.UpdateGroupByID(ctx, updateGroupParams) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f79fad21c2d55..4badaa158f33d 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -175,6 +175,7 @@ export interface CreateFirstUserResponse { // From codersdk/groups.go export interface CreateGroupRequest { readonly name: string + readonly display_name: string readonly avatar_url: string readonly quota_allowance: number } @@ -666,7 +667,7 @@ export interface PatchGroupRequest { readonly add_users: string[] readonly remove_users: string[] readonly name: string - readonly display_name: string + readonly display_name?: string readonly avatar_url?: string readonly quota_allowance?: number } diff --git a/site/src/pages/GroupsPage/CreateGroupPageView.tsx b/site/src/pages/GroupsPage/CreateGroupPageView.tsx index 961fc97c488b8..99c64d00b310f 100644 --- a/site/src/pages/GroupsPage/CreateGroupPageView.tsx +++ b/site/src/pages/GroupsPage/CreateGroupPageView.tsx @@ -29,6 +29,7 @@ export const CreateGroupPageView: FC = ({ const form = useFormik({ initialValues: { name: "", + display_name: "", avatar_url: "", quota_allowance: 0, }, diff --git a/site/src/pages/GroupsPage/SettingsGroupPageView.tsx b/site/src/pages/GroupsPage/SettingsGroupPageView.tsx index 1721cc50abdbc..1735d309c9d0c 100644 --- a/site/src/pages/GroupsPage/SettingsGroupPageView.tsx +++ b/site/src/pages/GroupsPage/SettingsGroupPageView.tsx @@ -37,7 +37,7 @@ const UpdateGroupForm: FC<{ name: group.name, // If these are equal, keep the display name blank. A blank display name means // default to using the name. - display_name: group.display_name === group.name ? "" : group.display_name, + display_name: group.display_name, avatar_url: group.avatar_url, quota_allowance: group.quota_allowance, }, @@ -59,19 +59,17 @@ const UpdateGroupForm: FC<{ fullWidth label="Name" /> - {/* We might want to always show this at some point, but for now only show if - the display name differs from the original name. */} - {group.name !== group.display_name && ( - - )} - + form.setFieldValue("avatar_url", value)} /> - Date: Mon, 31 Jul 2023 13:25:51 -0500 Subject: [PATCH 08/19] bump migration --- ...p_display_name.down.sql => 000144_group_display_name.down.sql} | 0 ...group_display_name.up.sql => 000144_group_display_name.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000142_group_display_name.down.sql => 000144_group_display_name.down.sql} (100%) rename coderd/database/migrations/{000142_group_display_name.up.sql => 000144_group_display_name.up.sql} (100%) diff --git a/coderd/database/migrations/000142_group_display_name.down.sql b/coderd/database/migrations/000144_group_display_name.down.sql similarity index 100% rename from coderd/database/migrations/000142_group_display_name.down.sql rename to coderd/database/migrations/000144_group_display_name.down.sql diff --git a/coderd/database/migrations/000142_group_display_name.up.sql b/coderd/database/migrations/000144_group_display_name.up.sql similarity index 100% rename from coderd/database/migrations/000142_group_display_name.up.sql rename to coderd/database/migrations/000144_group_display_name.up.sql From dcfef49a984e5d6a87295b5242ad9c5129791076 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 31 Jul 2023 13:26:39 -0500 Subject: [PATCH 09/19] Update golden files --- enterprise/cli/testdata/coder_groups_list_--help.golden | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/enterprise/cli/testdata/coder_groups_list_--help.golden b/enterprise/cli/testdata/coder_groups_list_--help.golden index 99c20f5be3c9a..fafa537205fad 100644 --- a/enterprise/cli/testdata/coder_groups_list_--help.golden +++ b/enterprise/cli/testdata/coder_groups_list_--help.golden @@ -3,9 +3,9 @@ Usage: coder groups list [flags] List user groups Options - -c, --column string-array (default: name,organization id,members,avatar url) - Columns to display in table output. Available columns: name, - organization id, members, avatar url. + -c, --column string-array (default: name,display name,organization id,members,avatar url) + Columns to display in table output. Available columns: name, display + name, organization id, members, avatar url. -o, --output string (default: table) Output format. Available formats: table, json. From a1640301b9a33da1c57b918414e7c617b922993a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 09:43:55 -0500 Subject: [PATCH 10/19] Make fmt --- site/src/xServices/groups/editGroupXService.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/site/src/xServices/groups/editGroupXService.ts b/site/src/xServices/groups/editGroupXService.ts index 6aceaa84e36cb..ef919b7db72d3 100644 --- a/site/src/xServices/groups/editGroupXService.ts +++ b/site/src/xServices/groups/editGroupXService.ts @@ -23,7 +23,12 @@ export const editGroupMachine = createMachine( }, events: {} as { type: "UPDATE" - data: { display_name: string, name: string; avatar_url: string; quota_allowance: number } + data: { + display_name: string + name: string + avatar_url: string + quota_allowance: number + } }, }, tsTypes: {} as import("./editGroupXService.typegen").Typegen0, From 974ba833a10033e1fe4db0cf1a63577daff6fdbd Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 11:04:24 -0500 Subject: [PATCH 11/19] Update FE to use name --- enterprise/coderd/groups.go | 8 ++------ .../UserOrGroupAutocomplete.tsx | 8 ++++++-- site/src/pages/GroupsPage/CreateGroupPageView.tsx | 11 +++++++++++ site/src/pages/GroupsPage/GroupPage.tsx | 12 +++++++++--- site/src/pages/GroupsPage/GroupsPageView.tsx | 4 ++-- site/src/pages/GroupsPage/SettingsGroupPageView.tsx | 2 -- .../TemplatePermissionsPageView.tsx | 4 ++-- site/src/utils/groups.ts | 2 +- 8 files changed, 33 insertions(+), 18 deletions(-) diff --git a/enterprise/coderd/groups.go b/enterprise/coderd/groups.go index 932fd0054a12e..b6f126e1f62e0 100644 --- a/enterprise/coderd/groups.go +++ b/enterprise/coderd/groups.go @@ -400,15 +400,11 @@ func convertGroup(g database.Group, users []database.User) codersdk.Group { for _, user := range users { orgs[user.ID] = []uuid.UUID{g.OrganizationID} } - // Always default to the group name if the display name is empty - displayName := g.DisplayName - if displayName == "" { - displayName = g.Name - } + return codersdk.Group{ ID: g.ID, Name: g.Name, - DisplayName: displayName, + DisplayName: g.DisplayName, OrganizationID: g.OrganizationID, AvatarURL: g.AvatarURL, QuotaAllowance: int(g.QuotaAllowance), diff --git a/site/src/components/UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx b/site/src/components/UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx index 43e5c432848fc..127d970c9ee56 100644 --- a/site/src/components/UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx +++ b/site/src/components/UserOrGroupAutocomplete/UserOrGroupAutocomplete.tsx @@ -71,7 +71,7 @@ export const UserOrGroupAutocomplete: React.FC< }} isOptionEqualToValue={(option, value) => option.id === value.id} getOptionLabel={(option) => - isGroup(option) ? option.display_name : option.email + isGroup(option) ? option.display_name || option.name : option.email } renderOption={(props, option) => { const isOptionGroup = isGroup(option) @@ -79,7 +79,11 @@ export const UserOrGroupAutocomplete: React.FC< return ( diff --git a/site/src/pages/GroupsPage/CreateGroupPageView.tsx b/site/src/pages/GroupsPage/CreateGroupPageView.tsx index 7971a6721c141..a2936a5924ae3 100644 --- a/site/src/pages/GroupsPage/CreateGroupPageView.tsx +++ b/site/src/pages/GroupsPage/CreateGroupPageView.tsx @@ -52,6 +52,17 @@ export const CreateGroupPageView: FC = ({ fullWidth label="Name" /> + { return ( <> - {pageTitle(group?.display_name ?? "Loading...")} + + {pageTitle((group?.display_name || group?.name) ?? "Loading...")} + @@ -128,10 +130,14 @@ export const GroupPage: React.FC = () => { } > - {group?.display_name} + + {group?.display_name || group?.name} + {/* Show the name if it differs from the display name. */} - {group?.display_name !== group?.name ? group?.name : ""}{" "} + {group?.display_name && group?.display_name !== group?.name + ? group?.name + : ""}{" "} diff --git a/site/src/pages/GroupsPage/GroupsPageView.tsx b/site/src/pages/GroupsPage/GroupsPageView.tsx index 39b3db374aebf..1acb5e79cf769 100644 --- a/site/src/pages/GroupsPage/GroupsPageView.tsx +++ b/site/src/pages/GroupsPage/GroupsPageView.tsx @@ -137,11 +137,11 @@ export const GroupsPageView: FC = ({ } - title={group.display_name} + title={group.display_name || group.name} subtitle={`${group.members.length} members`} /> diff --git a/site/src/pages/GroupsPage/SettingsGroupPageView.tsx b/site/src/pages/GroupsPage/SettingsGroupPageView.tsx index 1735d309c9d0c..fd5c3d4e3fd0c 100644 --- a/site/src/pages/GroupsPage/SettingsGroupPageView.tsx +++ b/site/src/pages/GroupsPage/SettingsGroupPageView.tsx @@ -35,8 +35,6 @@ const UpdateGroupForm: FC<{ const form = useFormik({ initialValues: { name: group.name, - // If these are equal, keep the display name blank. A blank display name means - // default to using the name. display_name: group.display_name, avatar_url: group.avatar_url, quota_allowance: group.quota_allowance, diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx index 62472707513eb..a434fcc9302be 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx @@ -253,11 +253,11 @@ export const TemplatePermissionsPageView: FC< } - title={group.display_name} + title={group.display_name || group.name} subtitle={getGroupSubtitle(group)} /> diff --git a/site/src/utils/groups.ts b/site/src/utils/groups.ts index 28b7a07d5c90a..9afc048ce7cef 100644 --- a/site/src/utils/groups.ts +++ b/site/src/utils/groups.ts @@ -3,7 +3,7 @@ import { Group } from "api/typesGenerated" export const everyOneGroup = (organizationId: string): Group => ({ id: organizationId, name: "Everyone", - display_name: "Everyone", + display_name: "", organization_id: organizationId, members: [], avatar_url: "", From 6481757e2c8ef5ff160ec973be10a0e4cd949a94 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 11:15:23 -0500 Subject: [PATCH 12/19] Add extra comment --- coderd/database/migrations/000144_group_display_name.up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/migrations/000144_group_display_name.up.sql b/coderd/database/migrations/000144_group_display_name.up.sql index e7b6d69bfc0b2..a812ad8aa34c3 100644 --- a/coderd/database/migrations/000144_group_display_name.up.sql +++ b/coderd/database/migrations/000144_group_display_name.up.sql @@ -3,6 +3,6 @@ BEGIN; ALTER TABLE groups ADD COLUMN display_name TEXT NOT NULL DEFAULT ''; -COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friendly group name that user can set. This is not required to be unique.'; +COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friendly group name that user can set. This is not required to be unique and can be the empty string.'; COMMIT; From d0c254cd35293b5b0ef5c2f77d37f0972141a250 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 11:24:07 -0500 Subject: [PATCH 13/19] Add display name to cli --- enterprise/cli/groupcreate.go | 17 ++++++++++++++--- enterprise/cli/groupedit.go | 19 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/enterprise/cli/groupcreate.go b/enterprise/cli/groupcreate.go index c7c63d469ad0c..d80c1d441e69d 100644 --- a/enterprise/cli/groupcreate.go +++ b/enterprise/cli/groupcreate.go @@ -12,7 +12,11 @@ import ( ) func (r *RootCmd) groupCreate() *clibase.Cmd { - var avatarURL string + var ( + avatarURL string + displayName string + ) + client := new(codersdk.Client) cmd := &clibase.Cmd{ Use: "create ", @@ -30,8 +34,9 @@ func (r *RootCmd) groupCreate() *clibase.Cmd { } group, err := client.CreateGroup(ctx, org.ID, codersdk.CreateGroupRequest{ - Name: inv.Args[0], - AvatarURL: avatarURL, + Name: inv.Args[0], + DisplayName: displayName, + AvatarURL: avatarURL, }) if err != nil { return xerrors.Errorf("create group: %w", err) @@ -50,6 +55,12 @@ func (r *RootCmd) groupCreate() *clibase.Cmd { Env: "CODER_AVATAR_URL", Value: clibase.StringOf(&avatarURL), }, + { + Flag: "display-name", + Description: `Optional human friendly name for the group.`, + Env: "CODER_DISPLAY_NAME", + Value: clibase.StringOf(&displayName), + }, } return cmd diff --git a/enterprise/cli/groupedit.go b/enterprise/cli/groupedit.go index 0b8abae1dad0e..9ef6a3100658d 100644 --- a/enterprise/cli/groupedit.go +++ b/enterprise/cli/groupedit.go @@ -15,10 +15,11 @@ import ( func (r *RootCmd) groupEdit() *clibase.Cmd { var ( - avatarURL string - name string - addUsers []string - rmUsers []string + avatarURL string + name string + displayName string + addUsers []string + rmUsers []string ) client := new(codersdk.Client) cmd := &clibase.Cmd{ @@ -52,6 +53,10 @@ func (r *RootCmd) groupEdit() *clibase.Cmd { req.AvatarURL = &avatarURL } + if inv.ParsedFlags().Lookup("display-name").Changed { + req.DisplayName = &displayName + } + userRes, err := client.Users(ctx, codersdk.UsersRequest{}) if err != nil { return xerrors.Errorf("get users: %w", err) @@ -90,6 +95,12 @@ func (r *RootCmd) groupEdit() *clibase.Cmd { Description: "Update the group avatar.", Value: clibase.StringOf(&avatarURL), }, + { + Flag: "display-name", + Description: `Optional human friendly name for the group.`, + Env: "CODER_DISPLAY_NAME", + Value: clibase.StringOf(&displayName), + }, { Flag: "add-users", FlagShorthand: "a", From 1f1f69a92584f1c3e17e3abaa4f84e3bb30d531f Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 11:52:58 -0500 Subject: [PATCH 14/19] make gen update-golden-files --- docs/cli/groups_create.md | 9 +++++++++ docs/cli/groups_edit.md | 9 +++++++++ .../cli/testdata/coder_groups_create_--help.golden | 3 +++ enterprise/cli/testdata/coder_groups_edit_--help.golden | 3 +++ 4 files changed, 24 insertions(+) diff --git a/docs/cli/groups_create.md b/docs/cli/groups_create.md index 5158cf20dd094..dd51ed7233a9a 100644 --- a/docs/cli/groups_create.md +++ b/docs/cli/groups_create.md @@ -20,3 +20,12 @@ coder groups create [flags] | Environment | $CODER_AVATAR_URL | Set an avatar for a group. + +### --display-name + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_DISPLAY_NAME | + +Optional human friendly name for the group. diff --git a/docs/cli/groups_edit.md b/docs/cli/groups_edit.md index 8de7b567bf949..da8788806367a 100644 --- a/docs/cli/groups_edit.md +++ b/docs/cli/groups_edit.md @@ -28,6 +28,15 @@ Add users to the group. Accepts emails or IDs. Update the group avatar. +### --display-name + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_DISPLAY_NAME | + +Optional human friendly name for the group. + ### -n, --name | | | diff --git a/enterprise/cli/testdata/coder_groups_create_--help.golden b/enterprise/cli/testdata/coder_groups_create_--help.golden index 1cc883ab83aa0..d661a25b8740e 100644 --- a/enterprise/cli/testdata/coder_groups_create_--help.golden +++ b/enterprise/cli/testdata/coder_groups_create_--help.golden @@ -6,5 +6,8 @@ Create a user group -u, --avatar-url string, $CODER_AVATAR_URL Set an avatar for a group. + --display-name string, $CODER_DISPLAY_NAME + Optional human friendly name for the group. + --- Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_groups_edit_--help.golden b/enterprise/cli/testdata/coder_groups_edit_--help.golden index a265ef521508b..b5743bc4fc260 100644 --- a/enterprise/cli/testdata/coder_groups_edit_--help.golden +++ b/enterprise/cli/testdata/coder_groups_edit_--help.golden @@ -9,6 +9,9 @@ Edit a user group -u, --avatar-url string Update the group avatar. + --display-name string, $CODER_DISPLAY_NAME + Optional human friendly name for the group. + -n, --name string Update the group name. From 26a1122d7ee3ad466f572ce8fa184fbf83e2c2ee Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 13:06:17 -0500 Subject: [PATCH 15/19] Make gen --- coderd/database/dump.sql | 2 +- coderd/database/models.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index fef942c365059..aea6fcda116de 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -299,7 +299,7 @@ CREATE TABLE groups ( display_name text DEFAULT ''::text NOT NULL ); -COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friendly group name that user can set. This is not required to be unique.'; +COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friendly group name that user can set. This is not required to be unique and can be the empty string.'; CREATE TABLE licenses ( id integer NOT NULL, diff --git a/coderd/database/models.go b/coderd/database/models.go index ac95404146218..6af46e2d57126 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1492,7 +1492,7 @@ type Group struct { OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` AvatarURL string `db:"avatar_url" json:"avatar_url"` QuotaAllowance int32 `db:"quota_allowance" json:"quota_allowance"` - // Display name is a custom, human-friendly group name that user can set. This is not required to be unique. + // Display name is a custom, human-friendly group name that user can set. This is not required to be unique and can be the empty string. DisplayName string `db:"display_name" json:"display_name"` } From 9c805705f3a56121c55772475e8d73357d768772 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 14:15:17 -0500 Subject: [PATCH 16/19] Fix sql spacing --- coderd/database/queries/groups.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/queries/groups.sql b/coderd/database/queries/groups.sql index 9b258414887d7..e1ee6635a5fe0 100644 --- a/coderd/database/queries/groups.sql +++ b/coderd/database/queries/groups.sql @@ -34,7 +34,7 @@ AND INSERT INTO groups ( id, name, - display_name, + display_name, organization_id, avatar_url, quota_allowance From d350d0cb273b64b9f5c531588127e4c481d76ee0 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 14:18:52 -0500 Subject: [PATCH 17/19] Begin to add some display name tests --- enterprise/coderd/groups_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/enterprise/coderd/groups_test.go b/enterprise/coderd/groups_test.go index 322ee17c36cd4..9165b8a534715 100644 --- a/enterprise/coderd/groups_test.go +++ b/enterprise/coderd/groups_test.go @@ -37,6 +37,7 @@ func TestCreateGroup(t *testing.T) { require.Equal(t, "hi", group.Name) require.Equal(t, "https://example.com", group.AvatarURL) require.Empty(t, group.Members) + require.Empty(t, group.DisplayName) require.NotEqual(t, uuid.Nil.String(), group.ID.String()) }) @@ -124,11 +125,13 @@ func TestPatchGroup(t *testing.T) { codersdk.FeatureTemplateRBAC: 1, }, }}) + const displayName = "foobar" ctx := testutil.Context(t, testutil.WaitLong) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ Name: "hi", AvatarURL: "https://example.com", QuotaAllowance: 10, + DisplayName: "", }) require.NoError(t, err) require.Equal(t, 10, group.QuotaAllowance) @@ -137,8 +140,10 @@ func TestPatchGroup(t *testing.T) { Name: "bye", AvatarURL: ptr.Ref("https://google.com"), QuotaAllowance: ptr.Ref(20), + DisplayName: ptr.Ref(displayName), }) require.NoError(t, err) + require.Equal(t, displayName, group.DisplayName) require.Equal(t, "bye", group.Name) require.Equal(t, "https://google.com", group.AvatarURL) require.Equal(t, 20, group.QuotaAllowance) From 7df0ead108b53befef89954437978a2da73efb4e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 1 Aug 2023 15:31:22 -0500 Subject: [PATCH 18/19] Add unit test for display name changing --- enterprise/coderd/groups_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/enterprise/coderd/groups_test.go b/enterprise/coderd/groups_test.go index 9165b8a534715..5999fa47b1f6d 100644 --- a/enterprise/coderd/groups_test.go +++ b/enterprise/coderd/groups_test.go @@ -149,6 +149,37 @@ func TestPatchGroup(t *testing.T) { require.Equal(t, 20, group.QuotaAllowance) }) + t.Run("DisplayNameUnchanged", func(t *testing.T) { + t.Parallel() + + client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, + }}) + const displayName = "foobar" + ctx := testutil.Context(t, testutil.WaitLong) + group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ + Name: "hi", + AvatarURL: "https://example.com", + QuotaAllowance: 10, + DisplayName: displayName, + }) + require.NoError(t, err) + require.Equal(t, 10, group.QuotaAllowance) + + group, err = client.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{ + Name: "bye", + AvatarURL: ptr.Ref("https://google.com"), + QuotaAllowance: ptr.Ref(20), + }) + require.NoError(t, err) + require.Equal(t, displayName, group.DisplayName) + require.Equal(t, "bye", group.Name) + require.Equal(t, "https://google.com", group.AvatarURL) + require.Equal(t, 20, group.QuotaAllowance) + }) + // The FE sends a request from the edit page where the old name == new name. // This should pass since it's not really an error to update a group name // to itself. From e6de76a5270b39b537ecea0bf7e519e08c6ba6fb Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 2 Aug 2023 09:14:16 -0500 Subject: [PATCH 19/19] Fix spacing --- coderd/database/queries.sql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 99e96e8394c92..d875697aa68ce 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1307,7 +1307,7 @@ const insertGroup = `-- name: InsertGroup :one INSERT INTO groups ( id, name, - display_name, + display_name, organization_id, avatar_url, quota_allowance