Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(coderd): add ability to mark workspaces as favorite#11673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
johnstcn wants to merge27 commits intomainfromcj/pin-workspaces

Conversation

johnstcn
Copy link
Member

@johnstcnjohnstcn commentedJan 17, 2024
edited by matifali
Loading

Part of#7912

  • Update DB schema + queries (this PR)
  • Add API endpoints to favorite/unfavorite workspace (this PR)
  • Show favorite status in UI/CLI (follow-up)
  • Add CLI commands to favorite/unfavorite workspace (follow-up)
  • Add UI support to favorite/unfavorite workspace (follow-up)

Goal:

When you query workspaces in the UI,your favorite workspaces should show up first (that is, favorite workspaces of other users should be sorted as they would if they were not favorited at all).

Approach:

The 'obvious' approach is to add a boolean columnfavorite to the workspaces table. This will work for one user, but then all users' favorites go to the top when filtering by multiple users. I decided to avoid this approach unless absolutely required.

Instead, I first tried adding a separate tableuser_favorites in which we store(user_id, workspace_id) tuples and adding some joins to return the favorite status by user. This proved unwieldy forGetWorkspaceByID as that changes the return signature to aGetWorkspaceByIDRow and breaks everything.

I then changed tactics and instead added a nullable UUID columnfavorite_of. This stores the UUID of the workspaceowner when marked as 'favorite', and isNULL otherwise. We can then pass in the UUID of the caller in order to control the ordering of 'favorites'.

We convert theFavorite status to a boolean value when converting between the respectivedatabase andcodersdk structs.

I originally considered co-opting theowner parameter for this ordering but instead elected to add a separate parameter to avoid surprises (e.g. querying forowner:larry ascurly would then show Larry's favorite workspaces first).

This means that:

  • You must be able towrite a workspsace in order to mark it as favorite.
  • Marking someone else's workspace as favorite will still make it show up astheir favorite, and not yours.

@johnstcnjohnstcn self-assigned thisJan 17, 2024
@johnstcnjohnstcnforce-pushed thecj/pin-workspaces branch 4 times, most recently fromf154058 to321aca1CompareJanuary 22, 2024 08:53
@johnstcnjohnstcn changed the titlewip: feat(coderd): add ability to pin workspaceswip: feat(coderd): add ability to mark workspaces as favo(u?)riteJan 22, 2024
@johnstcnjohnstcnforce-pushed thecj/pin-workspaces branch 2 times, most recently from61da6b9 to4469c84CompareJanuary 22, 2024 22:33
@johnstcnjohnstcn changed the titlewip: feat(coderd): add ability to mark workspaces as favo(u?)ritewip: feat(coderd): add ability to mark workspaces as favoriteJan 22, 2024
@johnstcnjohnstcn changed the titlewip: feat(coderd): add ability to mark workspaces as favoritefeat(coderd): add ability to mark workspaces as favoriteJan 23, 2024
Comment on lines 265 to 267
CASE WHEN workspaces.favorite_of = @order_by_favorite THEN
workspaces.favorite_of = @order_by_favorite
END ASC,
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

self-review: this new parameterorder_by_favorite is passed in and contains the requestor's user ID. I tried multiple permutations without theCASE statement but all of them did not result in the desired sort order.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Interesting that you do not need anELSE here.

I think we can drop the body of the case, and just puttrue. Just to remove a bit of duplication.

Suggested change
CASE WHENworkspaces.favorite_of= @order_by_favorite THEN
workspaces.favorite_of= @order_by_favorite
ENDASC,
CASE WHENworkspaces.favorite_of= @order_by_favorite THEN
true
ENDASC,

Comment on lines +7761 to +7766
ifstrings.Compare(preloadedUsers[w1.ID].Username, preloadedUsers[w2.ID].Username) < 0 {
returntrue
}

// Order by: workspace names
returnsort.StringsAreSorted([]string{w1.Name, w2.Name})
returnstrings.Compare(w1.Name, w2.Name) < 0
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed a pre-existing sort ordering bug in dbmem.

@@ -479,55 +479,85 @@ func TestAdminViewAllWorkspaces(t *testing.T) {
func TestWorkspacesSortOrder(t *testing.T) {
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

self-review: this test has been significantly beefed up

Comment on lines +1581 to +1590
s.Run("FavoriteWorkspace", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID})
check.Args(ws.ID).Asserts(ws, rbac.ActionUpdate).Returns()
}))
s.Run("UnfavoriteWorkspace", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID})
check.Args(ws.ID).Asserts(ws, rbac.ActionUpdate).Returns()
}))
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

self-review: you need to be able to update a workspace in order to favorite it.

Comment on lines +1 to +3
ALTER TABLE ONLY workspaces
ADD COLUMN favorite_of uuid DEFAULT NULL;
COMMENT ON COLUMN workspaces.favorite_of IS 'FavoriteOf contains the UUID of the workspace owner if the workspace has been favorited.';
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

self-review: just storing a boolean value here doesn't provide enough context to the query to be able to know how to sort favorites.

Copy link
Member

@EmyrkEmyrkJan 23, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Do you think there is any benefit to using a zero uuid here instead of null? It just makes you not need to useuuid.NullUUID{...}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Doesn't a workspace have the owner uuid already in the table? If so can't we just use a boolean + that? When I hear "favorite of" I kind of expect the data type to be an array and favorable by many.

If we don't have owner uuid in there, should we just add it vs. the current workaround?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I was originally wary of making that conditional on owner_id, but now it's looking like it makes more sense in its current shape.

@johnstcnjohnstcn marked this pull request as ready for reviewJanuary 23, 2024 12:01
Copy link
Member

@mtojekmtojek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I then changed tactics and instead added a nullable UUID column favorite_of. This stores the UUID of the workspace owner when marked as 'favorite', and is NULL otherwise. We can then pass in the UUID of the caller in order to control the ordering of 'favorites'.

A comment on the design. Let's assume that Alice has workspace, and Bob is admin, but would like to mark Alice's workspace as favorite. Is it doable with this design?

@johnstcn
Copy link
MemberAuthor

I then changed tactics and instead added a nullable UUID column favorite_of. This stores the UUID of the workspace owner when marked as 'favorite', and is NULL otherwise. We can then pass in the UUID of the caller in order to control the ordering of 'favorites'.

A comment on the design. Let's assume that Alice has workspace, and Bob is admin, but would like to mark Alice's workspace as favorite. Is it doable with this design?

Bob can mark Alice's workspace as Alice's favorite on her behalf. Favoriting a workspace that you do not own isn't supported here, but is doable with some minor modifications:

  • favorite_of becomes auuid[]
  • Adduser_id to parameters ofFavoriteWorkspace /UnfavoriteWorkspace

Is this something you think is worthwhile?

Copy link
Member

@mtojekmtojek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is this something you think is worthwhile?

I would say so, unless PMs have different preferences. Alice might be scared that somebody "hacked" her account and started interacting with "favorites" markers.

favorite_of becomes a uuid[]

I admit that I prefer to have a separate "favorites" table than expanding the workspaces table schema, but I'm not a database expert. I wouldn't like to mix concerns as there is always the risk of growing up the workspace table. You may want to consult our database guy@mafredri :)

@johnstcn
Copy link
MemberAuthor

Is this something you think is worthwhile?

I would say so, unless PMs have different preferences. Alice might be scared that somebody "hacked" her account and started interacting with "favorites" markers.

To be fair, the same can be said of any action an admin takes on a user's resources.

@johnstcn
Copy link
MemberAuthor

I admit that I prefer to have a separate "favorites" table than expanding the workspaces table schema, but I'm not a database expert. I wouldn't like to mix concerns as there is always the risk of growing up the workspace table.

Yeah, this was my first attempt as well. Suffice it to say that adding any sort of join to theGetWorkspaceByID query will break many, many things and requires some annoying workarounds. Migrating this to a separate table down the line is definitely possible if required.

@mtojek
Copy link
Member

query will break many, many things and requires some annoying workarounds.

... unless you identify API calls that truly require "favorites" data. Maybe it is just theGetWorkspaces API call, so we can replace the database call withGetWorkspacesWithShares. Array fieldfavorites_of []uuid could be an option too if we acknowledge risks above.

Anyway, I understand the complexity, and I don't intend to stop you. I'd like to save us from reimplementing this feature in the future if we make wrong product assumptions.

@Emyrk
Copy link
Member

Emyrk commentedJan 23, 2024
edited
Loading

@mtojek Joining tables is more a limitation of our SQLc design. When you do any joins, the data structs become a mess.

So some of our database design, and likely this, is because of that. I think in a pure academic database setting, this could be normalized to another table. But the headache that gives up the stack makes a[]uuid column simpler imo.

And table normalization would only really show value if we then use that table for some other queries somehow? Like "Show me the number of favorited workpaces for each template". Then the[]uuid column starts to fall short.

We can always normalize later too, that migration doesn't feel too hard.

@mtojek
Copy link
Member

Like "Show me the number of favorited workpaces for each template". Then the []uuid column starts to fall short.

I'm afraid that devs could perceive it as an invitation to add more joined arrays, and build the single table model.

One more question, I might have missed it in the review, but will it be possible to expose the "favorite" information only to the requester? It would be useless to leak the information about who really "likes" the workspace.

@johnstcn
Copy link
MemberAuthor

Like "Show me the number of favorited workpaces for each template". Then the []uuid column starts to fall short.

I'm afraid that devs could perceive it as an invitation to add more joined arrays, and build the single table model.

One more question, I might have missed it in the review, but will it be possible to expose the "favorite" information only to the requester? It would be useless to leak the information about who really "likes" the workspace.

I think this is covered byconvertWorkspace taking the user ID of the requestor and only showing it as 'favorite' if the database structfavorite_of field matches the requestor's ID.

mtojek reacted with thumbs up emoji

@mtojek
Copy link
Member

Ok, thanks for clarifying my doubts. Feel free to pick the implementation you prefer.

Copy link
Member

@EmyrkEmyrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Left some nits, I think we should drop allowing admins to favorite other workspaces for now.

My nits are not blocking.

Comment on lines 265 to 267
CASE WHEN workspaces.favorite_of = @order_by_favorite THEN
workspaces.favorite_of = @order_by_favorite
END ASC,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Interesting that you do not need anELSE here.

I think we can drop the body of the case, and just puttrue. Just to remove a bit of duplication.

Suggested change
CASE WHENworkspaces.favorite_of= @order_by_favorite THEN
workspaces.favorite_of= @order_by_favorite
ENDASC,
CASE WHENworkspaces.favorite_of= @order_by_favorite THEN
true
ENDASC,

Comment on lines +1057 to +1064
err := api.Database.FavoriteWorkspace(ctx, workspace.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error setting workspace as favorite",
Detail: err.Error(),
})
return
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It feels weird not to verify that the caller is the person. I understand this is so an admin can favorite a workspace for someone else, but still feels odd 🤔.

Should we just not allow anyone to favorite someone else's workspace for now? If a feature request is made, we can add it back.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm happy to do that, it does smell like RBAC creep though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We do a lot of this outside rbac already unfortunately 😢. This stuff doesn't fit in rbac atm.

@Emyrk
Copy link
Member

Some notes for posterity:

Sorting by booleans is weird:

SELECT * FROM (VALUES(true), (false), (null)) s(a) ORDER BY a DESC;| a     || ----- || null  || true  || false |
SELECT * FROM (VALUES(true), (false), (null)) s(a) ORDER BY a ASC;| a     || ----- || false || true  || null  |

There isORDER BY x NULLS FIRST/LAST, but it is better to just remove the possibility of nulls.

Comment on lines +265 to +267
-- COALESCE because the result of the comparison is NULL if not true
-- and this messes up the ordering.
COALESCE(workspaces.favorite_of = @order_by_favorite, false) DESC,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍

@johnstcn
Copy link
MemberAuthor

This has gotten a bit big and unwieldy so I'm going to close this out and open a new PR with the feedback from this one.

@github-actionsgithub-actionsbot locked and limited conversation to collaboratorsJan 23, 2024
@github-actionsgithub-actionsbot deleted the cj/pin-workspaces branchJuly 24, 2024 00:06
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@mafredrimafredrimafredri left review comments

@EmyrkEmyrkEmyrk approved these changes

@mtojekmtojekmtojek left review comments

Assignees

@johnstcnjohnstcn

Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

4 participants
@johnstcn@mtojek@Emyrk@mafredri

[8]ページ先頭

©2009-2025 Movatter.jp