mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
Route GitHub drawer metadata by source host
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -268,3 +268,4 @@ Done means: reviewers and future agents can understand the vision, implementatio
|
||||
- [x] 2026-06-12: Completed Tasks account/host metadata by showing repo-backed account labels in source summaries when GitHub/GitLab accounts differ by Host, while preserving host labels, provider identities, Linear workspace names, Jira site names, and unavailable-host status in labels/titles. Verified with source-summary and repo picker tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
- [x] 2026-06-12: Continued the automation project-id migration by changing CLI automation show output to foreground explicit `runContext` fields (`runProjectId`, `runHostId`, `projectHostSetupId`, `runRepoId`, `runPath`) and label the old repo id as `legacyRepoId`. The checklist row remains partial because the shared `Automation.projectId` compatibility field still exists. Verified with CLI format/index tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
- [x] 2026-06-12: Aligned Automations manual-run disabled states with the backend run-target resolver so saved run contexts now disable Run Now when the project-host setup is missing or not ready, instead of failing after dispatch. Verified with focused automation availability tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
- [x] 2026-06-12: Routed GitHub drawer label/assignee/reviewer metadata reads through the same task source context as issue/PR mutations, including slug-addressed Project view rows and reviewer request/remove actions, so same-repo multi-host drawers do not fall back to the repo owner or focused runtime. Verified with a focused source-boundary test, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
|
||||
@@ -167,7 +167,10 @@ import type {
|
||||
PRCheckDetail,
|
||||
PRComment
|
||||
} from '../../../shared/types'
|
||||
import type { TaskSourceContext } from '../../../shared/task-source-context'
|
||||
import {
|
||||
getTaskSourceRuntimeSettings,
|
||||
type TaskSourceContext
|
||||
} from '../../../shared/task-source-context'
|
||||
import { PER_REPO_FETCH_LIMIT } from '../../../shared/work-items'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner'
|
||||
@@ -456,10 +459,19 @@ function PRReviewersPanel({
|
||||
reviewRequests: item.reviewRequests
|
||||
}))
|
||||
const patchWorkItem = useAppStore((s) => s.patchWorkItem)
|
||||
const settings = useAppStore((s) => s.settings)
|
||||
const repoOwnerSettings = useAppStore(
|
||||
useShallow((s) => getSettingsForRepoRuntimeOwner(s, item.repoId ?? null))
|
||||
)
|
||||
const sourceSettings = useMemo(
|
||||
() =>
|
||||
sourceContext?.provider === 'github'
|
||||
? ({
|
||||
...repoOwnerSettings,
|
||||
...getTaskSourceRuntimeSettings(sourceContext)
|
||||
} as typeof repoOwnerSettings)
|
||||
: repoOwnerSettings,
|
||||
[repoOwnerSettings, sourceContext]
|
||||
)
|
||||
const reviewerInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const reviewerInputFocusFrameRef = useRef<number | null>(null)
|
||||
const reviewerPanelMountedRef = useRef(true)
|
||||
@@ -534,11 +546,12 @@ function PRReviewersPanel({
|
||||
open && reviewSlug ? reviewSlug.owner : null,
|
||||
open && reviewSlug ? reviewSlug.repo : null,
|
||||
reviewerSeedUsers.map((user) => user.login),
|
||||
settings
|
||||
sourceSettings
|
||||
)
|
||||
const reviewerMetadataByPath = useRepoAssignees(
|
||||
open && !reviewSlug ? repoPath : null,
|
||||
open && !reviewSlug ? item.repoId : null
|
||||
open && !reviewSlug ? item.repoId : null,
|
||||
sourceSettings
|
||||
)
|
||||
const reviewerMetadata = reviewSlug ? reviewerMetadataBySlug : reviewerMetadataByPath
|
||||
const displayItem = { ...item, reviewRequests: localReviewRequests }
|
||||
@@ -638,7 +651,7 @@ function PRReviewersPanel({
|
||||
item.reviewRequests !== undefined ||
|
||||
item.latestReviews !== undefined
|
||||
const canRequestReview =
|
||||
!!repoPath || getActiveRuntimeTarget(repoOwnerSettings).kind === 'environment'
|
||||
!!repoPath || getActiveRuntimeTarget(sourceSettings).kind === 'environment'
|
||||
|
||||
const measureReviewerPickerPlacement = useCallback(() => {
|
||||
const rect = reviewerInputRef.current?.getBoundingClientRect()
|
||||
@@ -681,7 +694,7 @@ function PRReviewersPanel({
|
||||
)
|
||||
return
|
||||
}
|
||||
const target = getActiveRuntimeTarget(repoOwnerSettings)
|
||||
const target = getActiveRuntimeTarget(sourceSettings)
|
||||
if (target.kind !== 'environment' && !repoPath) {
|
||||
toast.error(
|
||||
translate(
|
||||
@@ -759,7 +772,7 @@ function PRReviewersPanel({
|
||||
if (logins.length === 0) {
|
||||
return
|
||||
}
|
||||
const target = getActiveRuntimeTarget(repoOwnerSettings)
|
||||
const target = getActiveRuntimeTarget(sourceSettings)
|
||||
if (target.kind !== 'environment' && !repoPath) {
|
||||
toast.error(
|
||||
translate(
|
||||
@@ -4545,6 +4558,19 @@ function GHEditSection({
|
||||
const assigneesItemKey = `${item.repoId}\0${item.id}`
|
||||
const patchWorkItem = useAppStore((s) => s.patchWorkItem)
|
||||
const patchProjectRowContent = useAppStore((s) => s.patchProjectRowContent)
|
||||
const repoOwnerSettings = useAppStore(
|
||||
useShallow((s) => getSettingsForRepoRuntimeOwner(s, item.repoId ?? null))
|
||||
)
|
||||
const sourceSettings = useMemo(
|
||||
() =>
|
||||
sourceContext?.provider === 'github'
|
||||
? ({
|
||||
...repoOwnerSettings,
|
||||
...getTaskSourceRuntimeSettings(sourceContext)
|
||||
} as typeof repoOwnerSettings)
|
||||
: repoOwnerSettings,
|
||||
[repoOwnerSettings, sourceContext]
|
||||
)
|
||||
const { isPending, run } = useImmediateMutation()
|
||||
// Why: when the dialog opens from a Project view, mutations route through
|
||||
// *BySlug IPCs and we must keep `projectViewCache` in sync alongside
|
||||
@@ -4568,16 +4594,18 @@ function GHEditSection({
|
||||
const slugRepo = projectOrigin?.repo ?? null
|
||||
const repoLabelsByPath = useRepoLabels(
|
||||
projectOrigin ? null : repoPath,
|
||||
projectOrigin ? null : repoId
|
||||
projectOrigin ? null : repoId,
|
||||
sourceSettings
|
||||
)
|
||||
const repoLabelsBySlug = useRepoLabelsBySlug(slugOwner, slugRepo)
|
||||
const repoLabelsBySlug = useRepoLabelsBySlug(slugOwner, slugRepo, sourceSettings)
|
||||
const repoLabels = projectOrigin ? repoLabelsBySlug : repoLabelsByPath
|
||||
const repositoryLabelsUrl = useMemo(() => getGitHubRepositoryLabelsUrl(item.url), [item.url])
|
||||
const repoAssigneesByPath = useRepoAssignees(
|
||||
projectOrigin ? null : repoPath,
|
||||
projectOrigin ? null : repoId
|
||||
projectOrigin ? null : repoId,
|
||||
sourceSettings
|
||||
)
|
||||
const repoAssigneesBySlug = useRepoAssigneesBySlug(slugOwner, slugRepo, assignees)
|
||||
const repoAssigneesBySlug = useRepoAssigneesBySlug(slugOwner, slugRepo, assignees, sourceSettings)
|
||||
const repoAssignees = projectOrigin ? repoAssigneesBySlug : repoAssigneesByPath
|
||||
const hasAttachedWorkspace =
|
||||
attachedWorkspaceLabel !== null && attachedWorkspaceLabel !== undefined
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const COMPONENT_ROOT = __dirname
|
||||
|
||||
function componentSource(relativePath: string): string {
|
||||
return readFileSync(join(COMPONENT_ROOT, relativePath), 'utf8')
|
||||
}
|
||||
|
||||
function sourceBetween(source: string, startPattern: string, endPattern: string): string {
|
||||
const start = source.indexOf(startPattern)
|
||||
expect(start).toBeGreaterThanOrEqual(0)
|
||||
const end = source.indexOf(endPattern, start + startPattern.length)
|
||||
expect(end).toBeGreaterThan(start)
|
||||
return source.slice(start, end)
|
||||
}
|
||||
|
||||
describe('GitHubItemDialog source host boundaries', () => {
|
||||
it('routes reviewer metadata and reviewer mutations through the task source context', () => {
|
||||
const source = componentSource('GitHubItemDialog.tsx')
|
||||
const section = sourceBetween(source, 'function PRReviewersPanel', 'function isPRFileViewed')
|
||||
|
||||
expect(section).toContain('getTaskSourceRuntimeSettings(sourceContext)')
|
||||
expect(section).toContain('useRepoAssigneesBySlug(')
|
||||
expect(section).toContain('sourceSettings')
|
||||
expect(section).toContain('useRepoAssignees(')
|
||||
expect(section).toContain('sourceSettings')
|
||||
expect(section).toContain('getActiveRuntimeTarget(sourceSettings)')
|
||||
})
|
||||
|
||||
it('routes edit metadata through the same task source as issue mutations', () => {
|
||||
const source = componentSource('GitHubItemDialog.tsx')
|
||||
const section = sourceBetween(source, 'function GHEditSection', 'const hasAttachedWorkspace')
|
||||
|
||||
expect(section).toContain('getTaskSourceRuntimeSettings(sourceContext)')
|
||||
expect(section).toContain('useRepoLabels(')
|
||||
expect(section).toContain('useRepoLabelsBySlug(slugOwner, slugRepo, sourceSettings)')
|
||||
expect(section).toContain('useRepoAssignees(')
|
||||
expect(section).toContain('useRepoAssigneesBySlug(')
|
||||
expect(section).toContain('sourceSettings')
|
||||
})
|
||||
})
|
||||
@@ -31,6 +31,7 @@ type MetadataState<T> = {
|
||||
|
||||
type GitHubMetadataOptions = {
|
||||
runtimeEnvironmentId?: string | null
|
||||
activeRuntimeEnvironmentId?: string | null
|
||||
}
|
||||
|
||||
// ─── GitHub ────────────────────────────────────────────────
|
||||
@@ -54,7 +55,8 @@ export function useRepoLabels(
|
||||
if (!repoPath && !repoId) {
|
||||
return
|
||||
}
|
||||
const runtimeEnvironmentId = options?.runtimeEnvironmentId?.trim() || null
|
||||
const runtimeEnvironmentId =
|
||||
options?.runtimeEnvironmentId?.trim() || options?.activeRuntimeEnvironmentId?.trim() || null
|
||||
const repoSelector = repoId ?? repoPath ?? ''
|
||||
// Why: SSH/runtime metadata must not reuse host-path cache entries; the same
|
||||
// repo id may resolve through a different credential/runtime boundary.
|
||||
@@ -107,7 +109,7 @@ export function useRepoLabels(
|
||||
error: err instanceof Error ? err.message : 'Failed to load labels'
|
||||
}))
|
||||
})
|
||||
}, [repoPath, repoId, options?.runtimeEnvironmentId])
|
||||
}, [repoPath, repoId, options?.runtimeEnvironmentId, options?.activeRuntimeEnvironmentId])
|
||||
|
||||
return state
|
||||
}
|
||||
@@ -128,7 +130,8 @@ export function useRepoAssignees(
|
||||
if (!repoPath && !repoId) {
|
||||
return
|
||||
}
|
||||
const runtimeEnvironmentId = options?.runtimeEnvironmentId?.trim() || null
|
||||
const runtimeEnvironmentId =
|
||||
options?.runtimeEnvironmentId?.trim() || options?.activeRuntimeEnvironmentId?.trim() || null
|
||||
const repoSelector = repoId ?? repoPath ?? ''
|
||||
// Why: SSH/runtime metadata must not reuse host-path cache entries; the same
|
||||
// repo id may resolve through a different credential/runtime boundary.
|
||||
@@ -181,7 +184,7 @@ export function useRepoAssignees(
|
||||
error: err instanceof Error ? err.message : 'Failed to load assignees'
|
||||
}))
|
||||
})
|
||||
}, [repoPath, repoId, options?.runtimeEnvironmentId])
|
||||
}, [repoPath, repoId, options?.runtimeEnvironmentId, options?.activeRuntimeEnvironmentId])
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user