Add `pr view <n>` and `pr checks <n>`, built on the truncation machinery
and review fetches from earlier slices.
`pr view` uses the three-call fetch pattern (PR + reviews in parallel, then
the head commit's combined status) so `checks`, `comment_count`, and
`review_count` are in the default output; `--comments`, `--reviews`, and
`--full` behave as on `issue view`, with `--reviews` exposing Gitea's
`official`/`stale` fields plus per-review inline comments.
`pr checks <n>` renders the checks summary line and the `{ name, conclusion }`
rows, or the scalar no-CI message when no statuses exist.
The state→conclusion mapping and summary live in a new `src/checks.ts`;
`commentRows` is extracted into `src/comment.ts` and shared with `issue view`.
102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
import type { PullReview, PullReviewComment } from "gitea-js";
|
|
import type { GiteaClient } from "./client.js";
|
|
import type { RepoContext } from "./context.js";
|
|
import { classifyHttpError } from "./errors.js";
|
|
|
|
/**
|
|
* gitea-axi's reviewDecision, the three-value result of {@link reviewDecision}.
|
|
* Deliberately narrower than gh-axi's four values: Gitea offers no non-admin way
|
|
* to tell whether review is formally required, so there is no `none` (ADR 0006).
|
|
*/
|
|
export type ReviewDecision = "approved" | "changes_requested" | "required";
|
|
|
|
// Gitea's ReviewStateType is typed as a bare string; these are the two states
|
|
// the decision turns on. A comment-only or pending review is neither, so it
|
|
// falls through to `required`.
|
|
const APPROVED = "APPROVED";
|
|
const REQUEST_CHANGES = "REQUEST_CHANGES";
|
|
|
|
/**
|
|
* Derive a PR's reviewDecision from its reviews, using the official-first
|
|
* fallback (ADR 0006): when any review is `official`, only official reviews
|
|
* count — preserving branch-protection semantics; otherwise every review counts,
|
|
* since unprotected repos never mark a review official and `approved` would
|
|
* otherwise be unreachable there.
|
|
*
|
|
* Within the considered set: a non-dismissed `REQUEST_CHANGES` wins as
|
|
* `changes_requested`; else a non-dismissed, non-stale `APPROVED` is `approved`;
|
|
* everything else — zero reviews, comment-only, stale or dismissed approvals —
|
|
* is `required`.
|
|
*/
|
|
export function reviewDecision(reviews: PullReview[]): ReviewDecision {
|
|
const hasOfficial = reviews.some((review) => review.official === true);
|
|
const considered = hasOfficial
|
|
? reviews.filter((review) => review.official === true)
|
|
: reviews;
|
|
|
|
if (considered.some((review) => review.state === REQUEST_CHANGES && !review.dismissed)) {
|
|
return "changes_requested";
|
|
}
|
|
if (
|
|
considered.some(
|
|
(review) => review.state === APPROVED && !review.stale && !review.dismissed,
|
|
)
|
|
) {
|
|
return "approved";
|
|
}
|
|
return "required";
|
|
}
|
|
|
|
/**
|
|
* Fetch a PR's reviews. One HTTP call per PR — `pr list` issues these in
|
|
* parallel across the rows it renders, and `pr view` fetches them alongside the
|
|
* PR itself (ADR 0006).
|
|
*/
|
|
export async function fetchReviews(
|
|
api: GiteaClient,
|
|
context: RepoContext,
|
|
number: number,
|
|
): Promise<PullReview[]> {
|
|
try {
|
|
const response = await api.repos.repoListPullReviews(context.owner, context.name, number);
|
|
return response.data ?? [];
|
|
} catch (error) {
|
|
throw classifyHttpError(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch a PR's reviews and reduce them to its reviewDecision. One HTTP call per
|
|
* PR — `pr list` issues these in parallel across the rows it renders (ADR 0006).
|
|
*/
|
|
export async function fetchReviewDecision(
|
|
api: GiteaClient,
|
|
context: RepoContext,
|
|
number: number,
|
|
): Promise<ReviewDecision> {
|
|
return reviewDecision(await fetchReviews(api, context, number));
|
|
}
|
|
|
|
/**
|
|
* Fetch a single review's inline (diff) comments. `pr view --reviews` issues one
|
|
* of these per review, in parallel, to attach each review's comments to it.
|
|
*/
|
|
export async function fetchReviewComments(
|
|
api: GiteaClient,
|
|
context: RepoContext,
|
|
number: number,
|
|
reviewId: number,
|
|
): Promise<PullReviewComment[]> {
|
|
try {
|
|
const response = await api.repos.repoGetPullReviewComments(
|
|
context.owner,
|
|
context.name,
|
|
number,
|
|
reviewId,
|
|
);
|
|
return response.data ?? [];
|
|
} catch (error) {
|
|
throw classifyHttpError(error);
|
|
}
|
|
}
|