From 248e457a3bf073852123699f7f31db3a2cd963de Mon Sep 17 00:00:00 2001 From: Konv Suu Date: Tue, 14 Apr 2026 15:13:58 +0800 Subject: [PATCH 1/2] Refine PR merge and branch update permissions --- .../pulls/detail/pull-detail-activity.tsx | 15 ++++++++++++--- apps/dashboard/src/lib/github.functions.ts | 8 ++++++++ apps/dashboard/src/lib/github.types.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx b/apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx index 5518fce..dbadba9 100644 --- a/apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx +++ b/apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx @@ -422,6 +422,7 @@ function MergeStatusCard({ baseRefName, canUpdateBranch, canBypassProtections, + canMerge, } = status; const approvedReviews = reviews.filter((r) => r.state === "APPROVED"); @@ -489,6 +490,7 @@ function MergeStatusCard({ @@ -1091,11 +1096,15 @@ function MergeFooter({ - {isMergeBlocked && !bypassChecks && ( + {!canMerge ? ( +

+ You don't have permission to merge this pull request. +

+ ) : isMergeBlocked && !bypassChecks ? (

Merging is blocked — all required conditions have not been met.

- )} + ) : null} {isMergeBlocked && canBypassProtections && (
diff --git a/apps/dashboard/src/lib/github.functions.ts b/apps/dashboard/src/lib/github.functions.ts index 469cf8f..c52239e 100644 --- a/apps/dashboard/src/lib/github.functions.ts +++ b/apps/dashboard/src/lib/github.functions.ts @@ -3187,7 +3187,14 @@ async function computePullStatus( behindBy = null; } + const viewer = await getViewer(userContext ?? context); + const isViewerAuthor = pull.user?.login === viewer.login; const canUpdateBranch = + isViewerAuthor || + !permissions || + permissions.push === true || + permissions.admin === true; + const canMerge = !permissions || permissions.push === true || permissions.admin === true; const canBypassProtections = await getPullRequestBypassState({ branch: pull.base.ref, @@ -3224,6 +3231,7 @@ async function computePullStatus( baseRefName: pull.base.ref, canUpdateBranch, canBypassProtections, + canMerge, }; } diff --git a/apps/dashboard/src/lib/github.types.ts b/apps/dashboard/src/lib/github.types.ts index 4f99973..199d0a4 100644 --- a/apps/dashboard/src/lib/github.types.ts +++ b/apps/dashboard/src/lib/github.types.ts @@ -223,6 +223,7 @@ export type PullStatus = { baseRefName: string; canUpdateBranch: boolean; canBypassProtections: boolean; + canMerge: boolean; }; export type PullCommit = { From 8547d464d8598b2924c3d0c3edd10970470de930 Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Tue, 14 Apr 2026 15:26:50 -0400 Subject: [PATCH 2/2] show/hide bypass check based on met criterias --- .../pulls/detail/pull-detail-activity.tsx | 31 ++++++++----- .../pulls/detail/use-merge-bypass.ts | 43 +++++++++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 apps/dashboard/src/components/pulls/detail/use-merge-bypass.ts diff --git a/apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx b/apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx index dbadba9..75c7f1e 100644 --- a/apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx +++ b/apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx @@ -60,6 +60,7 @@ import { groupTimelineEvents, } from "#/components/details/grouped-label-event"; import { LabelPill } from "#/components/details/label-pill"; +import { useMergeBypass } from "#/components/pulls/detail/use-merge-bypass"; import { formatRelativeTime } from "#/lib/format-relative-time"; import { deleteBranch, @@ -438,6 +439,17 @@ function MergeStatusCard({ const hasConflicts = mergeableState === "dirty"; const isMergeBlocked = mergeableState === "blocked" || mergeable === false; + const bypass = useMergeBypass({ + isMergeBlocked, + canBypassProtections, + hasCheckFailures, + hasReviewIssue, + hasConflicts, + isBehind, + allChecksPassed, + totalChecks: checks.total, + }); + return (
{/* Reviews section */} @@ -489,8 +501,8 @@ function MergeStatusCard({ {/* Merge action footer */} ; owner: string; repo: string; pullNumber: number; @@ -1007,7 +1019,6 @@ function MergeFooter({ "squash", ); const [isMerging, setIsMerging] = useState(false); - const [bypassChecks, setBypassChecks] = useState(false); const queryClient = useQueryClient(); const currentStrategy = @@ -1023,7 +1034,7 @@ function MergeFooter({ repo, pullNumber, mergeMethod, - bypassProtections: bypassChecks, + bypassProtections: bypass.shouldBypass, }, }); if (result.ok) { @@ -1040,7 +1051,7 @@ function MergeFooter({ }; const isDisabled = - !canMerge || (isMergeBlocked && !bypassChecks) || isMerging; + !canMerge || (isMergeBlocked && !bypass.shouldBypass) || isMerging; return (
@@ -1100,18 +1111,18 @@ function MergeFooter({

You don't have permission to merge this pull request.

- ) : isMergeBlocked && !bypassChecks ? ( + ) : isMergeBlocked && !bypass.shouldBypass ? (

Merging is blocked — all required conditions have not been met.

) : null}
- {isMergeBlocked && canBypassProtections && ( + {bypass.showOption && (
setBypassChecks(checked === true)} + checked={bypass.checked} + onCheckedChange={(checked) => bypass.setChecked(checked === true)} />