--- import BaseLayout from "@/layouts/BaseLayout.astro"; import { getDatabase, schema } from "@/db"; import { eq, desc, and, asc } from "drizzle-orm"; import { GitMerge, Clock, CheckCircle2, XCircle, Loader2, Pause, Play, Layers, AlertCircle, Trash2, ArrowUp, ArrowDown, RefreshCw, } from "lucide-react"; const user = Astro.locals.user; if (!user) { return Astro.redirect("/login"); } const db = getDatabase(); const repoFilter = Astro.url.searchParams.get("repo") || ""; // Get all repositories user has access to let userRepos: { id: string; name: string; fullName?: string }[] = []; try { const repos = await db.query.repositories.findMany({ where: eq(schema.repositories.ownerId, user.id), columns: { id: true, name: true }, with: { owner: { columns: { username: true } } }, }); userRepos = repos.map((r) => ({ id: r.id, name: r.name, fullName: `${r.owner.username}/${r.name}`, })); } catch (e) {} // Get merge queue items interface QueueItemWithDetails { id: string; position: number; status: string; priority: number; mergeMethod: string; addedAt: string; pr: { number: number; title: string; headBranch: string; baseBranch: string; additions?: number; deletions?: number; author?: { username: string; avatarUrl?: string }; } | null; stackInfo?: { id: string; name: string; position: number; total: number; }; ciStatus?: string; estimatedMerge?: Date; } let queueItems: QueueItemWithDetails[] = []; let isPaused = false; let lastProcessedAt: string | null = null; try { const query = repoFilter ? and( eq(schema.mergeQueue.repositoryId, repoFilter), eq(schema.mergeQueue.status, "pending"), ) : eq(schema.mergeQueue.status, "pending"); const items = await db.query.mergeQueue.findMany({ where: query, orderBy: [ desc(schema.mergeQueue.priority), asc(schema.mergeQueue.position), ], with: { pullRequest: { with: { author: true, }, }, }, }); // Calculate estimated merge times and add stack info let currentPosition = 1; const avgCiTime = 10; // minutes for (const item of items) { const estimatedMerge = new Date( Date.now() + currentPosition * avgCiTime * 60 * 1000, ); // Check if PR is part of a stack let stackInfo = undefined; try { const stackEntry = await db.query.prStackEntries.findFirst({ where: eq( schema.prStackEntries.pullRequestId, item.pullRequestId, ), }); if (stackEntry) { const stack = await db.query.prStacks.findFirst({ where: eq(schema.prStacks.id, stackEntry.stackId), }); const entries = await db.query.prStackEntries.findMany({ where: eq( schema.prStackEntries.stackId, stackEntry.stackId, ), }); if (stack) { stackInfo = { id: stack.id, name: stack.name || "Unnamed Stack", position: stackEntry.stackOrder, total: entries.length, }; } } } catch (e) {} queueItems.push({ id: item.id, position: currentPosition, status: item.status, priority: item.priority ?? 0, mergeMethod: item.mergeMethod || "merge", addedAt: item.addedAt?.toISOString() ?? new Date().toISOString(), pr: item.pullRequest ? { number: item.pullRequest.number, title: item.pullRequest.title, headBranch: item.pullRequest.headBranch, baseBranch: item.pullRequest.baseBranch, additions: item.pullRequest.additions || 0, deletions: item.pullRequest.deletions || 0, author: item.pullRequest.author ? { username: item.pullRequest.author.username, avatarUrl: item.pullRequest.author.avatarUrl || undefined, } : undefined, } : null, stackInfo, ciStatus: "pending", // Would come from CI system estimatedMerge, }); currentPosition++; } } catch (e) { console.error("Failed to fetch merge queue:", e); } function getStatusIcon(status: string) { switch (status) { case "pending": return { icon: Clock, color: "text-yellow-400", bg: "bg-yellow-500/10", }; case "running_ci": return { icon: Loader2, color: "text-blue-400", bg: "bg-blue-500/10", }; case "ready": return { icon: CheckCircle2, color: "text-green-400", bg: "bg-green-500/10", }; case "merging": return { icon: GitMerge, color: "text-purple-400", bg: "bg-purple-500/10", }; case "failed": return { icon: XCircle, color: "text-red-400", bg: "bg-red-500/10", }; default: return { icon: Clock, color: "text-muted-foreground", bg: "bg-gray-500/10", }; } } function formatTime(date: Date | string) { const d = date instanceof Date ? date : new Date(date); return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", }); } function timeAgo(date: string) { const seconds = Math.floor((Date.now() - new Date(date).getTime()) / 1000); if (seconds < 60) return "just now"; if (seconds < 3600) return Math.floor(seconds / 60) + "m ago"; if (seconds < 86400) return Math.floor(seconds / 3600) + "h ago"; return Math.floor(seconds / 86400) + "d ago"; } ---

Merge Queue

{queueItems.length} {queueItems.length === 1 ? "PR" : "PRs"} in queue

{ userRepos.length > 1 && ( ) }
{ isPaused && (
Queue is paused. No PRs will be merged until resumed.
) }
{ queueItems.length === 0 ? (

Queue is empty

No PRs are waiting to be merged.

Add PRs to the queue with{" "} och queue add <pr>

) : (
{queueItems.map((item, index) => { const StatusIcon = getStatusIcon( item.status, ).icon; const statusColor = getStatusIcon( item.status, ).color; const statusBg = getStatusIcon( item.status, ).bg; return (
{/* Position */}
{item.position}
{/* Status Icon */}
{/* PR Info */}
{item.pr?.title || "Unknown PR"} {item.stackInfo && ( { item.stackInfo .position } / { item.stackInfo .total } )} {item.mergeMethod}
#{item.pr?.number} {item.pr?.headBranch} →{" "} {item.pr?.baseBranch} {item.pr?.author && ( {item.pr.author .avatarUrl ? ( ) : (
{item.pr.author.username[0]?.toUpperCase()}
)} { item.pr.author .username }
)} {item.pr?.additions !== undefined && ( + { item.pr .additions } / - { item.pr .deletions } )}
{/* Estimated Merge Time */}
ETA
{item.estimatedMerge ? formatTime( item.estimatedMerge, ) : "-"}
{/* Actions */}
{item.priority < 10 && ( )} {item.priority > 0 && ( )}
); })}
) }
Pending
Running CI
Ready
Merging
Failed