mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 16:03:26 +00:00
Cover imported detached PR sidebar status
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -193,6 +193,57 @@ describe('WorktreeCard merged PR fallback display', () => {
|
||||
expect(markup).not.toContain('Detached HEAD @ merge-c')
|
||||
})
|
||||
|
||||
it('shows detached PRs from legacy path-scoped cache entries', async () => {
|
||||
hostedReviewCache = {
|
||||
'local::repo-1::__detached_head__:merge-commit': {
|
||||
data: null,
|
||||
fetchedAt: 200
|
||||
}
|
||||
}
|
||||
prCache = {
|
||||
'/repo::__detached_head__:merge-commit': {
|
||||
data: makePRInfo({
|
||||
headSha: 'pr-head-before-merge'
|
||||
}),
|
||||
fetchedAt: 100
|
||||
}
|
||||
}
|
||||
const { default: WorktreeCard } = await import('./WorktreeCard')
|
||||
|
||||
const markup = renderWorktreeCardMarkup(
|
||||
<WorktreeCard
|
||||
worktree={makeWorktree({ branch: '', linkedPR: null, head: 'merge-commit' })}
|
||||
repo={makeRepo()}
|
||||
isActive={false}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Linked PR #6340')
|
||||
expect(markup).not.toContain('Detached HEAD @ merge-c')
|
||||
})
|
||||
|
||||
it('hides detached HEAD badge when the parent list supplies PR status', async () => {
|
||||
const { default: WorktreeCard } = await import('./WorktreeCard')
|
||||
|
||||
const markup = renderWorktreeCardMarkup(
|
||||
<WorktreeCard
|
||||
worktree={makeWorktree({ branch: '', linkedPR: null, head: 'merge-commit' })}
|
||||
repo={makeRepo()}
|
||||
isActive={false}
|
||||
statusPrDisplay={{
|
||||
provider: 'github',
|
||||
number: 24,
|
||||
title: 'Detached PR from parent status',
|
||||
state: 'merged',
|
||||
status: 'failure',
|
||||
url: 'https://github.com/acme/orca/pull/24'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).not.toContain('Detached HEAD @ merge-c')
|
||||
})
|
||||
|
||||
it('suppresses cached merged PR after a newer hosted-review miss when the worktree head moved', async () => {
|
||||
prCache = {
|
||||
'repo-1::feature/local-branch': {
|
||||
|
||||
@@ -3,7 +3,11 @@ import React, { useEffect, useCallback, useState } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { getHostedReviewCacheKey } from '@/store/slices/hosted-review'
|
||||
import { issueCacheKey as getIssueCacheKey } from '@/store/slices/github'
|
||||
import { getGitHubPRCacheBranch, getGitHubPRCacheKey } from '@/store/slices/github-cache-key'
|
||||
import {
|
||||
getGitHubPRCacheBranch,
|
||||
getGitHubPRCacheKey,
|
||||
getLegacyGitHubPRCacheKey
|
||||
} from '@/store/slices/github-cache-key'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
||||
@@ -435,6 +439,15 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
true
|
||||
)
|
||||
: ''
|
||||
const canUseLegacyPRCache = repo !== undefined && !repo.connectionId && !repo.executionHostId
|
||||
const legacyRepoScopedPRCacheKey =
|
||||
canUseLegacyPRCache && prCacheBranch
|
||||
? getLegacyGitHubPRCacheKey(repo.path, repo.id, prCacheBranch)
|
||||
: ''
|
||||
const legacyPathScopedPRCacheKey =
|
||||
canUseLegacyPRCache && prCacheBranch
|
||||
? getLegacyGitHubPRCacheKey(repo.path, undefined, prCacheBranch)
|
||||
: ''
|
||||
const issueCacheKey =
|
||||
repo && worktree.linkedIssue
|
||||
? getIssueCacheKey(
|
||||
@@ -455,7 +468,12 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
const hostedReviewEntry = useAppStore((s) =>
|
||||
hostedReviewCacheKey ? s.hostedReviewCache[hostedReviewCacheKey] : undefined
|
||||
)
|
||||
const prCacheEntry = useAppStore((s) => (prCacheKey ? s.prCache?.[prCacheKey] : undefined))
|
||||
const prCacheEntry = useAppStore(
|
||||
(s) =>
|
||||
(prCacheKey ? s.prCache?.[prCacheKey] : undefined) ??
|
||||
(legacyRepoScopedPRCacheKey ? s.prCache?.[legacyRepoScopedPRCacheKey] : undefined) ??
|
||||
(legacyPathScopedPRCacheKey ? s.prCache?.[legacyPathScopedPRCacheKey] : undefined)
|
||||
)
|
||||
const issueEntry = useAppStore((s) => (issueCacheKey ? s.issueCache[issueCacheKey] : undefined))
|
||||
const linearIssueEntry = useAppStore((s) =>
|
||||
linearIssueCacheKey ? s.linearIssueCache[linearIssueCacheKey] : undefined
|
||||
@@ -1171,7 +1189,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
// Why: a detached merge commit can still have PR context; once shown, the PR
|
||||
// status is the higher-signal sidebar identity than the raw commit badge.
|
||||
const showDetachedHeadInMetaRow =
|
||||
!compactCards && !isFolder && detachedHeadDisplay !== null && hoverReview === null
|
||||
!compactCards && !isFolder && detachedHeadDisplay !== null && statusLaneReview === null
|
||||
const showBranch =
|
||||
!isFolder &&
|
||||
branch.length > 0 &&
|
||||
|
||||
@@ -145,6 +145,37 @@ describe('getFolderWorkspaceCardPrDisplay', () => {
|
||||
|
||||
expect(display).toMatchObject({ number: 4, status: 'success' })
|
||||
})
|
||||
|
||||
it('uses detached HEAD-scoped PR cache for attached worktrees', () => {
|
||||
const detached = makeWorktree({
|
||||
id: 'detached',
|
||||
branch: '',
|
||||
head: 'merge-commit',
|
||||
linkedPR: null
|
||||
})
|
||||
|
||||
const display = getFolderWorkspaceCardPrDisplay({
|
||||
folderWorkspaceId: 'folder-1',
|
||||
workspaceLineageByChildKey: { [detached.id]: makeWorkspaceLineage(detached) },
|
||||
worktreeLineageById: {},
|
||||
worktreeMap: new Map([[detached.id, detached]]),
|
||||
repoMap: new Map([[repo.id, repo]]),
|
||||
hostedReviewCache: {
|
||||
'local::repo-1::__detached_head__:merge-commit': { data: null }
|
||||
},
|
||||
prCache: {
|
||||
'repo-1::__detached_head__:merge-commit': {
|
||||
data: makePr(24, 'failure')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(display).toMatchObject({
|
||||
provider: 'github',
|
||||
number: 24,
|
||||
status: 'failure'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function makePr(number: number, checksStatus: CheckStatus) {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { AppState } from '@/store/types'
|
||||
import { getHostedReviewCacheKey } from '@/store/slices/hosted-review'
|
||||
import { getGitHubPRCacheKey, getLegacyGitHubPRCacheKey } from '@/store/slices/github-cache-key'
|
||||
import {
|
||||
getGitHubPRCacheBranch,
|
||||
getGitHubPRCacheKey,
|
||||
getLegacyGitHubPRCacheKey
|
||||
} from '@/store/slices/github-cache-key'
|
||||
import { branchName } from '@/lib/git-utils'
|
||||
import type { HostedReviewInfo } from '../../../../shared/hosted-review'
|
||||
import type {
|
||||
@@ -162,13 +166,14 @@ function getAttachedWorktreePrDisplay({
|
||||
}
|
||||
|
||||
const branch = branchName(worktree.branch).trim()
|
||||
if (!branch) {
|
||||
const cacheBranch = getGitHubPRCacheBranch(branch, worktree.head)
|
||||
if (!cacheBranch) {
|
||||
return getLinkedReviewDisplay(worktree)
|
||||
}
|
||||
|
||||
const hostedReviewCacheKey = getHostedReviewCacheKey(
|
||||
repo.path,
|
||||
branch,
|
||||
cacheBranch,
|
||||
settings,
|
||||
repo.id,
|
||||
repo.connectionId,
|
||||
@@ -193,7 +198,7 @@ function getAttachedWorktreePrDisplay({
|
||||
return hostedReviewDisplay
|
||||
}
|
||||
|
||||
const cachedGitHubPr = getCachedGitHubPr({ worktree, repo, branch, prCache, settings })
|
||||
const cachedGitHubPr = getCachedGitHubPr({ worktree, repo, cacheBranch, prCache, settings })
|
||||
if (cachedGitHubPr) {
|
||||
return {
|
||||
provider: 'github',
|
||||
@@ -222,24 +227,24 @@ function getLinkedReviewDisplay(worktree: Worktree): WorktreeCardPrDisplay | nul
|
||||
function getCachedGitHubPr({
|
||||
worktree,
|
||||
repo,
|
||||
branch,
|
||||
cacheBranch,
|
||||
prCache,
|
||||
settings
|
||||
}: {
|
||||
worktree: Worktree
|
||||
repo: Repo
|
||||
branch: string
|
||||
cacheBranch: string
|
||||
prCache: Record<string, unknown> | null
|
||||
settings?: AppState['settings']
|
||||
}): PRInfo | null {
|
||||
if (!prCache || worktree.linkedPR === null) {
|
||||
if (!prCache || (worktree.linkedPR === null && !cacheBranch.startsWith('__detached_head__:'))) {
|
||||
return null
|
||||
}
|
||||
|
||||
const cacheKey = getGitHubPRCacheKey(
|
||||
repo.path,
|
||||
repo.id,
|
||||
branch,
|
||||
cacheBranch,
|
||||
settings,
|
||||
repo.connectionId,
|
||||
repo.executionHostId,
|
||||
@@ -247,10 +252,10 @@ function getCachedGitHubPr({
|
||||
)
|
||||
const canUseLegacyPRCache = !repo.connectionId && !repo.executionHostId
|
||||
const legacyRepoScopedCacheKey = canUseLegacyPRCache
|
||||
? getLegacyGitHubPRCacheKey(repo.path, repo.id, branch)
|
||||
? getLegacyGitHubPRCacheKey(repo.path, repo.id, cacheBranch)
|
||||
: ''
|
||||
const legacyPathScopedCacheKey = canUseLegacyPRCache
|
||||
? getLegacyGitHubPRCacheKey(repo.path, undefined, branch)
|
||||
? getLegacyGitHubPRCacheKey(repo.path, undefined, cacheBranch)
|
||||
: ''
|
||||
const entry =
|
||||
(prCache[cacheKey] as PrCacheEntry | undefined) ??
|
||||
@@ -261,7 +266,13 @@ function getCachedGitHubPr({
|
||||
? (prCache[legacyPathScopedCacheKey] as PrCacheEntry | undefined)
|
||||
: undefined)
|
||||
const pr = entry?.data ?? null
|
||||
return pr?.number === worktree.linkedPR ? pr : null
|
||||
if (!pr) {
|
||||
return null
|
||||
}
|
||||
if (cacheBranch.startsWith('__detached_head__:')) {
|
||||
return pr
|
||||
}
|
||||
return pr.number === worktree.linkedPR ? pr : null
|
||||
}
|
||||
|
||||
function compareReviewDisplays(left: WorktreeCardPrDisplay, right: WorktreeCardPrDisplay): number {
|
||||
|
||||
Reference in New Issue
Block a user