mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 00:03:09 +00:00
Route GitHub task row actions by source context
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -218,6 +218,7 @@ Done means: reviewers and future agents can understand the vision, implementatio
|
||||
- [x] 2026-06-12: Polished sidebar host headers from card-like rows into quieter sidebar grouping rows while keeping explicit SSH/status/count text; verified with focused sidebar tests, web typecheck, and live Electron screenshot `notes/artifacts/host-context-sidebar-calm-host-headers.png`.
|
||||
- [x] 2026-06-12: Added a narrow `.gitignore` rule for `notes/artifacts/` so verification screenshots stay local while the HTML report can reference them; verified with `git check-ignore`.
|
||||
- [x] 2026-06-12: Added `taskSourceContext` and `workspaceRunContext` to retryable pending worktree creation requests, seeded them from the Composer's selected task source and project-host setup, and verified the pending store preserves both identities with focused worktree tests plus web typecheck.
|
||||
- [x] 2026-06-12: Threaded GitHub Tasks row-level mutations through `TaskSourceContext` for issue state, assignees, reviewer requests/removals, PR merge/auto-merge, and new issue creation/follow-up reads; verified with focused GitHub/TaskPage tests, focused oxlint, `pnpm run typecheck`, and `git diff --check`.
|
||||
- [x] 2026-06-12: Updated PR #5071 with the current verified scope, evidence docs, validation commands, explicit out-of-scope cloud/onboarding items, and remaining provider/version-skew risk.
|
||||
- [x] 2026-06-12: Added load-time backfill for legacy automations and automation runs that predate `runContext`/`sourceContext`, deriving explicit project host and provider source contexts from the saved repo id/path; verified with focused persistence migration coverage, automation service/precheck tests, daemon/settings merge-repair tests, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-12: Repaired the merge-repair lint gate without disabling max-lines by extracting automation run-target/usage modules and settings subcomponents, fixed stale source-context hook dependencies, and tightened GitHub PR checks cache ownership so `noCache` bypasses cache/in-flight requests while head-specific checks are reused during refresh events; verified targeted oxlint, focused automation/settings/GitHub cache tests (10 files / 157 tests), `github-checks-cache.test.ts`, `git diff --check`, and full `pnpm run typecheck`.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { TaskSourceContext } from '../../shared/task-source-context'
|
||||
|
||||
export type WorkItemArgs = {
|
||||
repoPath: string
|
||||
repoId?: string | null
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
type?: 'issue' | 'pr'
|
||||
}
|
||||
|
||||
+10
-9
@@ -319,7 +319,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi
|
||||
|
||||
ipcMain.handle(
|
||||
'gh:createIssue',
|
||||
(_event, args: { repoPath: string; title: string; body: string } & GitHubCreateIssueFields) => {
|
||||
(_event, args: RepoScopedArgs & { title: string; body: string } & GitHubCreateIssueFields) => {
|
||||
const repo = assertRegisteredRepo(args, store)
|
||||
const fields =
|
||||
args.labels !== undefined || args.assignees !== undefined
|
||||
@@ -727,6 +727,8 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi
|
||||
event,
|
||||
args: {
|
||||
repoPath: string
|
||||
repoId?: string | null
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
method?: 'merge' | 'squash' | 'rebase'
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
@@ -756,6 +758,8 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi
|
||||
event,
|
||||
args: {
|
||||
repoPath: string
|
||||
repoId?: string | null
|
||||
sourceContext?: TaskSourceContext | null
|
||||
prNumber: number
|
||||
enabled: boolean
|
||||
prRepo?: GitHubOwnerRepo | null
|
||||
@@ -783,7 +787,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi
|
||||
'gh:updatePRState',
|
||||
async (
|
||||
event,
|
||||
args: { repoPath: string; prNumber: number; updates: GitHubPullRequestStateUpdate }
|
||||
args: RepoScopedArgs & { prNumber: number; updates: GitHubPullRequestStateUpdate }
|
||||
) => {
|
||||
const repo = assertRegisteredRepo(args, store)
|
||||
if (
|
||||
@@ -813,7 +817,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi
|
||||
'gh:rerunPRChecks',
|
||||
async (
|
||||
_event,
|
||||
args: { repoPath: string; prNumber: number; headSha?: string; failedOnly?: boolean }
|
||||
args: RepoScopedArgs & { prNumber: number; headSha?: string; failedOnly?: boolean }
|
||||
) => {
|
||||
const repo = assertRegisteredRepo(args, store)
|
||||
if (
|
||||
@@ -834,7 +838,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi
|
||||
|
||||
ipcMain.handle(
|
||||
'gh:requestPRReviewers',
|
||||
async (event, args: { repoPath: string; prNumber: number; reviewers: string[] }) => {
|
||||
async (event, args: RepoScopedArgs & { prNumber: number; reviewers: string[] }) => {
|
||||
const repo = assertRegisteredRepo(args, store)
|
||||
const result = await requestPRReviewers(
|
||||
repo.path,
|
||||
@@ -854,7 +858,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi
|
||||
|
||||
ipcMain.handle(
|
||||
'gh:removePRReviewers',
|
||||
async (event, args: { repoPath: string; prNumber: number; reviewers: string[] }) => {
|
||||
async (event, args: RepoScopedArgs & { prNumber: number; reviewers: string[] }) => {
|
||||
const repo = assertRegisteredRepo(args, store)
|
||||
const result = await removePRReviewers(
|
||||
repo.path,
|
||||
@@ -874,10 +878,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi
|
||||
|
||||
ipcMain.handle(
|
||||
'gh:updateIssue',
|
||||
async (
|
||||
event,
|
||||
args: { repoPath: string; repoId?: string; number: number; updates: GitHubIssueUpdate }
|
||||
) => {
|
||||
async (event, args: RepoScopedArgs & { number: number; updates: GitHubIssueUpdate }) => {
|
||||
const repo = assertRegisteredRepo(args, store)
|
||||
if (typeof args.number !== 'number' || !Number.isInteger(args.number) || args.number < 1) {
|
||||
return { ok: false, error: 'Invalid issue number' }
|
||||
|
||||
@@ -1142,6 +1142,7 @@ export type PreloadApi = {
|
||||
workItem: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
type?: 'issue' | 'pr'
|
||||
}) => Promise<Omit<GitHubWorkItem, 'repoId'> | null>
|
||||
@@ -1177,6 +1178,7 @@ export type PreloadApi = {
|
||||
createIssue: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
title: string
|
||||
body: string
|
||||
labels?: string[]
|
||||
|
||||
@@ -950,6 +950,7 @@ const api = {
|
||||
workItem: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
number: number
|
||||
type?: 'issue' | 'pr'
|
||||
}): Promise<unknown> => ipcRenderer.invoke('gh:workItem', args),
|
||||
@@ -989,6 +990,7 @@ const api = {
|
||||
createIssue: (args: {
|
||||
repoPath: string
|
||||
repoId?: string
|
||||
sourceContext?: TaskSourceContext | null
|
||||
title: string
|
||||
body: string
|
||||
labels?: string[]
|
||||
|
||||
@@ -148,6 +148,7 @@ import { getRepoExecutionHostId } from '../../../shared/execution-host'
|
||||
import { projectHostSetupProjectionFromRepos } from '../../../shared/project-host-setup-projection'
|
||||
import {
|
||||
getTaskSourceCacheScope,
|
||||
getTaskSourceRuntimeSettings,
|
||||
normalizeTaskSourceContext,
|
||||
type TaskSourceContext
|
||||
} from '../../../shared/task-source-context'
|
||||
@@ -897,15 +898,27 @@ function buildJiraCreateCustomFields(
|
||||
|
||||
function GHStatusCell({
|
||||
item,
|
||||
repo
|
||||
repo,
|
||||
sourceContext
|
||||
}: {
|
||||
item: GitHubWorkItem
|
||||
repo: Repo | null
|
||||
sourceContext?: TaskSourceContext | null
|
||||
}): React.JSX.Element {
|
||||
const patchWorkItem = useAppStore((s) => s.patchWorkItem)
|
||||
const repoOwnerSettings = useAppStore(
|
||||
useShallow((s) => getSettingsForRepoRuntimeOwner(s, repo?.id ?? null))
|
||||
)
|
||||
const sourceSettings = useMemo(
|
||||
() =>
|
||||
sourceContext?.provider === 'github'
|
||||
? ({
|
||||
...repoOwnerSettings,
|
||||
...getTaskSourceRuntimeSettings(sourceContext)
|
||||
} as typeof repoOwnerSettings)
|
||||
: repoOwnerSettings,
|
||||
[repoOwnerSettings, sourceContext]
|
||||
)
|
||||
const [statusStateDraft, setStatusStateDraft] = useState(() =>
|
||||
createTaskPageGitHubStatusStateDraft(item)
|
||||
)
|
||||
@@ -937,18 +950,21 @@ function GHStatusCell({
|
||||
const reqId = reqRef.current
|
||||
updateLocalState(newState)
|
||||
patchWorkItem(item.id, { state: newState }, item.repoId)
|
||||
const target = getActiveRuntimeTarget(repoOwnerSettings)
|
||||
const target = getActiveRuntimeTarget(sourceSettings)
|
||||
const runtimeRepoId =
|
||||
sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id
|
||||
const updatePromise =
|
||||
target.kind === 'environment'
|
||||
? callRuntimeRpc<{ ok?: boolean; error?: string }>(
|
||||
target,
|
||||
'github.updateIssue',
|
||||
{ repo: repo.id, number: item.number, updates: { state: newState } },
|
||||
{ repo: runtimeRepoId, number: item.number, updates: { state: newState } },
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: window.api.gh.updateIssue({
|
||||
repoPath: repo.path,
|
||||
repoId: repo.id,
|
||||
sourceContext,
|
||||
number: item.number,
|
||||
updates: { state: newState }
|
||||
})
|
||||
@@ -982,7 +998,7 @@ function GHStatusCell({
|
||||
toast.error(translate('auto.components.TaskPage.1c893195ac', 'Failed to update state'))
|
||||
})
|
||||
},
|
||||
[item, localState, patchWorkItem, repo, repoOwnerSettings, updateLocalState]
|
||||
[item, localState, patchWorkItem, repo, sourceContext, sourceSettings, updateLocalState]
|
||||
)
|
||||
|
||||
if (item.type !== 'issue' || !repo) {
|
||||
@@ -1331,15 +1347,27 @@ function GitHubIssueAssigneeSelector({
|
||||
|
||||
function GHAssigneesCell({
|
||||
item,
|
||||
repo
|
||||
repo,
|
||||
sourceContext
|
||||
}: {
|
||||
item: GitHubWorkItem
|
||||
repo: Repo | null
|
||||
sourceContext?: TaskSourceContext | null
|
||||
}): React.JSX.Element {
|
||||
const patchWorkItem = useAppStore((s) => s.patchWorkItem)
|
||||
const repoOwnerSettings = useAppStore(
|
||||
useShallow((s) => getSettingsForRepoRuntimeOwner(s, repo?.id ?? null))
|
||||
)
|
||||
const sourceSettings = useMemo(
|
||||
() =>
|
||||
sourceContext?.provider === 'github'
|
||||
? ({
|
||||
...repoOwnerSettings,
|
||||
...getTaskSourceRuntimeSettings(sourceContext)
|
||||
} as typeof repoOwnerSettings)
|
||||
: repoOwnerSettings,
|
||||
[repoOwnerSettings, sourceContext]
|
||||
)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [pendingLogin, setPendingLogin] = useState<string | null>(null)
|
||||
const assignees = useMemo(() => item.assignees ?? [], [item.assignees])
|
||||
@@ -1358,7 +1386,7 @@ function GHAssigneesCell({
|
||||
open ? owner : null,
|
||||
open ? repoName : null,
|
||||
seedLogins,
|
||||
repoOwnerSettings
|
||||
sourceSettings
|
||||
)
|
||||
|
||||
const toggleAssignee = useCallback(
|
||||
@@ -1377,7 +1405,7 @@ function GHAssigneesCell({
|
||||
|
||||
try {
|
||||
const updates = isOn ? { removeAssignees: [user.login] } : { addAssignees: [user.login] }
|
||||
const target = getActiveRuntimeTarget(repoOwnerSettings)
|
||||
const target = getActiveRuntimeTarget(sourceSettings)
|
||||
if (owner && repoName) {
|
||||
const args = {
|
||||
owner,
|
||||
@@ -1398,17 +1426,20 @@ function GHAssigneesCell({
|
||||
throw new Error(res.error.message)
|
||||
}
|
||||
} else if (repo) {
|
||||
const runtimeRepoId =
|
||||
sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id
|
||||
const res =
|
||||
target.kind === 'environment'
|
||||
? await callRuntimeRpc<{ ok?: boolean; error?: string }>(
|
||||
target,
|
||||
'github.updateIssue',
|
||||
{ repo: repo.id, number: item.number, updates },
|
||||
{ repo: runtimeRepoId, number: item.number, updates },
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: await window.api.gh.updateIssue({
|
||||
repoPath: repo.path,
|
||||
repoId: repo.id,
|
||||
sourceContext,
|
||||
number: item.number,
|
||||
updates
|
||||
})
|
||||
@@ -1441,7 +1472,8 @@ function GHAssigneesCell({
|
||||
pendingLogin,
|
||||
repo,
|
||||
repoName,
|
||||
repoOwnerSettings
|
||||
sourceContext,
|
||||
sourceSettings
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1646,10 +1678,12 @@ function buildRequestedReviewUsers(
|
||||
|
||||
function PRReviewCell({
|
||||
item,
|
||||
repo
|
||||
repo,
|
||||
sourceContext
|
||||
}: {
|
||||
item: GitHubWorkItem
|
||||
repo: Repo | null
|
||||
sourceContext?: TaskSourceContext | null
|
||||
}): React.JSX.Element {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [reviewerInput, setReviewerInput] = useState('')
|
||||
@@ -1664,10 +1698,19 @@ function PRReviewCell({
|
||||
const patchWorkItem = useAppStore((s) => s.patchWorkItem)
|
||||
const [activeReviewerCursor, setActiveReviewerCursor] = useState({ resetKey: '', index: 0 })
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const settings = useAppStore((s) => s.settings)
|
||||
const repoOwnerSettings = useAppStore(
|
||||
useShallow((s) => getSettingsForRepoRuntimeOwner(s, repo?.id ?? 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)
|
||||
|
||||
@@ -1734,7 +1777,7 @@ function PRReviewCell({
|
||||
open && reviewSlug ? reviewSlug.owner : null,
|
||||
open && reviewSlug ? reviewSlug.repo : null,
|
||||
reviewerSeedUsers.map((user) => user.login),
|
||||
settings
|
||||
sourceSettings
|
||||
)
|
||||
|
||||
const authorLogin = item.author?.toLowerCase() ?? null
|
||||
@@ -1862,18 +1905,21 @@ function PRReviewCell({
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const target = getActiveRuntimeTarget(repoOwnerSettings)
|
||||
const target = getActiveRuntimeTarget(sourceSettings)
|
||||
const runtimeRepoId =
|
||||
sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id
|
||||
const result =
|
||||
target.kind === 'environment'
|
||||
? await callRuntimeRpc<{ ok: boolean; error?: string }>(
|
||||
target,
|
||||
'github.requestPRReviewers',
|
||||
{ repo: repo.id, prNumber: item.number, reviewers: logins },
|
||||
{ repo: runtimeRepoId, prNumber: item.number, reviewers: logins },
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: await window.api.gh.requestPRReviewers({
|
||||
repoPath: repo.path,
|
||||
repoId: repo.id,
|
||||
sourceContext,
|
||||
prNumber: item.number,
|
||||
reviewers: logins
|
||||
})
|
||||
@@ -1911,18 +1957,21 @@ function PRReviewCell({
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const target = getActiveRuntimeTarget(repoOwnerSettings)
|
||||
const target = getActiveRuntimeTarget(sourceSettings)
|
||||
const runtimeRepoId =
|
||||
sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id
|
||||
const result =
|
||||
target.kind === 'environment'
|
||||
? await callRuntimeRpc<{ ok: boolean; error?: string }>(
|
||||
target,
|
||||
'github.removePRReviewers',
|
||||
{ repo: repo.id, prNumber: item.number, reviewers: logins },
|
||||
{ repo: runtimeRepoId, prNumber: item.number, reviewers: logins },
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: await window.api.gh.removePRReviewers({
|
||||
repoPath: repo.path,
|
||||
repoId: repo.id,
|
||||
sourceContext,
|
||||
prNumber: item.number,
|
||||
reviewers: logins
|
||||
})
|
||||
@@ -2226,14 +2275,29 @@ function PRChecksCell({
|
||||
function PRMergeCell({
|
||||
item,
|
||||
repo,
|
||||
sourceContext,
|
||||
onRefresh
|
||||
}: {
|
||||
item: GitHubWorkItem
|
||||
repo: Repo | null
|
||||
sourceContext?: TaskSourceContext | null
|
||||
onRefresh: () => void
|
||||
}): React.JSX.Element {
|
||||
const [merging, setMerging] = useState(false)
|
||||
const confirm = useConfirmationDialog()
|
||||
const repoOwnerSettings = useAppStore(
|
||||
useShallow((s) => getSettingsForRepoRuntimeOwner(s, repo?.id ?? null))
|
||||
)
|
||||
const sourceSettings = useMemo(
|
||||
() =>
|
||||
sourceContext?.provider === 'github'
|
||||
? ({
|
||||
...repoOwnerSettings,
|
||||
...getTaskSourceRuntimeSettings(sourceContext)
|
||||
} as typeof repoOwnerSettings)
|
||||
: repoOwnerSettings,
|
||||
[repoOwnerSettings, sourceContext]
|
||||
)
|
||||
if (item.type !== 'pr') {
|
||||
return (
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
@@ -2266,13 +2330,30 @@ function PRMergeCell({
|
||||
}
|
||||
setMerging(true)
|
||||
try {
|
||||
const result = await window.api.gh.mergePR({
|
||||
repoPath: repo.path,
|
||||
repoId: repo.id,
|
||||
prNumber: item.number,
|
||||
method,
|
||||
prRepo: item.prRepo ?? null
|
||||
})
|
||||
const target = getActiveRuntimeTarget(sourceSettings)
|
||||
const runtimeRepoId =
|
||||
sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id
|
||||
const result =
|
||||
target.kind === 'environment'
|
||||
? await callRuntimeRpc<{ ok: boolean; error?: string }>(
|
||||
target,
|
||||
'github.mergePR',
|
||||
{
|
||||
repo: runtimeRepoId,
|
||||
prNumber: item.number,
|
||||
method,
|
||||
prRepo: item.prRepo ?? null
|
||||
},
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: await window.api.gh.mergePR({
|
||||
repoPath: repo.path,
|
||||
repoId: repo.id,
|
||||
sourceContext,
|
||||
prNumber: item.number,
|
||||
method,
|
||||
prRepo: item.prRepo ?? null
|
||||
})
|
||||
if (result.ok) {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(translate('auto.components.TaskPage.a161925adc', 'Pull request merged'))
|
||||
@@ -2294,13 +2375,30 @@ function PRMergeCell({
|
||||
const enabled = mergePresentation.autoMergeAction.kind === 'enable'
|
||||
setMerging(true)
|
||||
try {
|
||||
const result = await window.api.gh.setPRAutoMerge({
|
||||
repoPath: repo.path,
|
||||
repoId: repo.id,
|
||||
prNumber: item.number,
|
||||
enabled,
|
||||
prRepo: item.prRepo ?? null
|
||||
})
|
||||
const target = getActiveRuntimeTarget(sourceSettings)
|
||||
const runtimeRepoId =
|
||||
sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id
|
||||
const result =
|
||||
target.kind === 'environment'
|
||||
? await callRuntimeRpc<{ ok: boolean; error?: string }>(
|
||||
target,
|
||||
'github.setPRAutoMerge',
|
||||
{
|
||||
repo: runtimeRepoId,
|
||||
prNumber: item.number,
|
||||
enabled,
|
||||
prRepo: item.prRepo ?? null
|
||||
},
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: await window.api.gh.setPRAutoMerge({
|
||||
repoPath: repo.path,
|
||||
repoId: repo.id,
|
||||
sourceContext,
|
||||
prNumber: item.number,
|
||||
enabled,
|
||||
prRepo: item.prRepo ?? null
|
||||
})
|
||||
if (result.ok) {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(
|
||||
@@ -3303,20 +3401,31 @@ export default function TaskPage(): React.JSX.Element {
|
||||
() => selectedRepos.find((r) => r.id === newIssueRepoId) ?? selectedRepos[0] ?? null,
|
||||
[selectedRepos, newIssueRepoId]
|
||||
)
|
||||
const newIssueSourceContext = useMemo(
|
||||
() => getTaskPageRepoSourceContext(newIssueTargetRepo, 'github'),
|
||||
[newIssueTargetRepo]
|
||||
)
|
||||
const newIssueRuntimeTarget = useMemo(() => {
|
||||
if (!newIssueTargetRepo?.id) {
|
||||
return null
|
||||
}
|
||||
const targetSettings = getSettingsForRepoRuntimeOwner(
|
||||
const repoOwnerSettings = getSettingsForRepoRuntimeOwner(
|
||||
{ repos: [newIssueTargetRepo], settings },
|
||||
newIssueTargetRepo.id
|
||||
)
|
||||
const targetSettings =
|
||||
newIssueSourceContext?.provider === 'github'
|
||||
? {
|
||||
...repoOwnerSettings,
|
||||
...getTaskSourceRuntimeSettings(newIssueSourceContext)
|
||||
}
|
||||
: repoOwnerSettings
|
||||
const target = getActiveRuntimeTarget(targetSettings)
|
||||
if (target.kind !== 'environment') {
|
||||
return null
|
||||
}
|
||||
return repos.some((repo) => repo.id === newIssueTargetRepo.id) ? target : null
|
||||
}, [newIssueTargetRepo, repos, settings])
|
||||
}, [newIssueSourceContext, newIssueTargetRepo, repos, settings])
|
||||
const newIssueRepoLabels = useRepoLabels(
|
||||
newIssueOpen ? (newIssueTargetRepo?.path ?? null) : null,
|
||||
newIssueOpen ? (newIssueTargetRepo?.id ?? null) : null,
|
||||
@@ -5756,7 +5865,10 @@ export default function TaskPage(): React.JSX.Element {
|
||||
newIssueRuntimeTarget,
|
||||
'github.createIssue',
|
||||
{
|
||||
repo: newIssueTargetRepo.id,
|
||||
repo:
|
||||
newIssueSourceContext?.provider === 'github'
|
||||
? (newIssueSourceContext.repoId ?? newIssueTargetRepo.id)
|
||||
: newIssueTargetRepo.id,
|
||||
title,
|
||||
body: newIssueBody,
|
||||
labels: newIssueLabels,
|
||||
@@ -5767,6 +5879,7 @@ export default function TaskPage(): React.JSX.Element {
|
||||
: await window.api.gh.createIssue({
|
||||
repoPath: newIssueTargetRepo.path,
|
||||
repoId: newIssueTargetRepo.id,
|
||||
sourceContext: newIssueSourceContext,
|
||||
title,
|
||||
body: newIssueBody,
|
||||
labels: newIssueLabels,
|
||||
@@ -5822,12 +5935,20 @@ export default function TaskPage(): React.JSX.Element {
|
||||
? callRuntimeRpc<Awaited<ReturnType<typeof window.api.gh.workItem>>>(
|
||||
newIssueRuntimeTarget,
|
||||
'github.workItem',
|
||||
{ repo: newIssueTargetRepo.id, number: result.number, type: 'issue' },
|
||||
{
|
||||
repo:
|
||||
newIssueSourceContext?.provider === 'github'
|
||||
? (newIssueSourceContext.repoId ?? newIssueTargetRepo.id)
|
||||
: newIssueTargetRepo.id,
|
||||
number: result.number,
|
||||
type: 'issue'
|
||||
},
|
||||
{ timeoutMs: 30_000 }
|
||||
)
|
||||
: window.api.gh.workItem({
|
||||
repoPath: newIssueTargetRepo.path,
|
||||
repoId: newIssueTargetRepo.id,
|
||||
sourceContext: newIssueSourceContext,
|
||||
number: result.number,
|
||||
type: 'issue'
|
||||
})
|
||||
@@ -5851,6 +5972,7 @@ export default function TaskPage(): React.JSX.Element {
|
||||
newIssueAssignees,
|
||||
newIssueLabels,
|
||||
newIssueRuntimeTarget,
|
||||
newIssueSourceContext,
|
||||
newIssueSubmitting,
|
||||
newIssueTargetRepo,
|
||||
newIssueTitle,
|
||||
@@ -8344,14 +8466,22 @@ export default function TaskPage(): React.JSX.Element {
|
||||
|
||||
{!showPRManagementColumns ? (
|
||||
<div className="min-w-0 flex items-center text-xs text-muted-foreground">
|
||||
<GHAssigneesCell item={item} repo={itemRepo ?? null} />
|
||||
<GHAssigneesCell
|
||||
item={item}
|
||||
repo={itemRepo ?? null}
|
||||
sourceContext={getTaskPageRepoSourceContext(itemRepo, 'github')}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showPRManagementColumns ? (
|
||||
<>
|
||||
<div className="flex min-w-0 items-center">
|
||||
<PRReviewCell item={item} repo={itemRepo ?? null} />
|
||||
<PRReviewCell
|
||||
item={item}
|
||||
repo={itemRepo ?? null}
|
||||
sourceContext={getTaskPageRepoSourceContext(itemRepo, 'github')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 items-center">
|
||||
@@ -8366,13 +8496,18 @@ export default function TaskPage(): React.JSX.Element {
|
||||
<PRMergeCell
|
||||
item={item}
|
||||
repo={itemRepo ?? null}
|
||||
sourceContext={getTaskPageRepoSourceContext(itemRepo, 'github')}
|
||||
onRefresh={() => setTaskRefreshNonce((current) => current + 1)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<GHStatusCell item={item} repo={itemRepo ?? null} />
|
||||
<GHStatusCell
|
||||
item={item}
|
||||
repo={itemRepo ?? null}
|
||||
sourceContext={getTaskPageRepoSourceContext(itemRepo, 'github')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -77,6 +77,26 @@ describe('feature interaction writer boundaries', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('threads GitHub task source context through inline task mutations', () => {
|
||||
const source = componentSource('TaskPage.tsx')
|
||||
const sections = [
|
||||
sourceBetween(source, 'function GHStatusCell', 'function GitHubAssigneeAvatar'),
|
||||
sourceBetween(source, 'function GHAssigneesCell', 'const triggerContent ='),
|
||||
sourceBetween(source, 'function PRReviewCell', 'function PRChecksCell'),
|
||||
componentBodyBeforeRender(source, 'PRMergeCell'),
|
||||
sourceBetween(source, 'const handleCreateNewIssue', 'const handleCreateNewLinearProject')
|
||||
]
|
||||
|
||||
for (const section of sections) {
|
||||
expect(section).toContain('sourceContext')
|
||||
}
|
||||
const rowRenderStart = source.indexOf('filteredWorkItems.map((item) => {')
|
||||
expect(rowRenderStart).toBeGreaterThanOrEqual(0)
|
||||
expect(source.slice(rowRenderStart, rowRenderStart + 12_000)).toContain(
|
||||
'sourceContext={getTaskPageRepoSourceContext(itemRepo,'
|
||||
)
|
||||
})
|
||||
|
||||
it('suppresses Tasks surface telemetry for in-page provider switches and detail opens', () => {
|
||||
const source = componentSource('TaskPage.tsx')
|
||||
const suppression = 'recordTasksInteraction: false'
|
||||
@@ -91,9 +111,9 @@ describe('feature interaction writer boundaries', () => {
|
||||
sourceBetween(source, 'taskSourceManuallyChangedRef.current = true', 'void updateSettings')
|
||||
]
|
||||
|
||||
expect(githubDetailSection).toContain("recordFeatureInteraction('github-tasks')")
|
||||
expect(githubDetailSection).toContain('setDialogWorkItem(item, initialTab)')
|
||||
expect(githubDetailSection).not.toContain('openTaskPage')
|
||||
expect(githubDetailSection).toContain('openGitHubSourceContext')
|
||||
expect(githubDetailSection).toContain('openTaskPage')
|
||||
expect(githubDetailSection).toContain(suppression)
|
||||
|
||||
for (const section of inPageNavigationSections) {
|
||||
expect(section).toContain(suppression)
|
||||
|
||||
Reference in New Issue
Block a user