Skip to content

Commit f92336c

Browse files
authored
feat(coderd): allow workspace owners to mark workspaces as favorite (#11791)
- Adds column `favorite` to workspaces table - Adds API endpoints to favorite/unfavorite workspaces - Modifies sorting order to return owners' favorite workspaces first
1 parent 6145da8 commit f92336c

27 files changed

+646
-50
lines changed

cli/testdata/coder_list_--output_json.golden

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"failing_agents": []
6262
},
6363
"automatic_updates": "never",
64-
"allow_renames": false
64+
"allow_renames": false,
65+
"favorite": false
6566
}
6667
]

coderd/apidoc/docs.go

+59
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+55
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

+2
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,8 @@ func New(options *Options) *API {
950950
r.Get("/watch", api.watchWorkspace)
951951
r.Put("/extend", api.putExtendWorkspace)
952952
r.Put("/dormant", api.putWorkspaceDormant)
953+
r.Put("/favorite", api.putFavoriteWorkspace)
954+
r.Delete("/favorite", api.deleteFavoriteWorkspace)
953955
r.Put("/autoupdates", api.putWorkspaceAutoupdates)
954956
r.Get("/resolve-autostart", api.resolveAutostart)
955957
})

coderd/database/dbauthz/dbauthz.go

+14
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,13 @@ func (q *querier) DeleteTailnetTunnel(ctx context.Context, arg database.DeleteTa
891891
return q.db.DeleteTailnetTunnel(ctx, arg)
892892
}
893893

894+
func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error {
895+
fetch := func(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
896+
return q.db.GetWorkspaceByID(ctx, id)
897+
}
898+
return update(q.log, q.auth, fetch, q.db.FavoriteWorkspace)(ctx, id)
899+
}
900+
894901
func (q *querier) GetAPIKeyByID(ctx context.Context, id string) (database.APIKey, error) {
895902
return fetch(q.log, q.auth, q.db.GetAPIKeyByID)(ctx, id)
896903
}
@@ -2509,6 +2516,13 @@ func (q *querier) UnarchiveTemplateVersion(ctx context.Context, arg database.Una
25092516
return q.db.UnarchiveTemplateVersion(ctx, arg)
25102517
}
25112518

2519+
func (q *querier) UnfavoriteWorkspace(ctx context.Context, id uuid.UUID) error {
2520+
fetch := func(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
2521+
return q.db.GetWorkspaceByID(ctx, id)
2522+
}
2523+
return update(q.log, q.auth, fetch, q.db.UnfavoriteWorkspace)(ctx, id)
2524+
}
2525+
25122526
func (q *querier) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateAPIKeyByIDParams) error {
25132527
fetch := func(ctx context.Context, arg database.UpdateAPIKeyByIDParams) (database.APIKey, error) {
25142528
return q.db.GetAPIKeyByID(ctx, arg.ID)

coderd/database/dbauthz/dbauthz_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,16 @@ func (s *MethodTestSuite) TestWorkspace() {
15781578
WorkspaceID: ws.ID,
15791579
}).Asserts(ws, rbac.ActionUpdate).Returns()
15801580
}))
1581+
s.Run("FavoriteWorkspace", s.Subtest(func(db database.Store, check *expects) {
1582+
u := dbgen.User(s.T(), db, database.User{})
1583+
ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID})
1584+
check.Args(ws.ID).Asserts(ws, rbac.ActionUpdate).Returns()
1585+
}))
1586+
s.Run("UnfavoriteWorkspace", s.Subtest(func(db database.Store, check *expects) {
1587+
u := dbgen.User(s.T(), db, database.User{})
1588+
ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID})
1589+
check.Args(ws.ID).Asserts(ws, rbac.ActionUpdate).Returns()
1590+
}))
15811591
}
15821592

15831593
func (s *MethodTestSuite) TestExtraMethods() {

coderd/database/dbmem/dbmem.go

+52-4
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspac
359359
DeletingAt: w.DeletingAt,
360360
Count: count,
361361
AutomaticUpdates: w.AutomaticUpdates,
362+
Favorite: w.Favorite,
362363
}
363364

364365
for _, t := range q.templates {
@@ -1315,6 +1316,25 @@ func (*FakeQuerier) DeleteTailnetTunnel(_ context.Context, arg database.DeleteTa
13151316
return database.DeleteTailnetTunnelRow{}, ErrUnimplemented
13161317
}
13171318

1319+
func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error {
1320+
err := validateDatabaseType(arg)
1321+
if err != nil {
1322+
return err
1323+
}
1324+
1325+
q.mutex.Lock()
1326+
defer q.mutex.Unlock()
1327+
1328+
for i := 0; i < len(q.workspaces); i++ {
1329+
if q.workspaces[i].ID != arg {
1330+
continue
1331+
}
1332+
q.workspaces[i].Favorite = true
1333+
return nil
1334+
}
1335+
return nil
1336+
}
1337+
13181338
func (q *FakeQuerier) GetAPIKeyByID(_ context.Context, id string) (database.APIKey, error) {
13191339
q.mutex.RLock()
13201340
defer q.mutex.RUnlock()
@@ -5984,6 +6004,26 @@ func (q *FakeQuerier) UnarchiveTemplateVersion(_ context.Context, arg database.U
59846004
return sql.ErrNoRows
59856005
}
59866006

6007+
func (q *FakeQuerier) UnfavoriteWorkspace(_ context.Context, arg uuid.UUID) error {
6008+
err := validateDatabaseType(arg)
6009+
if err != nil {
6010+
return err
6011+
}
6012+
6013+
q.mutex.Lock()
6014+
defer q.mutex.Unlock()
6015+
6016+
for i := 0; i < len(q.workspaces); i++ {
6017+
if q.workspaces[i].ID != arg {
6018+
continue
6019+
}
6020+
q.workspaces[i].Favorite = false
6021+
return nil
6022+
}
6023+
6024+
return nil
6025+
}
6026+
59876027
func (q *FakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPIKeyByIDParams) error {
59886028
if err := validateDatabaseType(arg); err != nil {
59896029
return err
@@ -7713,7 +7753,15 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
77137753
w1 := workspaces[i]
77147754
w2 := workspaces[j]
77157755

7716-
// Order by: running first
7756+
// Order by: favorite first
7757+
if arg.RequesterID == w1.OwnerID && w1.Favorite {
7758+
return true
7759+
}
7760+
if arg.RequesterID == w2.OwnerID && w2.Favorite {
7761+
return false
7762+
}
7763+
7764+
// Order by: running
77177765
w1IsRunning := isRunning(preloadedWorkspaceBuilds[w1.ID], preloadedProvisionerJobs[w1.ID])
77187766
w2IsRunning := isRunning(preloadedWorkspaceBuilds[w2.ID], preloadedProvisionerJobs[w2.ID])
77197767

@@ -7726,12 +7774,12 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
77267774
}
77277775

77287776
// Order by: usernames
7729-
if w1.ID != w2.ID {
7730-
return sort.StringsAreSorted([]string{preloadedUsers[w1.ID].Username, preloadedUsers[w2.ID].Username})
7777+
if strings.Compare(preloadedUsers[w1.ID].Username, preloadedUsers[w2.ID].Username) < 0 {
7778+
return true
77317779
}
77327780

77337781
// Order by: workspace names
7734-
return sort.StringsAreSorted([]string{w1.Name, w2.Name})
7782+
return strings.Compare(w1.Name, w2.Name) < 0
77357783
})
77367784

77377785
beforePageCount := len(workspaces)

coderd/database/dbmetrics/dbmetrics.go

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbmock/dbmock.go

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dump.sql

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE ONLY workspaces DROP COLUMN favorite;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE ONLY workspaces
2+
ADD COLUMN favorite boolean NOT NULL DEFAULT false;
3+
COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.';

coderd/database/modelmethods.go

+1
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
373373
DormantAt: r.DormantAt,
374374
DeletingAt: r.DeletingAt,
375375
AutomaticUpdates: r.AutomaticUpdates,
376+
Favorite: r.Favorite,
376377
}
377378
}
378379

0 commit comments

Comments
 (0)