Skip to content

Commit 345b5f6

Browse files
JulianMaurinclaude
andcommitted
feat: restrict the queue and logs links on merge-queue batch PRs
Batch PRs get a reduced link set: the logs link is dropped (event logs are not supported for batches yet), and the queue link is shown only while the batch PR is open — once it closes the batch has drained out of the queue, so the deep link has nothing to open. An open batch PR keeps the batch_pr queue deep-link; other PRs are unchanged. A closed batch PR loses its Draft pill, so it is recognized by Mergify authorship instead; this also covers closed Mergify config/backport PRs, whose queue and logs links point nowhere useful either. The gating is centralized in appendBatchAwareLinks(), shared by the default/rich and sidebar rows. MRGFY-7878 Test plan: - npx jest (278 passing) — open batch PR (queue=batch_pr, no logs), closed batch PR (neither link), open non-batch PR (both links), closed human PR unaffected. - npx biome check — clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qcXJDQnrrJU4Xu6BxmQz8 Change-Id: I23862d88938847449dc1a1ed7b7ed86f9cef3811
1 parent 38d6f5f commit 345b5f6

2 files changed

Lines changed: 85 additions & 8 deletions

File tree

src/__tests__/queue.test.js

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -674,12 +674,14 @@ const {
674674
} = require("../queue");
675675
const { injectFixtureInDOM } = require("./utils");
676676

677-
// Minimal PR-page DOM: a draft status pill, the header-meta author link, and
678-
// the classic base-ref span. Any argument left undefined is omitted so a test
679-
// can exercise a single missing piece.
680-
function setPrDom({ draft, author, baseRef } = {}) {
677+
// Minimal PR-page DOM: a status pill (draft/closed), the header-meta author
678+
// link, and the classic base-ref span. Any argument left undefined is omitted
679+
// so a test can exercise a single missing piece.
680+
function setPrDom({ draft, closed, author, baseRef } = {}) {
681681
const parts = [];
682682
if (draft) parts.push('<span class="State" title="Status: Draft"></span>');
683+
else if (closed)
684+
parts.push('<span class="State" title="Status: Closed"></span>');
683685
if (author) {
684686
const href = author === "mergify" ? "/apps/mergify" : `/${author}`;
685687
parts.push(
@@ -882,3 +884,54 @@ describe("batch-PR row treatment", () => {
882884
}
883885
});
884886
});
887+
888+
describe("batch-PR informational links", () => {
889+
beforeEach(() => {
890+
document.body.innerHTML = "";
891+
window.history.replaceState({}, "", "/acme/widget/pull/42");
892+
});
893+
afterEach(() => {
894+
document.body.innerHTML = "";
895+
});
896+
897+
// The informational-link labels ("queue"/"logs"), excluding the Mergify
898+
// brand anchor (whose textContent carries the inlined logo SVG).
899+
function linkLabels(row) {
900+
return [...row.querySelectorAll("a")]
901+
.map((a) => a.textContent.trim())
902+
.filter((t) => t === "queue" || t === "logs");
903+
}
904+
function queueHrefOf(row) {
905+
return [...row.querySelectorAll("a")]
906+
.find((a) => a.textContent.trim() === "queue")
907+
?.getAttribute("href");
908+
}
909+
910+
test("open batch PR: queue link deep-links via batch_pr, no logs link", () => {
911+
setPrDom({ draft: true, author: "mergify", baseRef: "develop" });
912+
const row = buildMergifyRow();
913+
expect(linkLabels(row)).toEqual(["queue"]);
914+
expect(queueHrefOf(row)).toBe(
915+
"https://dashboard.mergify.com/queues/status?login=acme&repository=widget&branch=develop&batch_pr=42",
916+
);
917+
});
918+
919+
test("closed batch PR: no queue link and no logs link", () => {
920+
setPrDom({ closed: true, author: "mergify" });
921+
const row = buildMergifyRow();
922+
expect(linkLabels(row)).toEqual([]);
923+
});
924+
925+
test("open non-batch PR keeps both the queue and logs links", () => {
926+
setPrDom({ author: "octocat", baseRef: "develop" });
927+
const row = buildMergifyRow();
928+
expect(linkLabels(row)).toEqual(["queue", "logs"]);
929+
expect(queueHrefOf(row)).toContain("pull-request-number=42");
930+
});
931+
932+
test("closed human PR is unaffected — keeps both links", () => {
933+
setPrDom({ closed: true, author: "octocat" });
934+
const row = buildMergifyRow();
935+
expect(linkLabels(row)).toEqual(["queue", "logs"]);
936+
});
937+
});

src/queue.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,17 @@ export function isMergeQueueBatchPr() {
165165
return isPullRequestDraft() && isPullRequestAuthoredByMergify();
166166
}
167167

168+
// A batch PR page for the purpose of the informational links. While open a
169+
// batch PR is a draft (isMergeQueueBatchPr); once closed the draft pill is gone,
170+
// so fall back to Mergify authorship. This also matches closed Mergify
171+
// config/backport PRs, whose queue and logs links point nowhere useful anyway.
172+
export function isBatchPrContext() {
173+
return (
174+
isMergeQueueBatchPr() ||
175+
(!isPullRequestOpen() && isPullRequestAuthoredByMergify())
176+
);
177+
}
178+
168179
export function wasMergedByMergify() {
169180
const timelineItems = document.querySelectorAll(".TimelineItem");
170181
for (const item of timelineItems) {
@@ -521,6 +532,21 @@ function appendCommandButtons(container) {
521532
}
522533
}
523534

535+
// Append the queue and logs links via the caller's appendLink(href, text, svg),
536+
// honoring batch-PR restrictions: the queue link points at the batch peek
537+
// drawer while the batch PR is open and is dropped once it closes (the batch
538+
// has drained — nothing to open); the logs link is omitted on batch PRs
539+
// entirely (not supported for batches yet).
540+
function appendBatchAwareLinks(appendLink) {
541+
const batch = isBatchPrContext();
542+
if (!batch || isPullRequestOpen()) {
543+
appendLink(getMergeQueueLink(), "queue", QUEUE_ICON_SVG);
544+
}
545+
if (!batch) {
546+
appendLink(getEventLogLink(), "logs", LOGS_ICON_SVG);
547+
}
548+
}
549+
524550
export function getEventLogLink() {
525551
const data = getPullRequestData();
526552
return `https://dashboard.mergify.com/event-logs?login=${data.org}&repository=${data.repo}&pullRequestNumber=${data.pull}`;
@@ -652,8 +678,7 @@ function buildLegacySidebarRow() {
652678
link.appendChild(document.createTextNode(text));
653679
links.appendChild(link);
654680
}
655-
appendLink(getMergeQueueLink(), "queue", QUEUE_ICON_SVG);
656-
appendLink(getEventLogLink(), "logs", LOGS_ICON_SVG);
681+
appendBatchAwareLinks(appendLink);
657682
if (state === "merged") {
658683
const status = document.createElement("span");
659684
status.style.color = "var(--fgColor-muted, #7d8590)";
@@ -957,8 +982,7 @@ function buildBrandAndLinks() {
957982
link.appendChild(document.createTextNode(text));
958983
info.appendChild(link);
959984
}
960-
appendLink(getMergeQueueLink(), "queue", QUEUE_ICON_SVG);
961-
appendLink(getEventLogLink(), "logs", LOGS_ICON_SVG);
985+
appendBatchAwareLinks(appendLink);
962986

963987
const brand = document.createElement("a");
964988
brand.href = "https://dashboard.mergify.com";

0 commit comments

Comments
 (0)