mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 18:47:40 +00:00
fix(automations): find folder-workspace targets on the Automations page (#27416)
Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
@@ -25,7 +25,12 @@ import {
|
||||
matchAutomationListSearchRowKeys
|
||||
} from './automation-list-search-rows'
|
||||
import { REPO_ID } from './automations-page-fixtures'
|
||||
import { automationRepoForRow, automationWorktreeForRow } from './automation-list-row-identity'
|
||||
import { makeFolderWorkspace } from '../sidebar/folder-workspace-composer-submit-fixtures'
|
||||
import {
|
||||
automationFolderWorktreeForRow,
|
||||
automationRepoForRow,
|
||||
automationWorktreeForRow
|
||||
} from './automation-list-row-identity'
|
||||
|
||||
const repoMap = new Map([
|
||||
[REPO_ID, { id: REPO_ID, displayName: 'orca', path: '/src/orca' } as Repo]
|
||||
@@ -177,4 +182,17 @@ describe('automation list row identity across hosts', () => {
|
||||
})
|
||||
).toEqual(['Desktop workspace', 'Runtime workspace'])
|
||||
})
|
||||
|
||||
it('resolves a folder workspace target inside each row authority', () => {
|
||||
const rows = collidedListRows().rows
|
||||
const folders = [
|
||||
makeFolderWorkspace({ id: 'fw-1', name: 'Desktop folder' }),
|
||||
makeFolderWorkspace({ id: 'fw-1', name: 'Runtime folder', executionHostId: 'runtime:gpu' })
|
||||
]
|
||||
|
||||
expect(
|
||||
rows.map((row) => automationFolderWorktreeForRow(row, folders, 'folder:fw-1')?.displayName)
|
||||
).toEqual(['Desktop folder', 'Runtime folder'])
|
||||
expect(automationFolderWorktreeForRow(rows[0], folders, 'ws-1')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,11 +19,15 @@ import type { AutomationUsageSummary } from '../../../../shared/automation-usage
|
||||
import { getAutomationRunRepoId } from '../../../../shared/automation-run-identity'
|
||||
import type { StableAutomationCatalogRef } from '../../../../shared/automation-owner-ref'
|
||||
import {
|
||||
getConnectionExecutionHostId,
|
||||
getRepoExecutionHostId,
|
||||
getWorktreeExecutionHostId
|
||||
} from '../../../../shared/execution-host'
|
||||
import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
|
||||
import type { Repo } from '../../../../shared/repo-types'
|
||||
import type { Worktree } from '../../../../shared/worktree/types'
|
||||
import { parseWorkspaceKey } from '../../../../shared/workspace-scope'
|
||||
import { getFolderWorkspaceWorktree } from '../../store/slices/worktrees/listing/detected-worktree-meta'
|
||||
|
||||
export type AutomationListRow = {
|
||||
/** Authority-qualified and incarnation-free; see the module note. */
|
||||
@@ -80,15 +84,41 @@ export function automationRepoForRow(
|
||||
if (!authority) {
|
||||
return fallback.get(repoId)
|
||||
}
|
||||
return repos.find((repo) => {
|
||||
const host = getRepoExecutionHostId(repo)
|
||||
return (
|
||||
repo.id === repoId &&
|
||||
(authority.kind === 'runtime'
|
||||
? host === `runtime:${encodeURIComponent(authority.environmentId)}`
|
||||
: !host.startsWith('runtime:'))
|
||||
)
|
||||
})
|
||||
return repos.find(
|
||||
(repo) => repo.id === repoId && authorityOwnsHost(authority, getRepoExecutionHostId(repo))
|
||||
)
|
||||
}
|
||||
|
||||
function authorityOwnsHost(
|
||||
authority: StableAutomationCatalogRef['authority'],
|
||||
hostId: string
|
||||
): boolean {
|
||||
return authority.kind === 'runtime'
|
||||
? hostId === `runtime:${encodeURIComponent(authority.environmentId)}`
|
||||
: !hostId.startsWith('runtime:')
|
||||
}
|
||||
|
||||
/** Folder workspaces are not in `worktreesByRepo`; dispatch resolves `folder:<id>` the same way. */
|
||||
export function automationFolderWorktreeForRow(
|
||||
row: AutomationListRow,
|
||||
folderWorkspaces: readonly FolderWorkspace[],
|
||||
workspaceId: string | null | undefined = row.automation.workspaceId
|
||||
): Worktree | undefined {
|
||||
const scope = workspaceId ? parseWorkspaceKey(workspaceId) : null
|
||||
if (scope?.type !== 'folder') {
|
||||
return undefined
|
||||
}
|
||||
const authority = row.catalogRef?.authority
|
||||
const matches = folderWorkspaces.filter(
|
||||
(workspace) =>
|
||||
workspace.id === scope.folderWorkspaceId &&
|
||||
(!authority ||
|
||||
authorityOwnsHost(
|
||||
authority,
|
||||
workspace.executionHostId ?? getConnectionExecutionHostId(workspace.connectionId)
|
||||
))
|
||||
)
|
||||
return matches.length === 1 ? getFolderWorkspaceWorktree(matches[0]) : undefined
|
||||
}
|
||||
|
||||
export function automationWorktreeForRow(
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { useAppStore } from '@/store'
|
||||
import { makeFolderWorkspace } from '../sidebar/folder-workspace-composer-submit-fixtures'
|
||||
import { makeAutomation } from './automations-page-fixtures'
|
||||
import { unscopedAutomationListRows } from './automation-list-row-identity'
|
||||
import { useAutomationsPageStoreState } from './use-automations-page-store-state'
|
||||
|
||||
const initialState = useAppStore.getState()
|
||||
|
||||
afterEach(() => {
|
||||
useAppStore.setState(initialState, true)
|
||||
})
|
||||
|
||||
describe('automation rows that target a folder workspace', () => {
|
||||
it('find the folder workspace instead of reporting it gone', () => {
|
||||
useAppStore.setState({
|
||||
folderWorkspaces: [makeFolderWorkspace({ id: 'fw-1', name: 'App' })]
|
||||
})
|
||||
const [row] = unscopedAutomationListRows([
|
||||
makeAutomation({ workspaceMode: 'existing', workspaceId: 'folder:fw-1' })
|
||||
])
|
||||
const { result } = renderHook(() => useAutomationsPageStoreState())
|
||||
|
||||
expect(result.current.worktreeForRow(row, undefined)?.displayName).toBe('App')
|
||||
})
|
||||
})
|
||||
@@ -6,6 +6,7 @@ import { getAgentCatalog } from '@/lib/agent-catalog'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useRepoMap, useWorktreeMap } from '@/store/selectors'
|
||||
import {
|
||||
automationFolderWorktreeForRow,
|
||||
automationRepoForRow,
|
||||
automationWorktreeForRow,
|
||||
type AutomationListRow
|
||||
@@ -18,6 +19,7 @@ export function useAutomationsPageStoreState() {
|
||||
const repos = useAppStore((s) => s.repos)
|
||||
const projectHostSetups = useAppStore((s) => s.projectHostSetups)
|
||||
const worktreesByRepo = useAppStore((s) => s.worktreesByRepo)
|
||||
const folderWorkspaces = useAppStore((s) => s.folderWorkspaces)
|
||||
const unifiedTabsByWorktree = useAppStore((s) => s.unifiedTabsByWorktree)
|
||||
const terminalLayoutsByTabId = useAppStore((s) => s.terminalLayoutsByTabId)
|
||||
const ptyIdsByTabId = useAppStore((s) => s.ptyIdsByTabId)
|
||||
@@ -52,8 +54,9 @@ export function useAutomationsPageStoreState() {
|
||||
repo: Repo | undefined,
|
||||
workspaceId: string | null | undefined = row.automation.workspaceId
|
||||
): Worktree | undefined =>
|
||||
automationFolderWorktreeForRow(row, folderWorkspaces, workspaceId) ??
|
||||
automationWorktreeForRow(row, worktreesByRepo, repo, worktreeMap, workspaceId),
|
||||
[worktreeMap, worktreesByRepo]
|
||||
[folderWorkspaces, worktreeMap, worktreesByRepo]
|
||||
)
|
||||
const enabledAgents = filterEnabledTuiAgents(AGENTS, settings?.disabledTuiAgents)
|
||||
const defaultAgent =
|
||||
|
||||
@@ -78,6 +78,17 @@ export function folderWorkspaceMatchesHost(
|
||||
)
|
||||
}
|
||||
|
||||
/** Stable synthetic row per folder workspace object, so selectors keep referential equality. */
|
||||
export function getFolderWorkspaceWorktree(folderWorkspace: FolderWorkspace): Worktree {
|
||||
const cached = folderWorkspaceWorktreeCache.get(folderWorkspace)
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
const worktree = folderWorkspaceToWorktree(folderWorkspace)
|
||||
folderWorkspaceWorktreeCache.set(folderWorkspace, worktree)
|
||||
return worktree
|
||||
}
|
||||
|
||||
export function findKnownWorktreeById(
|
||||
state: Pick<
|
||||
AppState,
|
||||
@@ -105,16 +116,7 @@ export function findKnownWorktreeById(
|
||||
workspace.id === workspaceScope.folderWorkspaceId &&
|
||||
(!executionHostId || folderWorkspaceMatchesHost(workspace, executionHostId))
|
||||
)
|
||||
if (!folderWorkspace) {
|
||||
return undefined
|
||||
}
|
||||
const cached = folderWorkspaceWorktreeCache.get(folderWorkspace)
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
const worktree = folderWorkspaceToWorktree(folderWorkspace)
|
||||
folderWorkspaceWorktreeCache.set(folderWorkspace, worktree)
|
||||
return worktree
|
||||
return folderWorkspace ? getFolderWorkspaceWorktree(folderWorkspace) : undefined
|
||||
}
|
||||
const visible = executionHostId
|
||||
? (findIndexedWorktreeOwnerForHost(
|
||||
|
||||
Reference in New Issue
Block a user