mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 00:02:41 +00:00
@@ -102,6 +102,47 @@ describe('Branch source results', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('uses Linear rows from a paginated collection shape', () => {
|
||||
const rows = buildSmartWorkspaceSourceRows({
|
||||
mode: 'smart',
|
||||
value: '',
|
||||
branches: [],
|
||||
githubItems: [],
|
||||
gitlabItems: [],
|
||||
linearIssues: {
|
||||
items: [{ id: 'linear-1', identifier: 'ENG-1', title: 'Fix composer crash' } as never],
|
||||
hasMore: true
|
||||
},
|
||||
gitlabAvailable: false,
|
||||
linearAvailable: true,
|
||||
resultLimit: 12
|
||||
})
|
||||
|
||||
expect(rows).toEqual([
|
||||
{
|
||||
kind: 'linear',
|
||||
value: 'linear-linear-1',
|
||||
issue: { id: 'linear-1', identifier: 'ENG-1', title: 'Fix composer crash' }
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('ignores malformed Linear collection rows instead of throwing during render', () => {
|
||||
expect(() =>
|
||||
buildSmartWorkspaceSourceRows({
|
||||
mode: 'smart',
|
||||
value: '',
|
||||
branches: [],
|
||||
githubItems: [],
|
||||
gitlabItems: [],
|
||||
linearIssues: { items: { id: 'not-an-array' } } as never,
|
||||
gitlabAvailable: false,
|
||||
linearAvailable: true,
|
||||
resultLimit: 12
|
||||
})
|
||||
).not.toThrow()
|
||||
})
|
||||
|
||||
it('describes empty Branch results after the empty-query search runs', () => {
|
||||
expect(getSmartWorkspaceEmptyHint('branches')).toBe('No matching branches.')
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
BaseRefSearchResult,
|
||||
GitHubWorkItem,
|
||||
GitLabWorkItem,
|
||||
LinearCollectionResult,
|
||||
LinearIssue
|
||||
} from '../../../../shared/types'
|
||||
|
||||
@@ -15,6 +16,8 @@ export type SmartWorkspaceSourceRow =
|
||||
| { kind: 'branch'; value: string; refName: string; localBranchName: string }
|
||||
| { kind: 'linear'; value: string; issue: LinearIssue }
|
||||
|
||||
type LinearIssueSourceInput = LinearIssue[] | LinearCollectionResult<LinearIssue> | null | undefined
|
||||
|
||||
const EMPTY_HINT_BY_MODE: Record<SmartNameMode, string> = {
|
||||
smart: 'Start typing to create a name or find a source.',
|
||||
github: 'Start typing to search GitHub PRs and issues.',
|
||||
@@ -92,7 +95,7 @@ export function buildSmartWorkspaceSourceRows({
|
||||
gitlabAvailable: boolean
|
||||
gitlabItems: GitLabWorkItem[]
|
||||
linearAvailable: boolean
|
||||
linearIssues: LinearIssue[]
|
||||
linearIssues: LinearIssueSourceInput
|
||||
mode: SmartNameMode
|
||||
resultLimit: number
|
||||
value: string
|
||||
@@ -141,8 +144,15 @@ export function buildSmartWorkspaceSourceRows({
|
||||
)
|
||||
}
|
||||
if (linearAvailable && (mode === 'smart' || mode === 'linear')) {
|
||||
// Why: mixed-version runtime responses may briefly carry the paginated
|
||||
// collection shape into this render path; rendering must stay recoverable.
|
||||
const resolvedLinearIssues = Array.isArray(linearIssues)
|
||||
? linearIssues
|
||||
: Array.isArray(linearIssues?.items)
|
||||
? linearIssues.items
|
||||
: []
|
||||
nextRows.push(
|
||||
...linearIssues.map((issue) => ({
|
||||
...resolvedLinearIssues.map((issue) => ({
|
||||
kind: 'linear' as const,
|
||||
value: `linear-${issue.id}`,
|
||||
issue
|
||||
|
||||
@@ -132,6 +132,14 @@ describe('runtime linear client', () => {
|
||||
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('wraps legacy local Linear issue list arrays as collection results', async () => {
|
||||
linearListIssuesLocal.mockResolvedValue([{ id: 'legacy-issue' }])
|
||||
|
||||
await expect(
|
||||
linearListIssues({ activeRuntimeEnvironmentId: null }, 'assigned', 20)
|
||||
).resolves.toEqual({ items: [{ id: 'legacy-issue' }] })
|
||||
})
|
||||
|
||||
it('does not throw when an older local preload lacks project listing', async () => {
|
||||
delete (window.api.linear as { listProjects?: unknown }).listProjects
|
||||
|
||||
@@ -285,6 +293,39 @@ describe('runtime linear client', () => {
|
||||
expect(linearListIssuesLocal).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('wraps legacy remote Linear issue list arrays as collection results', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValueOnce({
|
||||
id: 'rpc-list',
|
||||
ok: true,
|
||||
result: [{ id: 'legacy-issue' }],
|
||||
_meta: { runtimeId: 'runtime-1' }
|
||||
})
|
||||
|
||||
await expect(
|
||||
linearListIssues({ activeRuntimeEnvironmentId: 'env-1' }, 'assigned', 20)
|
||||
).resolves.toEqual({ items: [{ id: 'legacy-issue' }] })
|
||||
|
||||
expect(runtimeEnvironmentCall).toHaveBeenCalledWith({
|
||||
selector: 'env-1',
|
||||
method: 'linear.listIssues',
|
||||
params: { filter: 'assigned', limit: 20, workspaceId: undefined },
|
||||
timeoutMs: 30_000
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to an empty Linear issue collection for malformed list responses', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValueOnce({
|
||||
id: 'rpc-list',
|
||||
ok: true,
|
||||
result: { items: { id: 'not-an-array' }, hasMore: true },
|
||||
_meta: { runtimeId: 'runtime-1' }
|
||||
})
|
||||
|
||||
await expect(
|
||||
linearListIssues({ activeRuntimeEnvironmentId: 'env-1' }, 'assigned', 20)
|
||||
).resolves.toEqual({ items: [] })
|
||||
})
|
||||
|
||||
it('routes Linear mutations and metadata through the selected runtime environment', async () => {
|
||||
runtimeEnvironmentCall
|
||||
.mockResolvedValueOnce({
|
||||
|
||||
@@ -39,6 +39,26 @@ function linearReadForce(options?: LinearReadOptions): { force: true } | {} {
|
||||
return options?.force ? { force: true } : {}
|
||||
}
|
||||
|
||||
function normalizeLinearIssueCollectionResult(
|
||||
result: unknown
|
||||
): LinearCollectionResult<LinearIssue> {
|
||||
if (Array.isArray(result)) {
|
||||
return { items: result as LinearIssue[] }
|
||||
}
|
||||
if (!result || typeof result !== 'object') {
|
||||
return { items: [] }
|
||||
}
|
||||
const collection = result as Partial<LinearCollectionResult<LinearIssue>>
|
||||
if (!Array.isArray(collection.items)) {
|
||||
return { items: [] }
|
||||
}
|
||||
return {
|
||||
items: collection.items,
|
||||
...(Array.isArray(collection.errors) ? { errors: collection.errors } : {}),
|
||||
...(typeof collection.hasMore === 'boolean' ? { hasMore: collection.hasMore } : {})
|
||||
}
|
||||
}
|
||||
|
||||
export async function linearStatus(
|
||||
settings: RuntimeLinearSettings
|
||||
): Promise<LinearConnectionStatus> {
|
||||
@@ -144,14 +164,20 @@ export async function linearListIssues(
|
||||
workspaceId?: LinearWorkspaceSelection | null
|
||||
): Promise<LinearCollectionResult<LinearIssue>> {
|
||||
const target = getActiveRuntimeTarget(settings)
|
||||
return target.kind === 'environment'
|
||||
? callRuntimeRpc<LinearCollectionResult<LinearIssue>>(
|
||||
target,
|
||||
'linear.listIssues',
|
||||
{ filter, limit, workspaceId: workspaceId ?? undefined },
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: window.api.linear.listIssues({ filter, limit, workspaceId: workspaceId ?? undefined })
|
||||
const result =
|
||||
target.kind === 'environment'
|
||||
? await callRuntimeRpc<unknown>(
|
||||
target,
|
||||
'linear.listIssues',
|
||||
{ filter, limit, workspaceId: workspaceId ?? undefined },
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: await window.api.linear.listIssues({
|
||||
filter,
|
||||
limit,
|
||||
workspaceId: workspaceId ?? undefined
|
||||
})
|
||||
return normalizeLinearIssueCollectionResult(result)
|
||||
}
|
||||
|
||||
export async function linearCreateIssue(
|
||||
|
||||
Reference in New Issue
Block a user