From 647101b4216e5c38771d552777aba1859b4fec15 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Thu, 25 Sep 2025 16:54:44 -0400 Subject: [PATCH] feat: add `--shared-with-me` flag to `coder list` command (#19948) Closes [coder/internal#1013](https://github.com/coder/internal/issues/1013) --- cli/list.go | 26 +++++++++++++++++++++++++- cli/list_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/cli/list.go b/cli/list.go index be80843534..bcd5ae2dc0 100644 --- a/cli/list.go +++ b/cli/list.go @@ -95,6 +95,7 @@ func (r *RootCmd) list() *serpent.Command { ), cliui.JSONFormat(), ) + sharedWithMe bool ) cmd := &serpent.Command{ Annotations: workspaceCommand, @@ -104,13 +105,36 @@ func (r *RootCmd) list() *serpent.Command { Middleware: serpent.Chain( serpent.RequireNArgs(0), ), + Options: serpent.OptionSet{ + { + Name: "shared-with-me", + Description: "Show workspaces shared with you.", + Flag: "shared-with-me", + Value: serpent.BoolOf(&sharedWithMe), + Hidden: true, + }, + }, Handler: func(inv *serpent.Invocation) error { client, err := r.InitClient(inv) if err != nil { return err } - res, err := QueryConvertWorkspaces(inv.Context(), client, filter.Filter(), WorkspaceListRowFromWorkspace) + workspaceFilter := filter.Filter() + if sharedWithMe { + user, err := client.User(inv.Context(), codersdk.Me) + if err != nil { + return xerrors.Errorf("fetch current user: %w", err) + } + workspaceFilter.SharedWithUser = user.ID.String() + + // Unset the default query that conflicts with the --shared-with-me flag + if workspaceFilter.FilterQuery == "owner:me" { + workspaceFilter.FilterQuery = "" + } + } + + res, err := QueryConvertWorkspaces(inv.Context(), client, workspaceFilter, WorkspaceListRowFromWorkspace) if err != nil { return err } diff --git a/cli/list_test.go b/cli/list_test.go index a70c70babf..0210fd715f 100644 --- a/cli/list_test.go +++ b/cli/list_test.go @@ -13,6 +13,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbfake" + "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" @@ -100,4 +101,49 @@ func TestList(t *testing.T) { require.Len(t, stderr.Bytes(), 0) }) + + t.Run("SharedWorkspaces", func(t *testing.T) { + t.Parallel() + + var ( + client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{ + DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) { + dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)} + }), + }) + orgOwner = coderdtest.CreateFirstUser(t, client) + memberClient, member = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID)) + sharedWorkspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ + Name: "wibble", + OwnerID: orgOwner.UserID, + OrganizationID: orgOwner.OrganizationID, + }).Do().Workspace + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ + Name: "wobble", + OwnerID: orgOwner.UserID, + OrganizationID: orgOwner.OrganizationID, + }).Do().Workspace + ) + + ctx := testutil.Context(t, testutil.WaitMedium) + + client.UpdateWorkspaceACL(ctx, sharedWorkspace.ID, codersdk.UpdateWorkspaceACL{ + UserRoles: map[string]codersdk.WorkspaceRole{ + member.ID.String(): codersdk.WorkspaceRoleUse, + }, + }) + + inv, root := clitest.New(t, "list", "--shared-with-me", "--output=json") + clitest.SetupConfig(t, memberClient, root) + + stdout := new(bytes.Buffer) + inv.Stdout = stdout + err := inv.WithContext(ctx).Run() + require.NoError(t, err) + + var workspaces []codersdk.Workspace + require.NoError(t, json.Unmarshal(stdout.Bytes(), &workspaces)) + require.Len(t, workspaces, 1) + require.Equal(t, sharedWorkspace.ID, workspaces[0].ID) + }) }