Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
}))

vi.mock('next/navigation', () => ({
usePathname: () => '/workspace/workspace-denied',
useRouter: () => ({ push: mockPush }),
}))

Expand Down Expand Up @@ -49,7 +50,52 @@ vi.mock('@/stores/workflows/registry/store', () => ({
) => selector({ switchToWorkspace: mockSwitchToWorkspace }),
}))

import { useWorkspaceManagement } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management'
import {
resolveWorkspaceSwitchHref,
useWorkspaceManagement,
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management'

describe('resolveWorkspaceSwitchHref', () => {
it('preserves the active settings section', () => {
expect(
resolveWorkspaceSwitchHref({
pathname: '/workspace/workspace-a/settings/mcp',
currentWorkspaceId: 'workspace-a',
targetWorkspaceId: 'workspace-b',
})
).toBe('/workspace/workspace-b/settings/mcp')
})

it('drops workspace-scoped settings detail segments', () => {
expect(
resolveWorkspaceSwitchHref({
pathname: '/workspace/workspace-a/settings/secrets/credential-a',
currentWorkspaceId: 'workspace-a',
targetWorkspaceId: 'workspace-b',
})
).toBe('/workspace/workspace-b/settings/secrets')
})

it('navigates to the workspace root outside settings', () => {
expect(
resolveWorkspaceSwitchHref({
pathname: '/workspace/workspace-a/w/workflow-a',
currentWorkspaceId: 'workspace-a',
targetWorkspaceId: 'workspace-b',
})
).toBe('/workspace/workspace-b')
})

it('fails fast when a settings pathname has no section', () => {
expect(() =>
resolveWorkspaceSwitchHref({
pathname: '/workspace/workspace-a/settings/',
currentWorkspaceId: 'workspace-a',
targetWorkspaceId: 'workspace-b',
})
).toThrow('Settings pathname is missing a section')
})
})

function Harness() {
useWorkspaceManagement({ workspaceId: 'workspace-denied', sessionUserId: 'user-1' })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { useRouter } from 'next/navigation'
import { usePathname, useRouter } from 'next/navigation'
import { requestJson } from '@/lib/api/client/request'
import { updateUserSettingsContract } from '@/lib/api/contracts'
import { WorkspaceRecencyStorage } from '@/lib/core/utils/browser-storage'
Expand All @@ -25,6 +25,33 @@ interface UseWorkspaceManagementProps {
sessionUserId?: string
}

interface ResolveWorkspaceSwitchHrefParams {
pathname: string
currentWorkspaceId: string
targetWorkspaceId: string
}

/**
* Keeps the active settings section across workspace switches without carrying
* workspace-scoped detail IDs into the destination workspace.
*/
export function resolveWorkspaceSwitchHref({
pathname,
currentWorkspaceId,
targetWorkspaceId,
}: ResolveWorkspaceSwitchHrefParams): string {
const targetWorkspaceHref = `/workspace/${targetWorkspaceId}`
const settingsPrefix = `/workspace/${currentWorkspaceId}/settings/`
if (!pathname.startsWith(settingsPrefix)) return targetWorkspaceHref

const [section] = pathname.slice(settingsPrefix.length).split('/')
if (!section) {
throw new Error(`Settings pathname is missing a section: ${pathname}`)
}

return `${targetWorkspaceHref}/settings/${section}`
Comment thread
TheodoreSpeaks marked this conversation as resolved.
}

/**
* Manages workspace operations including fetching, switching, creating, deleting, and leaving workspaces.
* Handles URL synchronization and recency-based ordering. Route access is
Expand All @@ -40,6 +67,7 @@ export function useWorkspaceManagement({
sessionUserId,
}: UseWorkspaceManagementProps) {
const router = useRouter()
const pathname = usePathname()
const switchToWorkspace = useWorkflowRegistry((state) => state.switchToWorkspace)

const { data: workspaces = [], isLoading: isWorkspacesLoading } = useWorkspacesQuery(
Expand Down Expand Up @@ -157,15 +185,21 @@ export function useWorkspaceManagement({
return
}

const href = resolveWorkspaceSwitchHref({
pathname,
currentWorkspaceId: workspaceIdRef.current,
targetWorkspaceId: workspace.id,
})

try {
switchToWorkspace(workspace.id)
routerRef.current?.push(`/workspace/${workspace.id}`)
routerRef.current.push(href)
logger.info(`Switched to workspace: ${workspace.name} (${workspace.id})`)
} catch (error) {
logger.error('Error switching workspace:', error)
}
},
[switchToWorkspace]
[pathname, switchToWorkspace]
)

const handleCreateWorkspace = useCallback(
Expand Down
Loading