feat(fleet): track + show PR status (open/merged) on the run

Runs now carry prState (open when the PR is opened, merged when auto-merge
succeeds), reported on lease release. Job-detail Runs table shows a status badge
next to the PR link.
This commit is contained in:
saravanakumardb1 2026-05-31 13:56:33 -07:00
parent e9c1714c13
commit 696ee4189e
5 changed files with 30 additions and 11 deletions

View File

@ -404,15 +404,28 @@ export default function FleetJobDetailPage() {
</td>
<td className="py-2 text-xs">
{r.prUrl ? (
<a
href={r.prUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
aria-label="Open pull request"
>
PR
</a>
<span className="inline-flex items-center gap-1">
<a
href={r.prUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
aria-label="Open pull request"
>
PR
</a>
{r.prState && (
<span
className={`rounded-full px-1.5 py-0.5 text-[10px] font-medium ${
r.prState === 'merged'
? 'bg-green-600/15 text-green-700 dark:text-green-400'
: 'bg-muted text-muted-foreground'
}`}
>
{r.prState}
</span>
)}
</span>
) : (
'—'
)}

View File

@ -65,6 +65,7 @@ export interface FleetRun {
insights: FleetRunInsights;
prUrl?: string;
branch?: string;
prState?: 'open' | 'merged';
}
export interface FleetEvent {

View File

@ -895,6 +895,7 @@ export async function releaseLease(
result?: FleetRunDoc['result'];
prUrl?: string;
branch?: string;
prState?: FleetRunDoc['prState'];
}
): Promise<FenceResult<FleetLeaseDoc>> {
const job = await repo.getJob(jobId, productId);
@ -906,13 +907,14 @@ export async function releaseLease(
if (!res.ok) return { ok: false, reason: 'conflict' };
if (stage) await repo.revUpdateJob(jobId, productId, job.rev, { stage });
// Record the factory's reported metrics + outcome + PR deliverable on the run.
if (report?.insights || report?.result || report?.prUrl || report?.branch) {
if (report?.insights || report?.result || report?.prUrl || report?.branch || report?.prState) {
const runId = `${jobId}:run:${job.attempts}`;
await repo.updateRun(runId, jobId, {
...(report.insights ? { insights: report.insights } : {}),
...(report.result ? { result: report.result } : {}),
...(report.prUrl ? { prUrl: report.prUrl } : {}),
...(report.branch ? { branch: report.branch } : {}),
...(report.prState ? { prState: report.prState } : {}),
endedAt: new Date().toISOString(),
});
}

View File

@ -263,6 +263,7 @@ export async function fleetRoutes(app: FastifyInstance) {
result: parsed.data.result,
prUrl: parsed.data.prUrl,
branch: parsed.data.branch,
prState: parsed.data.prState,
});
if (!res.ok) {
if (res.reason === 'not_found') throw new NotFoundError('Job or lease not found');

View File

@ -227,6 +227,7 @@ export const FleetRunDocSchema = z.object({
/** PR deliverable produced by this run (§PR mode). */
prUrl: z.string().optional(),
branch: z.string().optional(),
prState: z.enum(['open', 'merged']).optional(),
});
export type FleetRunDoc = z.infer<typeof FleetRunDocSchema>;
@ -432,9 +433,10 @@ export const ReleaseLeaseSchema = z.object({
// releases the lease at the end of a work unit. Recorded on the current run.
insights: InsightsSchema.optional(),
result: z.enum(RUN_RESULTS).optional(),
// PR deliverable (§PR mode): the pull request the run opened + its branch.
// PR deliverable (§PR mode): the pull request the run opened + its branch + state.
prUrl: z.string().optional(),
branch: z.string().optional(),
prState: z.enum(['open', 'merged']).optional(),
});
export type ReleaseLeaseInput = z.infer<typeof ReleaseLeaseSchema>;