feat: allow editing org icon (#13547)

This commit is contained in:
Kayla Washburn-Love
2024-06-12 12:28:13 -06:00
committed by GitHub
parent 58bf0ec1c6
commit 28228f1bcb
19 changed files with 116 additions and 22 deletions
+3 -2
View File
@@ -2,12 +2,13 @@ package clock
import (
"context"
"errors"
"fmt"
"slices"
"sync"
"testing"
"time"
"golang.org/x/xerrors"
)
// Mock is the testing implementation of Clock. It tracks a time that monotonically increases
@@ -571,7 +572,7 @@ func (t *Trap) Close() {
close(t.done)
}
var ErrTrapClosed = errors.New("trap closed")
var ErrTrapClosed = xerrors.New("trap closed")
func (t *Trap) Wait(ctx context.Context) (*Call, error) {
select {
+9
View File
@@ -8376,6 +8376,9 @@ const docTemplate = `{
"description": "DisplayName will default to the same value as ` + "`" + `Name` + "`" + ` if not provided.",
"type": "string"
},
"icon": {
"type": "string"
},
"name": {
"type": "string"
}
@@ -10007,6 +10010,9 @@ const docTemplate = `{
"display_name": {
"type": "string"
},
"icon": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
@@ -11724,6 +11730,9 @@ const docTemplate = `{
"display_name": {
"type": "string"
},
"icon": {
"type": "string"
},
"name": {
"type": "string"
}
+9
View File
@@ -7456,6 +7456,9 @@
"description": "DisplayName will default to the same value as `Name` if not provided.",
"type": "string"
},
"icon": {
"type": "string"
},
"name": {
"type": "string"
}
@@ -8996,6 +8999,9 @@
"display_name": {
"type": "string"
},
"icon": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
@@ -10628,6 +10634,9 @@
"display_name": {
"type": "string"
},
"icon": {
"type": "string"
},
"name": {
"type": "string"
}
+1
View File
@@ -338,6 +338,7 @@ func Organization(t testing.TB, db database.Store, orig database.Organization) d
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)),
DisplayName: takeFirst(orig.Name, namesgenerator.GetRandomName(1)),
Description: takeFirst(orig.Description, namesgenerator.GetRandomName(1)),
Icon: takeFirst(orig.Icon, ""),
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
})
+3
View File
@@ -88,6 +88,7 @@ func New() database.Store {
Name: "first-organization",
DisplayName: "first-organization",
Description: "Builtin default organization.",
Icon: "",
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
})
@@ -6189,6 +6190,7 @@ func (q *FakeQuerier) InsertOrganization(_ context.Context, arg database.InsertO
Name: arg.Name,
DisplayName: arg.DisplayName,
Description: arg.Description,
Icon: arg.Icon,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
IsDefault: len(q.organizations) == 0,
@@ -7334,6 +7336,7 @@ func (q *FakeQuerier) UpdateOrganization(_ context.Context, arg database.UpdateO
org.Name = arg.Name
org.DisplayName = arg.DisplayName
org.Description = arg.Description
org.Icon = arg.Icon
q.organizations[i] = org
return org, nil
}
+2 -1
View File
@@ -595,7 +595,8 @@ CREATE TABLE organizations (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
is_default boolean DEFAULT false NOT NULL,
display_name text NOT NULL
display_name text NOT NULL,
icon text DEFAULT ''::text NOT NULL
);
CREATE TABLE parameter_schemas (
@@ -0,0 +1,2 @@
alter table organizations
drop column icon;
@@ -0,0 +1,2 @@
alter table organizations
add column icon text not null default '';
+1
View File
@@ -1933,6 +1933,7 @@ type Organization struct {
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
IsDefault bool `db:"is_default" json:"is_default"`
DisplayName string `db:"display_name" json:"display_name"`
Icon string `db:"icon" json:"icon"`
}
type OrganizationMember struct {
+22 -10
View File
@@ -3949,7 +3949,7 @@ func (q *sqlQuerier) DeleteOrganization(ctx context.Context, id uuid.UUID) error
const getDefaultOrganization = `-- name: GetDefaultOrganization :one
SELECT
id, name, description, created_at, updated_at, is_default, display_name
id, name, description, created_at, updated_at, is_default, display_name, icon
FROM
organizations
WHERE
@@ -3969,13 +3969,14 @@ func (q *sqlQuerier) GetDefaultOrganization(ctx context.Context) (Organization,
&i.UpdatedAt,
&i.IsDefault,
&i.DisplayName,
&i.Icon,
)
return i, err
}
const getOrganizationByID = `-- name: GetOrganizationByID :one
SELECT
id, name, description, created_at, updated_at, is_default, display_name
id, name, description, created_at, updated_at, is_default, display_name, icon
FROM
organizations
WHERE
@@ -3993,13 +3994,14 @@ func (q *sqlQuerier) GetOrganizationByID(ctx context.Context, id uuid.UUID) (Org
&i.UpdatedAt,
&i.IsDefault,
&i.DisplayName,
&i.Icon,
)
return i, err
}
const getOrganizationByName = `-- name: GetOrganizationByName :one
SELECT
id, name, description, created_at, updated_at, is_default, display_name
id, name, description, created_at, updated_at, is_default, display_name, icon
FROM
organizations
WHERE
@@ -4019,13 +4021,14 @@ func (q *sqlQuerier) GetOrganizationByName(ctx context.Context, name string) (Or
&i.UpdatedAt,
&i.IsDefault,
&i.DisplayName,
&i.Icon,
)
return i, err
}
const getOrganizations = `-- name: GetOrganizations :many
SELECT
id, name, description, created_at, updated_at, is_default, display_name
id, name, description, created_at, updated_at, is_default, display_name, icon
FROM
organizations
`
@@ -4047,6 +4050,7 @@ func (q *sqlQuerier) GetOrganizations(ctx context.Context) ([]Organization, erro
&i.UpdatedAt,
&i.IsDefault,
&i.DisplayName,
&i.Icon,
); err != nil {
return nil, err
}
@@ -4063,7 +4067,7 @@ func (q *sqlQuerier) GetOrganizations(ctx context.Context) ([]Organization, erro
const getOrganizationsByUserID = `-- name: GetOrganizationsByUserID :many
SELECT
id, name, description, created_at, updated_at, is_default, display_name
id, name, description, created_at, updated_at, is_default, display_name, icon
FROM
organizations
WHERE
@@ -4094,6 +4098,7 @@ func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, userID uuid.U
&i.UpdatedAt,
&i.IsDefault,
&i.DisplayName,
&i.Icon,
); err != nil {
return nil, err
}
@@ -4110,10 +4115,10 @@ func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, userID uuid.U
const insertOrganization = `-- name: InsertOrganization :one
INSERT INTO
organizations (id, "name", display_name, description, created_at, updated_at, is_default)
organizations (id, "name", display_name, description, icon, created_at, updated_at, is_default)
VALUES
-- If no organizations exist, and this is the first, make it the default.
($1, $2, $3, $4, $5, $6, (SELECT TRUE FROM organizations LIMIT 1) IS NULL) RETURNING id, name, description, created_at, updated_at, is_default, display_name
($1, $2, $3, $4, $5, $6, $7, (SELECT TRUE FROM organizations LIMIT 1) IS NULL) RETURNING id, name, description, created_at, updated_at, is_default, display_name, icon
`
type InsertOrganizationParams struct {
@@ -4121,6 +4126,7 @@ type InsertOrganizationParams struct {
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Description string `db:"description" json:"description"`
Icon string `db:"icon" json:"icon"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
@@ -4131,6 +4137,7 @@ func (q *sqlQuerier) InsertOrganization(ctx context.Context, arg InsertOrganizat
arg.Name,
arg.DisplayName,
arg.Description,
arg.Icon,
arg.CreatedAt,
arg.UpdatedAt,
)
@@ -4143,6 +4150,7 @@ func (q *sqlQuerier) InsertOrganization(ctx context.Context, arg InsertOrganizat
&i.UpdatedAt,
&i.IsDefault,
&i.DisplayName,
&i.Icon,
)
return i, err
}
@@ -4154,10 +4162,11 @@ SET
updated_at = $1,
name = $2,
display_name = $3,
description = $4
description = $4,
icon = $5
WHERE
id = $5
RETURNING id, name, description, created_at, updated_at, is_default, display_name
id = $6
RETURNING id, name, description, created_at, updated_at, is_default, display_name, icon
`
type UpdateOrganizationParams struct {
@@ -4165,6 +4174,7 @@ type UpdateOrganizationParams struct {
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Description string `db:"description" json:"description"`
Icon string `db:"icon" json:"icon"`
ID uuid.UUID `db:"id" json:"id"`
}
@@ -4174,6 +4184,7 @@ func (q *sqlQuerier) UpdateOrganization(ctx context.Context, arg UpdateOrganizat
arg.Name,
arg.DisplayName,
arg.Description,
arg.Icon,
arg.ID,
)
var i Organization
@@ -4185,6 +4196,7 @@ func (q *sqlQuerier) UpdateOrganization(ctx context.Context, arg UpdateOrganizat
&i.UpdatedAt,
&i.IsDefault,
&i.DisplayName,
&i.Icon,
)
return i, err
}
+4 -3
View File
@@ -49,10 +49,10 @@ WHERE
-- name: InsertOrganization :one
INSERT INTO
organizations (id, "name", display_name, description, created_at, updated_at, is_default)
organizations (id, "name", display_name, description, icon, created_at, updated_at, is_default)
VALUES
-- If no organizations exist, and this is the first, make it the default.
(@id, @name, @display_name, @description, @created_at, @updated_at, (SELECT TRUE FROM organizations LIMIT 1) IS NULL) RETURNING *;
(@id, @name, @display_name, @description, @icon, @created_at, @updated_at, (SELECT TRUE FROM organizations LIMIT 1) IS NULL) RETURNING *;
-- name: UpdateOrganization :one
UPDATE
@@ -61,7 +61,8 @@ SET
updated_at = @updated_at,
name = @name,
display_name = @display_name,
description = @description
description = @description,
icon = @icon
WHERE
id = @id
RETURNING *;
+8 -2
View File
@@ -83,6 +83,7 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) {
Name: req.Name,
DisplayName: req.DisplayName,
Description: req.Description,
Icon: req.Icon,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
})
@@ -164,6 +165,7 @@ func (api *API) patchOrganization(rw http.ResponseWriter, r *http.Request) {
Name: organization.Name,
DisplayName: organization.DisplayName,
Description: organization.Description,
Icon: organization.Icon,
}
if req.Name != "" {
@@ -172,8 +174,11 @@ func (api *API) patchOrganization(rw http.ResponseWriter, r *http.Request) {
if req.DisplayName != "" {
updateOrgParams.DisplayName = req.DisplayName
}
if req.Description != "" {
updateOrgParams.Description = req.Description
if req.Description != nil {
updateOrgParams.Description = *req.Description
}
if req.Icon != nil {
updateOrgParams.Icon = *req.Icon
}
organization, err = tx.UpdateOrganization(ctx, updateOrgParams)
@@ -248,6 +253,7 @@ func convertOrganization(organization database.Organization) codersdk.Organizati
Name: organization.Name,
DisplayName: organization.DisplayName,
Description: organization.Description,
Icon: organization.Icon,
CreatedAt: organization.CreatedAt,
UpdatedAt: organization.UpdatedAt,
IsDefault: organization.IsDefault,
+26 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)
@@ -142,11 +143,13 @@ func TestPostOrganizationsByUser(t *testing.T) {
Name: "new",
DisplayName: "New",
Description: "A new organization to love and cherish forever.",
Icon: "/emojis/1f48f-1f3ff.png",
})
require.NoError(t, err)
require.Equal(t, "new", o.Name)
require.Equal(t, "New", o.DisplayName)
require.Equal(t, "A new organization to love and cherish forever.", o.Description)
require.Equal(t, "/emojis/1f48f-1f3ff.png", o.Icon)
})
t.Run("CreateWithoutExplicitDisplayName", func(t *testing.T) {
@@ -300,7 +303,7 @@ func TestPatchOrganizationsByUser(t *testing.T) {
require.NoError(t, err)
o, err = client.UpdateOrganization(ctx, o.Name, codersdk.UpdateOrganizationRequest{
Description: "wow, this organization description is so updated!",
Description: ptr.Ref("wow, this organization description is so updated!"),
})
require.NoError(t, err)
@@ -308,6 +311,28 @@ func TestPatchOrganizationsByUser(t *testing.T) {
require.Equal(t, "New", o.DisplayName) // didn't change
require.Equal(t, "wow, this organization description is so updated!", o.Description)
})
t.Run("UpdateIcon", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)
ctx := testutil.Context(t, testutil.WaitMedium)
o, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: "new",
DisplayName: "New",
})
require.NoError(t, err)
o, err = client.UpdateOrganization(ctx, o.Name, codersdk.UpdateOrganizationRequest{
Icon: ptr.Ref("/emojis/1f48f-1f3ff.png"),
})
require.NoError(t, err)
require.Equal(t, "new", o.Name) // didn't change
require.Equal(t, "New", o.DisplayName) // didn't change
require.Equal(t, "/emojis/1f48f-1f3ff.png", o.Icon)
})
}
func TestDeleteOrganizationsByUser(t *testing.T) {
+6 -3
View File
@@ -47,6 +47,7 @@ type Organization struct {
CreatedAt time.Time `table:"created_at" json:"created_at" validate:"required" format:"date-time"`
UpdatedAt time.Time `table:"updated_at" json:"updated_at" validate:"required" format:"date-time"`
IsDefault bool `table:"default" json:"is_default" validate:"required"`
Icon string `table:"icon" json:"icon"`
}
type OrganizationMember struct {
@@ -62,12 +63,14 @@ type CreateOrganizationRequest struct {
// DisplayName will default to the same value as `Name` if not provided.
DisplayName string `json:"display_name" validate:"omitempty,organization_display_name"`
Description string `json:"description,omitempty"`
Icon string `json:"icon,omitempty"`
}
type UpdateOrganizationRequest struct {
Name string `json:"name,omitempty" validate:"omitempty,organization_name"`
DisplayName string `json:"display_name,omitempty" validate:"omitempty,organization_display_name"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty" validate:"omitempty,organization_name"`
DisplayName string `json:"display_name,omitempty" validate:"omitempty,organization_display_name"`
Description *string `json:"description,omitempty"`
Icon *string `json:"icon,omitempty"`
}
// CreateTemplateVersionRequest enables callers to create a new Template Version.
+5
View File
@@ -107,6 +107,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations \
{
"description": "string",
"display_name": "string",
"icon": "string",
"name": "string"
}
```
@@ -126,6 +127,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations \
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
@@ -169,6 +171,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization} \
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
@@ -248,6 +251,7 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization} \
{
"description": "string",
"display_name": "string",
"icon": "string",
"name": "string"
}
```
@@ -268,6 +272,7 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization} \
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
+6
View File
@@ -1029,6 +1029,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
{
"description": "string",
"display_name": "string",
"icon": "string",
"name": "string"
}
```
@@ -1039,6 +1040,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| -------------- | ------ | -------- | ------------ | ---------------------------------------------------------------------- |
| `description` | string | false | | |
| `display_name` | string | false | | Display name will default to the same value as `Name` if not provided. |
| `icon` | string | false | | |
| `name` | string | true | | |
## codersdk.CreateTemplateRequest
@@ -3214,6 +3216,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
@@ -3228,6 +3231,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
| `created_at` | string | true | | |
| `description` | string | false | | |
| `display_name` | string | true | | |
| `icon` | string | false | | |
| `id` | string | true | | |
| `is_default` | boolean | true | | |
| `name` | string | true | | |
@@ -5000,6 +5004,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
{
"description": "string",
"display_name": "string",
"icon": "string",
"name": "string"
}
```
@@ -5010,6 +5015,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
| -------------- | ------ | -------- | ------------ | ----------- |
| `description` | string | false | | |
| `display_name` | string | false | | |
| `icon` | string | false | | |
| `name` | string | false | | |
## codersdk.UpdateRoles
+3
View File
@@ -1000,6 +1000,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/organizations \
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
@@ -1024,6 +1025,7 @@ Status Code **200**
| `» created_at` | string(date-time) | true | | |
| `» description` | string | false | | |
| `» display_name` | string | true | | |
| `» icon` | string | false | | |
| `» id` | string(uuid) | true | | |
| `» is_default` | boolean | true | | |
| `» name` | string | true | | |
@@ -1060,6 +1062,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/organizations/{organiza
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
+3
View File
@@ -228,6 +228,7 @@ export interface CreateOrganizationRequest {
readonly name: string;
readonly display_name: string;
readonly description?: string;
readonly icon?: string;
}
// From codersdk/organizations.go
@@ -784,6 +785,7 @@ export interface Organization {
readonly created_at: string;
readonly updated_at: string;
readonly is_default: boolean;
readonly icon: string;
}
// From codersdk/organizations.go
@@ -1330,6 +1332,7 @@ export interface UpdateOrganizationRequest {
readonly name?: string;
readonly display_name?: string;
readonly description?: string;
readonly icon?: string;
}
// From codersdk/users.go
+1
View File
@@ -16,6 +16,7 @@ export const MockOrganization: TypesGen.Organization = {
name: "test-organization",
display_name: "Test Organization",
description: "",
icon: "",
created_at: "",
updated_at: "",
is_default: true,