From 9dde4e7bc716cade57adbb17c5a167efef3da301 Mon Sep 17 00:00:00 2001 From: Simula_r <18093452+simula-r@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:11:35 -0800 Subject: [PATCH] feat: sort workspaces (#8770) ## Summary Sort workspaces so that the personal workspace appears first, followed by the rest in ascending order (oldest first) by created_at / joined_at. ## Changes - **What**: teamWorkspaceStore.ts, teamWorkspaceStore.test.ts - **Breaking**: - **Dependencies**: --- .../auth/workspace/useWorkspaceSwitch.test.ts | 4 +- src/platform/workspace/api/workspaceApi.ts | 2 + .../stores/teamWorkspaceStore.test.ts | 76 +++++++++++++++++-- .../workspace/stores/teamWorkspaceStore.ts | 22 +++++- 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/platform/auth/workspace/useWorkspaceSwitch.test.ts b/src/platform/auth/workspace/useWorkspaceSwitch.test.ts index 914c8bf9a..6cc03049d 100644 --- a/src/platform/auth/workspace/useWorkspaceSwitch.test.ts +++ b/src/platform/auth/workspace/useWorkspaceSwitch.test.ts @@ -53,7 +53,9 @@ describe('useWorkspaceSwitch', () => { id: 'workspace-1', name: 'Test Workspace', type: 'personal', - role: 'owner' + role: 'owner', + created_at: '2026-01-01T00:00:00Z', + joined_at: '2026-01-01T00:00:00Z' } mockModifiedWorkflows.length = 0 }) diff --git a/src/platform/workspace/api/workspaceApi.ts b/src/platform/workspace/api/workspaceApi.ts index caf07db40..a0361ddfa 100644 --- a/src/platform/workspace/api/workspaceApi.ts +++ b/src/platform/workspace/api/workspaceApi.ts @@ -11,6 +11,8 @@ interface Workspace { id: string name: string type: WorkspaceType + created_at: string + joined_at: string } export interface WorkspaceWithRole extends Workspace { diff --git a/src/platform/workspace/stores/teamWorkspaceStore.test.ts b/src/platform/workspace/stores/teamWorkspaceStore.test.ts index 92b712787..2c29bfd3c 100644 --- a/src/platform/workspace/stores/teamWorkspaceStore.test.ts +++ b/src/platform/workspace/stores/teamWorkspaceStore.test.ts @@ -2,7 +2,7 @@ import { createTestingPinia } from '@pinia/testing' import { setActivePinia } from 'pinia' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { useTeamWorkspaceStore } from './teamWorkspaceStore' +import { sortWorkspaces, useTeamWorkspaceStore } from './teamWorkspaceStore' // Mock workspaceAuthStore const mockWorkspaceAuthStore = vi.hoisted(() => ({ @@ -93,21 +93,27 @@ const mockPersonalWorkspace = { id: 'ws-personal-123', name: 'Personal', type: 'personal' as const, - role: 'owner' as const + role: 'owner' as const, + created_at: '2026-01-01T00:00:00Z', + joined_at: '2026-01-01T00:00:00Z' } const mockTeamWorkspace = { id: 'ws-team-456', name: 'Team Alpha', type: 'team' as const, - role: 'owner' as const + role: 'owner' as const, + created_at: '2026-02-01T00:00:00Z', + joined_at: '2026-02-01T00:00:00Z' } const mockMemberWorkspace = { id: 'ws-team-789', name: 'Team Beta', type: 'team' as const, - role: 'member' as const + role: 'member' as const, + created_at: '2026-03-01T00:00:00Z', + joined_at: '2026-03-01T00:00:00Z' } describe('useTeamWorkspaceStore', () => { @@ -309,7 +315,9 @@ describe('useTeamWorkspaceStore', () => { id: 'ws-new-999', name: 'New Workspace', type: 'team' as const, - role: 'member' as const + role: 'member' as const, + created_at: '2026-04-01T00:00:00Z', + joined_at: '2026-04-01T00:00:00Z' } mockWorkspaceApi.list @@ -347,7 +355,9 @@ describe('useTeamWorkspaceStore', () => { id: 'ws-new-created', name: 'Created Workspace', type: 'team' as const, - role: 'owner' as const + role: 'owner' as const, + created_at: '2026-05-01T00:00:00Z', + joined_at: '2026-05-01T00:00:00Z' } mockWorkspaceApi.create.mockResolvedValue(newWorkspace) @@ -386,7 +396,9 @@ describe('useTeamWorkspaceStore', () => { id: 'ws-new', name: 'New Workspace', type: 'team', - role: 'owner' + role: 'owner', + created_at: '2026-06-01T00:00:00Z', + joined_at: '2026-06-01T00:00:00Z' }) await resultPromise }) @@ -572,7 +584,9 @@ describe('useTeamWorkspaceStore', () => { id: `ws-owned-${i}`, name: `Owned ${i}`, type: 'team' as const, - role: 'owner' as const + role: 'owner' as const, + created_at: `2026-${String(i + 1).padStart(2, '0')}-01T00:00:00Z`, + joined_at: `2026-${String(i + 1).padStart(2, '0')}-01T00:00:00Z` })) mockWorkspaceApi.list.mockResolvedValue({ @@ -916,3 +930,49 @@ describe('useTeamWorkspaceStore', () => { }) }) }) + +describe('sortWorkspaces', () => { + it('places personal first, then sorts ascending by created_at for owners and joined_at for members', () => { + const input = [ + { + created_at: '2026-06-01T00:00:00Z', + id: 'w-team-new-owner', + joined_at: '2026-01-01T00:00:00Z', + name: 'Newest Owner Team', + role: 'owner' as const, + type: 'team' as const + }, + { + created_at: '2026-12-01T00:00:00Z', + id: 'w-personal', + joined_at: '2026-12-01T00:00:00Z', + name: 'Personal Workspace', + role: 'owner' as const, + type: 'personal' as const + }, + { + created_at: '2026-01-01T00:00:00Z', + id: 'w-team-member', + joined_at: '2026-04-01T00:00:00Z', + name: 'Member Team', + role: 'member' as const, + type: 'team' as const + }, + { + created_at: '2026-02-01T00:00:00Z', + id: 'w-team-old-owner', + joined_at: '2026-09-01T00:00:00Z', + name: 'Oldest Owner Team', + role: 'owner' as const, + type: 'team' as const + } + ] + + expect(sortWorkspaces(input).map((w) => w.id)).toEqual([ + 'w-personal', + 'w-team-old-owner', + 'w-team-member', + 'w-team-new-owner' + ]) + }) +}) diff --git a/src/platform/workspace/stores/teamWorkspaceStore.ts b/src/platform/workspace/stores/teamWorkspaceStore.ts index b6a6dbd46..acc69acad 100644 --- a/src/platform/workspace/stores/teamWorkspaceStore.ts +++ b/src/platform/workspace/stores/teamWorkspaceStore.ts @@ -76,6 +76,16 @@ function createWorkspaceState(workspace: WorkspaceWithRole): WorkspaceState { } } +export function sortWorkspaces(list: T[]): T[] { + return [...list].sort((a, b) => { + if (a.type === 'personal') return -1 + if (b.type === 'personal') return 1 + const dateA = a.role === 'owner' ? a.created_at : a.joined_at + const dateB = b.role === 'owner' ? b.created_at : b.joined_at + return dateA.localeCompare(dateB) + }) +} + function getLastWorkspaceId(): string | null { try { return localStorage.getItem(WORKSPACE_STORAGE_KEYS.LAST_WORKSPACE_ID) @@ -205,7 +215,9 @@ export const useTeamWorkspaceStore = defineStore('teamWorkspace', () => { if (hasValidSession && workspaceAuthStore.currentWorkspace) { // Valid session exists - fetch workspace list and verify access const response = await workspaceApi.list() - workspaces.value = response.workspaces.map(createWorkspaceState) + workspaces.value = sortWorkspaces( + response.workspaces.map(createWorkspaceState) + ) if (workspaces.value.length === 0) { throw new Error('No workspaces available') @@ -247,7 +259,9 @@ export const useTeamWorkspaceStore = defineStore('teamWorkspace', () => { // 2. No valid session - fetch workspaces and pick default const response = await workspaceApi.list() - workspaces.value = response.workspaces.map(createWorkspaceState) + workspaces.value = sortWorkspaces( + response.workspaces.map(createWorkspaceState) + ) if (workspaces.value.length === 0) { throw new Error('No workspaces available') @@ -315,7 +329,9 @@ export const useTeamWorkspaceStore = defineStore('teamWorkspace', () => { isFetchingWorkspaces.value = true try { const response = await workspaceApi.list() - workspaces.value = response.workspaces.map(createWorkspaceState) + workspaces.value = sortWorkspaces( + response.workspaces.map(createWorkspaceState) + ) } finally { isFetchingWorkspaces.value = false }