mirror of
https://github.com/stablyai/orca
synced 2026-04-21 14:17:16 +00:00
feat: display CI build status on worktree cards (#80)
Show CI check status icons (success/failure/pending) next to the branch name on worktree cards. Return 'neutral' instead of 'pending' when no CI checks exist to avoid showing a misleading spinner. Fix lint errors in client.ts (curly braces, array-type). Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1dff41c6d7
commit
598f7c17fa
2 changed files with 43 additions and 13 deletions
|
|
@ -7,7 +7,7 @@ const execFileAsync = promisify(execFile)
|
|||
// Concurrency limiter - max 4 parallel gh processes
|
||||
const MAX_CONCURRENT = 4
|
||||
let running = 0
|
||||
const queue: Array<() => void> = []
|
||||
const queue: (() => void)[] = []
|
||||
|
||||
function acquire(): Promise<void> {
|
||||
if (running < MAX_CONCURRENT) {
|
||||
|
|
@ -25,7 +25,9 @@ function acquire(): Promise<void> {
|
|||
function release(): void {
|
||||
running--
|
||||
const next = queue.shift()
|
||||
if (next) next()
|
||||
if (next) {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -104,13 +106,13 @@ export async function listIssues(repoPath: string, limit = 20): Promise<IssueInf
|
|||
encoding: 'utf-8'
|
||||
}
|
||||
)
|
||||
const data = JSON.parse(stdout) as Array<{
|
||||
const data = JSON.parse(stdout) as {
|
||||
number: number
|
||||
title: string
|
||||
state: string
|
||||
url: string
|
||||
labels: Array<{ name: string }>
|
||||
}>
|
||||
labels: { name: string }[]
|
||||
}[]
|
||||
return data.map((d) => ({
|
||||
number: d.number,
|
||||
title: d.title,
|
||||
|
|
@ -127,19 +129,25 @@ export async function listIssues(repoPath: string, limit = 20): Promise<IssueInf
|
|||
|
||||
function mapPRState(state: string): PRInfo['state'] {
|
||||
const s = state?.toUpperCase()
|
||||
if (s === 'MERGED') return 'merged'
|
||||
if (s === 'CLOSED') return 'closed'
|
||||
if (s === 'MERGED') {
|
||||
return 'merged'
|
||||
}
|
||||
if (s === 'CLOSED') {
|
||||
return 'closed'
|
||||
}
|
||||
// gh CLI returns isDraft separately, but state field is OPEN for drafts too
|
||||
return 'open'
|
||||
}
|
||||
|
||||
function deriveCheckStatus(rollup: unknown[] | null | undefined): CheckStatus {
|
||||
if (!rollup || !Array.isArray(rollup) || rollup.length === 0) return 'pending'
|
||||
if (!rollup || !Array.isArray(rollup) || rollup.length === 0) {
|
||||
return 'neutral'
|
||||
}
|
||||
|
||||
let hasFailure = false
|
||||
let hasPending = false
|
||||
|
||||
for (const check of rollup as Array<{ status?: string; conclusion?: string; state?: string }>) {
|
||||
for (const check of rollup as { status?: string; conclusion?: string; state?: string }[]) {
|
||||
const conclusion = check.conclusion?.toUpperCase()
|
||||
const status = check.status?.toUpperCase()
|
||||
const state = check.state?.toUpperCase()
|
||||
|
|
@ -162,7 +170,11 @@ function deriveCheckStatus(rollup: unknown[] | null | undefined): CheckStatus {
|
|||
}
|
||||
}
|
||||
|
||||
if (hasFailure) return 'failure'
|
||||
if (hasPending) return 'pending'
|
||||
if (hasFailure) {
|
||||
return 'failure'
|
||||
}
|
||||
if (hasPending) {
|
||||
return 'pending'
|
||||
}
|
||||
return 'success'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useAppStore } from '@/store'
|
|||
import { Badge } from '@/components/ui/badge'
|
||||
import { HoverCard, HoverCardTrigger, HoverCardContent } from '@/components/ui/hover-card'
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
||||
import { Bell, LoaderCircle, CircleDot } from 'lucide-react'
|
||||
import { Bell, LoaderCircle, CircleDot, CircleCheck, CircleX } from 'lucide-react'
|
||||
import RepoDotLabel from '@/components/repo/RepoDotLabel'
|
||||
import StatusIndicator from './StatusIndicator'
|
||||
import WorktreeContextMenu from './WorktreeContextMenu'
|
||||
|
|
@ -228,7 +228,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
{worktree.displayName}
|
||||
</div>
|
||||
|
||||
{/* Line 2: Repo badge + branch + primary badge */}
|
||||
{/* Line 2: Repo badge + branch + primary badge + CI status */}
|
||||
<div className="flex items-center gap-1 min-w-0">
|
||||
{repo && (
|
||||
<Badge
|
||||
|
|
@ -249,6 +249,24 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
main
|
||||
</Badge>
|
||||
)}
|
||||
{pr && pr.checksStatus !== 'neutral' && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="ml-auto shrink-0 inline-flex items-center">
|
||||
{pr.checksStatus === 'success' && (
|
||||
<CircleCheck className="size-3 text-emerald-400" />
|
||||
)}
|
||||
{pr.checksStatus === 'failure' && <CircleX className="size-3 text-red-400" />}
|
||||
{pr.checksStatus === 'pending' && (
|
||||
<LoaderCircle className="size-3 text-yellow-400 animate-spin" />
|
||||
)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
<span>CI checks {checksLabel(pr.checksStatus).toLowerCase()}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Meta section: Issue, Comment, PR */}
|
||||
|
|
|
|||
Loading…
Reference in a new issue