diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 10d0dc9281882..e01d4292cb800 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1147,16 +1147,34 @@ func convertWorkspaces(workspaces []database.Workspace, data workspaceData) ([]c &owner, )) } - sort.Slice(apiWorkspaces, func(i, j int) bool { - iw := apiWorkspaces[i] - jw := apiWorkspaces[j] + + sortWorkspaces(apiWorkspaces) + + return apiWorkspaces, nil +} + +func sortWorkspaces(workspaces []codersdk.Workspace) { + sort.Slice(workspaces, func(i, j int) bool { + iw := workspaces[i] + jw := workspaces[j] + + if iw.LatestBuild.Status == codersdk.WorkspaceStatusRunning && jw.LatestBuild.Status != codersdk.WorkspaceStatusRunning { + return true + } + + if jw.LatestBuild.Status == codersdk.WorkspaceStatusRunning && iw.LatestBuild.Status != codersdk.WorkspaceStatusRunning { + return false + } + + if iw.OwnerID != jw.OwnerID { + return iw.OwnerName < jw.OwnerName + } + if jw.LastUsedAt.IsZero() && iw.LastUsedAt.IsZero() { return iw.Name < jw.Name } return iw.LastUsedAt.After(jw.LastUsedAt) }) - - return apiWorkspaces, nil } func convertWorkspace( diff --git a/coderd/workspaces_internal_test.go b/coderd/workspaces_internal_test.go index 44c1699309c4c..62a5091539330 100644 --- a/coderd/workspaces_internal_test.go +++ b/coderd/workspaces_internal_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/coder/coder/coderd/database" @@ -80,3 +81,96 @@ func Test_calculateDeletingAt(t *testing.T) { }) } } + +func TestSortWorkspaces(t *testing.T) { + // the correct sorting order is: + // 1. first show workspaces that are currently running, + // 2. then sort by user_name, + // 3. then sort by last_used_at (descending), + t.Parallel() + + workspaceFactory := func(t *testing.T, name string, ownerID uuid.UUID, ownerName string, status codersdk.WorkspaceStatus, lastUsedAt time.Time) codersdk.Workspace { + t.Helper() + return codersdk.Workspace{ + ID: uuid.New(), + OwnerID: ownerID, + OwnerName: ownerName, + LatestBuild: codersdk.WorkspaceBuild{ + Status: status, + }, + Name: name, + LastUsedAt: lastUsedAt, + } + } + + userAuuid := uuid.New() + + workspaceRunningUserA := workspaceFactory(t, "running-userA", userAuuid, "userA", codersdk.WorkspaceStatusRunning, time.Now()) + workspaceRunningUserB := workspaceFactory(t, "running-userB", uuid.New(), "userB", codersdk.WorkspaceStatusRunning, time.Now()) + workspacePendingUserC := workspaceFactory(t, "pending-userC", uuid.New(), "userC", codersdk.WorkspaceStatusPending, time.Now()) + workspaceRunningUserA2 := workspaceFactory(t, "running-userA2", userAuuid, "userA", codersdk.WorkspaceStatusRunning, time.Now().Add(time.Minute)) + workspaceRunningUserZ := workspaceFactory(t, "running-userZ", uuid.New(), "userZ", codersdk.WorkspaceStatusRunning, time.Now()) + workspaceRunningUserA3 := workspaceFactory(t, "running-userA3", userAuuid, "userA", codersdk.WorkspaceStatusRunning, time.Now().Add(time.Hour)) + + testCases := []struct { + name string + input []codersdk.Workspace + expectedOrder []string + }{ + { + name: "Running workspaces should be first", + input: []codersdk.Workspace{ + workspaceRunningUserB, + workspacePendingUserC, + workspaceRunningUserA, + }, + expectedOrder: []string{ + "running-userA", + "running-userB", + "pending-userC", + }, + }, + { + name: "then sort by owner name", + input: []codersdk.Workspace{ + workspaceRunningUserZ, + workspaceRunningUserA, + workspaceRunningUserB, + }, + expectedOrder: []string{ + "running-userA", + "running-userB", + "running-userZ", + }, + }, + { + name: "then sort by last used at (recent first)", + input: []codersdk.Workspace{ + workspaceRunningUserA, + workspaceRunningUserA2, + workspaceRunningUserA3, + }, + expectedOrder: []string{ + "running-userA3", + "running-userA2", + "running-userA", + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + workspaces := tc.input + sortWorkspaces(workspaces) + + var resultNames []string + for _, workspace := range workspaces { + resultNames = append(resultNames, workspace.Name) + } + + require.Equal(t, tc.expectedOrder, resultNames, tc.name) + }) + } +}