swarm ui: a diff column on the pull requests card, and "approved" as its header
The pull requests card gets a diff column, the lines a PR adds and removes (`+120 −8`), sortable on the total. The controller takes the counts from the forge's pull request list, so no call is added. The column that said "has all approvals" is now headed "approved".
This commit is contained in:
parent
f633963b77
commit
944fd5b726
5 changed files with 46 additions and 3 deletions
|
|
@ -605,8 +605,9 @@ and base branch, and what a reviewer or merger filters on:
|
|||
decision, on the PR's current head, neither stale nor dismissed, and from a
|
||||
reviewer whose approval the rule accepts. A reviewer whose latest review asks for changes shows **changes
|
||||
requested**.
|
||||
- **has all approvals**: the approvals meet the rule. A PR into a branch with no
|
||||
protection rule has nothing to approve, so it has them all.
|
||||
- **approved**: the approvals meet the rule. A PR into a branch with no
|
||||
protection rule has nothing to approve, so it counts as approved.
|
||||
- **diff**: the lines the PR adds and removes (`+120 −8`), as the forge counts them.
|
||||
- **conflicts**: the forge can't merge the PR as it stands.
|
||||
- **protected base**: the base branch has a protection rule. A stacked PR targets a
|
||||
feature branch, so filtering this to **no** lists them.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import { TableSkeleton } from "../ui/table/TableSkeleton.js";
|
|||
import { ALL_REPOS, repoOptions } from "./issue-report-repo.js";
|
||||
import {
|
||||
approvalsText,
|
||||
diffSize,
|
||||
diffText,
|
||||
hasAllApprovals,
|
||||
prReportUrl,
|
||||
yesNo,
|
||||
|
|
@ -124,7 +126,7 @@ const COLUMNS: TableColumn<PrReportRow>[] = [
|
|||
},
|
||||
{
|
||||
key: "approved",
|
||||
header: "has all approvals",
|
||||
header: "approved",
|
||||
render: (r) =>
|
||||
hasAllApprovals(r) ? <Badge tone="positive" value="yes" /> : "no",
|
||||
sortBy: (r) => Number(hasAllApprovals(r)),
|
||||
|
|
@ -156,6 +158,12 @@ const COLUMNS: TableColumn<PrReportRow>[] = [
|
|||
filterValues: (r) => [r.ci ?? "none"],
|
||||
filterMode: "multiselect",
|
||||
},
|
||||
{
|
||||
key: "diff",
|
||||
header: "diff",
|
||||
render: (r) => diffText(r),
|
||||
sortBy: (r) => diffSize(r),
|
||||
},
|
||||
{
|
||||
key: "updated",
|
||||
header: "updated",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ export interface PrReportRow {
|
|||
head_branch: string;
|
||||
created_unix: number | null;
|
||||
updated_unix: number | null;
|
||||
/** Lines the PR adds and removes. */
|
||||
additions: number | null;
|
||||
deletions: number | null;
|
||||
/** The forge's draft flag. */
|
||||
wip: boolean;
|
||||
has_conflicts: boolean;
|
||||
|
|
@ -52,6 +55,18 @@ export function hasAllApprovals(row: PrReportRow): boolean {
|
|||
return row.approvals >= row.required_approvals;
|
||||
}
|
||||
|
||||
/** The diff size cell: lines added and removed, `+120 −8`. A count the forge
|
||||
* did not give reads as a dash. */
|
||||
export function diffText(row: PrReportRow): string {
|
||||
if (row.additions === null && row.deletions === null) return "—";
|
||||
return `+${row.additions ?? 0} −${row.deletions ?? 0}`;
|
||||
}
|
||||
|
||||
/** What the diff column sorts on: lines changed in all. */
|
||||
export function diffSize(row: PrReportRow): number {
|
||||
return (row.additions ?? 0) + (row.deletions ?? 0);
|
||||
}
|
||||
|
||||
/** The approvals cell: how many of the rule's approvals the PR has, or a dash
|
||||
* when its base branch asks for none. */
|
||||
export function approvalsText(row: PrReportRow): string {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import assert from "node:assert/strict";
|
|||
import { test } from "node:test";
|
||||
import {
|
||||
approvalsText,
|
||||
diffSize,
|
||||
diffText,
|
||||
hasAllApprovals,
|
||||
prReportUrl,
|
||||
yesNo,
|
||||
|
|
@ -52,6 +54,15 @@ test("an unprotected base, or a rule asking for none, shows only what it has", (
|
|||
);
|
||||
});
|
||||
|
||||
test("the diff reads as lines added and removed, and sorts on both", () => {
|
||||
assert.equal(diffText({ additions: 120, deletions: 8 }), "+120 −8");
|
||||
assert.equal(diffText({ additions: 0, deletions: 3 }), "+0 −3");
|
||||
assert.equal(diffText({ additions: 5, deletions: null }), "+5 −0");
|
||||
assert.equal(diffText({ additions: null, deletions: null }), "—");
|
||||
assert.equal(diffSize({ additions: 120, deletions: 8 }), 128);
|
||||
assert.equal(diffSize({ additions: null, deletions: null }), 0);
|
||||
});
|
||||
|
||||
test("a PR has all approvals when it meets the rule, and an unprotected base always does", () => {
|
||||
assert.equal(hasAllApprovals(row({})), false);
|
||||
assert.equal(hasAllApprovals(row({ approvals: 2 })), true);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ pub struct PrReportRow {
|
|||
pub head_branch: String,
|
||||
pub created_unix: Option<i64>,
|
||||
pub updated_unix: Option<i64>,
|
||||
/// Lines the PR adds and removes, as the forge counts them.
|
||||
pub additions: Option<i64>,
|
||||
pub deletions: Option<i64>,
|
||||
/// The forge's draft flag (setting a `WIP:` title is what sets it).
|
||||
pub wip: bool,
|
||||
/// The forge cannot merge the PR as it is: its head conflicts with the
|
||||
|
|
@ -200,6 +203,8 @@ fn row(
|
|||
head_branch: head.r#ref.clone().unwrap_or_default(),
|
||||
created_unix: pr.created_at.as_ref().map(|t| t.unix_timestamp()),
|
||||
updated_unix: pr.updated_at.as_ref().map(|t| t.unix_timestamp()),
|
||||
additions: pr.additions,
|
||||
deletions: pr.deletions,
|
||||
wip: pr.draft.unwrap_or(false),
|
||||
has_conflicts: pr.mergeable == Some(false),
|
||||
targets_protected_branch: protection.is_some(),
|
||||
|
|
@ -483,6 +488,8 @@ mod tests {
|
|||
"head": { "ref": "iris/thing", "sha": "h1" },
|
||||
"created_at": "2026-11-07T08:00:00Z",
|
||||
"updated_at": "2026-11-07T09:00:00Z",
|
||||
"additions": 120,
|
||||
"deletions": 8,
|
||||
}))
|
||||
.expect("a pull request");
|
||||
let status: CombinedStatus = serde_json::from_value(serde_json::json!({
|
||||
|
|
@ -504,6 +511,7 @@ mod tests {
|
|||
assert_eq!(r.author.as_deref(), Some("iris"));
|
||||
assert_eq!(r.requested_reviewers, ["argus"]);
|
||||
assert_eq!(r.updated_unix, Some(1_794_042_000));
|
||||
assert_eq!((r.additions, r.deletions), (Some(120), Some(8)));
|
||||
|
||||
// The same PR into a branch no rule names has nothing to approve.
|
||||
let r = row("o/r", &pr, &[], &reviews, &status).expect("a row");
|
||||
|
|
|
|||
Loading…
Reference in a new issue