mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 00:02:38 +00:00
* fix(checks): rank successful checks above skipped and neutral Checks were ordered with `skipped` (4) and `neutral` (3) ahead of `success` (5), so a PR with a long tail of skipped jobs pushed every passing check below the fold — you scroll past a wall of "Skipped" to find out whether anything actually ran. Rank the no-signal conclusions last (`success` 3, `neutral` 4, `skipped` 5) and pull the order out of its three duplicated copies (checks-panel-content, PullRequestPage, GitHubItemDialog) into `src/shared/pr-check-severity-order.ts`. Unknown conclusions now sink to the bottom instead of silently ranking as `neutral`. * fix(checks): look up check ranks through a Map, not an object literal An object-literal rank table resolves `constructor`, `toString`, and `__proto__` off Object.prototype, so those keys returned a function instead of falling through to UNKNOWN_CHECK_RANK — the comparator then subtracted functions, went NaN, and left the list in arbitrary order. Conclusions come from provider payloads, so keep the lookup on a Map and cover prototype property names in the test. * test(checks): cover provider-neutral ordering states * fix(checks): preserve actionable provider states * fix(checks): preserve unresolved provider rollups * fix(checks): keep unknown GitLab rollups neutral * fix: preserve neutral review check summaries * fix: complete provider-neutral check ordering remediation * fix: use provider-neutral mobile review status input * fix: hydrate GitLab mobile review status * fix: type mobile GitLab review hydration --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import type { HostedReviewInfo, HostedReviewQueueSummary } from './hosted-review'
|
|
import type { PRCheckDetail, PRComment } from './types'
|
|
import { derivePRCheckStatus } from './pr-check-status'
|
|
|
|
export type HostedReviewFromGitLabInfoArgs = {
|
|
review: HostedReviewInfo & { provider: 'gitlab' }
|
|
authorLogin?: string | null
|
|
authorIsBot?: boolean
|
|
comments?: PRComment[]
|
|
checks?: PRCheckDetail[]
|
|
lastViewedAt?: number
|
|
}
|
|
|
|
type GitLabIdentityParts = {
|
|
host: string
|
|
owner: string
|
|
repo: string
|
|
}
|
|
|
|
function parseGitLabIdentity(url: string): GitLabIdentityParts {
|
|
try {
|
|
const parsed = new URL(url)
|
|
const segments = parsed.pathname.split('/').filter(Boolean)
|
|
const markerIndex = segments.indexOf('-')
|
|
const projectSegments = markerIndex >= 0 ? segments.slice(0, markerIndex) : segments
|
|
if (projectSegments.length >= 2) {
|
|
return {
|
|
host: parsed.host || 'gitlab.com',
|
|
owner: projectSegments.slice(0, -1).join('/'),
|
|
repo: projectSegments.at(-1) ?? 'unknown'
|
|
}
|
|
}
|
|
return {
|
|
host: parsed.host || 'gitlab.com',
|
|
owner: projectSegments[0] ?? 'unknown',
|
|
repo: projectSegments[1] ?? 'unknown'
|
|
}
|
|
} catch {
|
|
// Why: queue badges should degrade gracefully for hand-entered/self-hosted URLs.
|
|
return { host: 'gitlab.com', owner: 'unknown', repo: 'unknown' }
|
|
}
|
|
}
|
|
|
|
function unresolvedThreadCount(comments?: PRComment[]): number | null {
|
|
if (comments === undefined) {
|
|
return null
|
|
}
|
|
const unresolved = new Set<string>()
|
|
for (const comment of comments) {
|
|
if (!comment.threadId || comment.isResolved !== false) {
|
|
continue
|
|
}
|
|
unresolved.add(comment.threadId)
|
|
}
|
|
return unresolved.size
|
|
}
|
|
|
|
function deriveChecksStatus(
|
|
reviewStatus: HostedReviewInfo['status'],
|
|
checks?: PRCheckDetail[]
|
|
): HostedReviewInfo['status'] {
|
|
if (!checks) {
|
|
return reviewStatus
|
|
}
|
|
return derivePRCheckStatus(checks)
|
|
}
|
|
|
|
export function hostedReviewSummaryFromGitLabInfo(
|
|
args: HostedReviewFromGitLabInfoArgs
|
|
): HostedReviewQueueSummary {
|
|
const identity = parseGitLabIdentity(args.review.url)
|
|
const unresolvedCount = unresolvedThreadCount(args.comments)
|
|
return {
|
|
identity: {
|
|
provider: 'gitlab',
|
|
host: identity.host,
|
|
owner: identity.owner,
|
|
repo: identity.repo,
|
|
number: args.review.number
|
|
},
|
|
title: args.review.title,
|
|
url: args.review.url,
|
|
state: args.review.state,
|
|
author: args.authorLogin ? { login: args.authorLogin, isBot: args.authorIsBot } : null,
|
|
updatedAt: args.review.updatedAt,
|
|
mergeable: args.review.mergeable,
|
|
...(args.review.mergeStateStatus !== undefined
|
|
? { mergeStateStatus: args.review.mergeStateStatus }
|
|
: {}),
|
|
checksStatus: deriveChecksStatus(args.review.status, args.checks),
|
|
reviewDecision:
|
|
args.review.reviewDecision === 'APPROVED'
|
|
? 'approved'
|
|
: args.review.reviewDecision === 'CHANGES_REQUESTED'
|
|
? 'changes_requested'
|
|
: args.review.reviewDecision === 'REVIEW_REQUIRED'
|
|
? 'review_required'
|
|
: undefined,
|
|
threadSummary:
|
|
unresolvedCount === null
|
|
? undefined
|
|
: {
|
|
unresolvedCount,
|
|
dataCompleteness: 'partial'
|
|
},
|
|
lastViewedAt: args.lastViewedAt,
|
|
draft: args.review.state === 'draft'
|
|
}
|
|
}
|