Files
orca/src/shared/hosted-review-github.ts
T
KyuJoo HanandOrcaWin 6f3845baa4 fix(checks): rank successful checks above skipped and neutral (#11337)
* 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>
2026-07-30 13:29:41 -07:00

107 lines
3.3 KiB
TypeScript

import type { PRCheckDetail, PRComment, PRInfo } from './types'
import type { HostedReviewInfo, HostedReviewQueueSummary } from './hosted-review'
import { derivePRCheckStatus } from './pr-check-status'
export type HostedReviewFromGitHubPRInfoArgs = {
pr: PRInfo
owner: string
repo: string
host?: string
authorLogin?: string | null
authorIsBot?: boolean
requestedReviewerLogins?: string[] | null
comments?: PRComment[]
checks?: PRCheckDetail[]
lastViewedAt?: number
}
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(
prChecksStatus: PRInfo['checksStatus'],
checks?: PRCheckDetail[]
): PRInfo['checksStatus'] {
if (!checks) {
return prChecksStatus
}
return derivePRCheckStatus(checks)
}
export function hostedReviewSummaryFromGitHubPRInfo(
args: HostedReviewFromGitHubPRInfoArgs
): HostedReviewQueueSummary {
const unresolvedCount = unresolvedThreadCount(args.comments)
return {
identity: {
provider: 'github',
host: args.host ?? 'github.com',
owner: args.owner,
repo: args.repo,
number: args.pr.number
},
title: args.pr.title,
url: args.pr.url,
state: args.pr.state,
author: args.authorLogin ? { login: args.authorLogin, isBot: args.authorIsBot } : null,
updatedAt: args.pr.updatedAt,
mergeable: args.pr.mergeable,
...(args.pr.mergeStateStatus !== undefined
? { mergeStateStatus: args.pr.mergeStateStatus }
: {}),
checksStatus: deriveChecksStatus(args.pr.checksStatus, args.checks),
reviewDecision:
args.pr.reviewDecision === 'APPROVED'
? 'approved'
: args.pr.reviewDecision === 'CHANGES_REQUESTED'
? 'changes_requested'
: args.pr.reviewDecision === 'REVIEW_REQUIRED'
? 'review_required'
: undefined,
threadSummary:
unresolvedCount === null
? undefined
: {
unresolvedCount,
dataCompleteness: 'partial'
},
requestedReviewerLogins: args.requestedReviewerLogins,
lastViewedAt: args.lastViewedAt,
draft: args.pr.state === 'draft'
}
}
export function hostedReviewInfoFromGitHubPRInfo(pr: PRInfo): HostedReviewInfo {
return {
provider: 'github',
number: pr.number,
title: pr.title,
state: pr.state,
url: pr.url,
status: pr.checksStatus,
updatedAt: pr.updatedAt,
mergeable: pr.mergeable,
...(pr.reviewDecision !== undefined ? { reviewDecision: pr.reviewDecision } : {}),
...(pr.autoMergeEnabled !== undefined ? { autoMergeEnabled: pr.autoMergeEnabled } : {}),
...(pr.autoMergeAllowed !== undefined ? { autoMergeAllowed: pr.autoMergeAllowed } : {}),
...(pr.mergeQueueRequired !== undefined ? { mergeQueueRequired: pr.mergeQueueRequired } : {}),
...(pr.mergeStateStatus !== undefined ? { mergeStateStatus: pr.mergeStateStatus } : {}),
...(pr.headSha ? { headSha: pr.headSha } : {}),
...(pr.confirmedContainedHeadOid
? { confirmedContainedHeadOid: pr.confirmedContainedHeadOid }
: {}),
...(pr.conflictSummary ? { conflictSummary: pr.conflictSummary } : {})
}
}