mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
feat(runs): link stored pull requests
This commit is contained in:
parent
12b475d692
commit
0b29dbe6d8
15 changed files with 282 additions and 39 deletions
7
apps/fabro-web/app/components/icons.tsx
Normal file
7
apps/fabro-web/app/components/icons.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export function GitPullRequestIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" className={className} aria-hidden="true">
|
||||
<path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -27,6 +27,10 @@ describe("mapRunListItem", () => {
|
|||
created_at: "2026-04-08T12:00:00Z",
|
||||
start_time: "2026-04-08T12:00:00Z",
|
||||
pending_control: null,
|
||||
pull_request: {
|
||||
number: 123,
|
||||
html_url: "https://github.com/fabro-sh/fabro/pull/123",
|
||||
},
|
||||
} as const;
|
||||
const item = mapRunListItem(summary);
|
||||
expect(item.id).toBe("01ABC");
|
||||
|
|
@ -37,6 +41,8 @@ describe("mapRunListItem", () => {
|
|||
expect(item.elapsed).toBeDefined();
|
||||
expect(item.column).toBe("running");
|
||||
expect(item.lifecycleStatus).toBe("paused");
|
||||
expect(item.number).toBe(123);
|
||||
expect(item.pullRequestUrl).toBe("https://github.com/fabro-sh/fabro/pull/123");
|
||||
});
|
||||
|
||||
test("uses a fallback title when the server title is blank", () => {
|
||||
|
|
@ -81,6 +87,15 @@ describe("mapRunSummaryToRunItem", () => {
|
|||
created_at: "2026-04-08T12:00:00Z",
|
||||
start_time: "2026-04-08T12:00:00Z",
|
||||
pending_control: null,
|
||||
pull_request: {
|
||||
html_url: "https://github.com/fabro-sh/fabro/pull/456",
|
||||
number: 456,
|
||||
owner: "fabro-sh",
|
||||
repo: "fabro",
|
||||
base_branch: "main",
|
||||
head_branch: "fabro/run/demo",
|
||||
title: "Add run PR chip",
|
||||
},
|
||||
};
|
||||
const item = mapRunSummaryToRunItem(summary);
|
||||
expect(item.id).toBe("01ABC");
|
||||
|
|
@ -90,6 +105,8 @@ describe("mapRunSummaryToRunItem", () => {
|
|||
expect(item.sourceDirectory).toBe("/home/user/myrepo");
|
||||
expect(item.elapsed).toBeDefined();
|
||||
expect(item.lifecycleStatus).toBe("running");
|
||||
expect(item.number).toBe(456);
|
||||
expect(item.pullRequestUrl).toBe("https://github.com/fabro-sh/fabro/pull/456");
|
||||
});
|
||||
|
||||
test("handles missing optional fields", () => {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export interface RunItem {
|
|||
lifecycleStatus?: RunStatus | null;
|
||||
lifecycleStatusLabel?: string;
|
||||
number?: number;
|
||||
pullRequestUrl?: string;
|
||||
additions?: number;
|
||||
deletions?: number;
|
||||
checks?: CheckRun[];
|
||||
|
|
@ -92,6 +93,7 @@ export function mapRunListItem(item: RunListItem): RunItem {
|
|||
lifecycleStatus,
|
||||
lifecycleStatusLabel: lifecycleStatusLabel(item.status),
|
||||
number: item.pull_request?.number,
|
||||
pullRequestUrl: item.pull_request?.html_url,
|
||||
additions: item.pull_request?.additions,
|
||||
deletions: item.pull_request?.deletions,
|
||||
checks: item.pull_request?.checks?.map((c) => ({
|
||||
|
|
@ -122,6 +124,8 @@ export function mapRunSummaryToRunItem(summary: RunSummary): RunItem {
|
|||
workflow: summary.workflow_slug ?? summary.workflow_name ?? "unknown",
|
||||
lifecycleStatus,
|
||||
lifecycleStatusLabel: lifecycleStatusLabel(summary.status),
|
||||
number: summary.pull_request?.number,
|
||||
pullRequestUrl: summary.pull_request?.html_url,
|
||||
sourceDirectory: summary.source_directory ?? undefined,
|
||||
elapsed:
|
||||
summary.elapsed_secs != null
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ type RunDetailActionResult = import("./run-detail").RunDetailActionResult;
|
|||
|
||||
const h = createElement;
|
||||
|
||||
function makeRunSummary(status = "succeeded", diffSummary: any = null) {
|
||||
function makeRunSummary(status = "succeeded", diffSummary: any = null, pullRequest: any = null) {
|
||||
return {
|
||||
run_id: "run_1",
|
||||
title: "Run 1",
|
||||
|
|
@ -83,6 +83,7 @@ function makeRunSummary(status = "succeeded", diffSummary: any = null) {
|
|||
elapsed_secs: null,
|
||||
source_directory: null,
|
||||
diff_summary: diffSummary,
|
||||
pull_request: pullRequest,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -109,13 +110,15 @@ async function renderRunDetail({
|
|||
status = "succeeded",
|
||||
questions = [],
|
||||
diffSummary = null,
|
||||
pullRequest = null,
|
||||
}: {
|
||||
initialEntry: string;
|
||||
status?: string;
|
||||
questions?: any[];
|
||||
diffSummary?: any;
|
||||
pullRequest?: any;
|
||||
}) {
|
||||
currentRunSummary = makeRunSummary(status, diffSummary);
|
||||
currentRunSummary = makeRunSummary(status, diffSummary, pullRequest);
|
||||
currentQuestions = questions;
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
|
|
@ -399,6 +402,31 @@ describe("RunDetail full-height child routes", () => {
|
|||
expect(tabCountBadges(renderer)).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("shows a linked pull request chip in the run header", async () => {
|
||||
const renderer = await renderRunDetail({
|
||||
initialEntry: "/runs/run_1",
|
||||
pullRequest: {
|
||||
html_url: "https://github.com/fabro-sh/fabro/pull/123",
|
||||
number: 123,
|
||||
owner: "fabro-sh",
|
||||
repo: "fabro",
|
||||
base_branch: "main",
|
||||
head_branch: "fabro/run/demo",
|
||||
title: "Add run PR chip",
|
||||
},
|
||||
});
|
||||
|
||||
const links = renderer.root.findAll(
|
||||
(node) =>
|
||||
node.type === "a" &&
|
||||
node.props.href === "https://github.com/fabro-sh/fabro/pull/123",
|
||||
);
|
||||
|
||||
expect(links).toHaveLength(1);
|
||||
expect(links[0].props.target).toBe("_blank");
|
||||
expect(links[0].children.filter((child) => typeof child !== "object").join("")).toBe("#123");
|
||||
});
|
||||
|
||||
test("keeps blocked full-height children clear of the interview dock without an h-72 sibling", async () => {
|
||||
const renderer = await renderRunDetail({
|
||||
initialEntry: "/runs/run_1/files",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { Link, Outlet, useLocation, useMatches, useNavigate } from "react-router
|
|||
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
|
||||
|
||||
import { InterviewDock } from "../components/interview-dock";
|
||||
import { GitPullRequestIcon } from "../components/icons";
|
||||
import { SteerBar, type SteerBarHandle } from "../components/steer-bar";
|
||||
import { ErrorState } from "../components/state";
|
||||
import { useToast } from "../components/toast";
|
||||
|
|
@ -323,6 +324,17 @@ export default function RunDetail({ params }: { params: { id: string } }) {
|
|||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{run.number != null && run.pullRequestUrl && (
|
||||
<a
|
||||
href={run.pullRequestUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center gap-1.5 font-mono text-xs text-fg-muted hover:text-fg"
|
||||
>
|
||||
<GitPullRequestIcon className="size-3.5" />
|
||||
#{run.number}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { ciConfig, columnStatusDisplay, columnStatuses, deriveCiStatus, mapRunLi
|
|||
import type { CiStatus, CheckRun, CheckStatus, RunItem, RunWithStatus, ColumnStatus } from "../data/runs";
|
||||
import { formatRelativeTime } from "../lib/format";
|
||||
import { EmptyState } from "../components/state";
|
||||
import { GitPullRequestIcon } from "../components/icons";
|
||||
import { useToast } from "../components/toast";
|
||||
import { shouldRefreshBoardForEvent, useBoardEvents } from "../lib/board-events";
|
||||
import { useAuthConfig, useBoardsRuns, useSystemInfo } from "../lib/queries";
|
||||
|
|
@ -128,19 +129,6 @@ function listLifecycleStatusLabel(run: Pick<RunWithStatus, "statusLabel" | "life
|
|||
}
|
||||
|
||||
|
||||
function GitPullRequestIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function CheckStatusIcon({ status }: { status: CheckStatus }) {
|
||||
switch (status) {
|
||||
case "success":
|
||||
|
|
@ -298,10 +286,23 @@ function PrCard({
|
|||
</span>
|
||||
)}
|
||||
{pr.number != null && (
|
||||
<span className={`ml-auto inline-flex items-center gap-1 font-mono text-xs ${iconColor}`}>
|
||||
<GitPullRequestIcon className="size-3.5 shrink-0" />
|
||||
#{pr.number}
|
||||
</span>
|
||||
pr.pullRequestUrl ? (
|
||||
<a
|
||||
href={pr.pullRequestUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
className={`ml-auto inline-flex items-center gap-1 font-mono text-xs ${iconColor} hover:text-fg`}
|
||||
>
|
||||
<GitPullRequestIcon className="size-3.5 shrink-0" />
|
||||
#{pr.number}
|
||||
</a>
|
||||
) : (
|
||||
<span className={`ml-auto inline-flex items-center gap-1 font-mono text-xs ${iconColor}`}>
|
||||
<GitPullRequestIcon className="size-3.5 shrink-0" />
|
||||
#{pr.number}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
@ -637,11 +638,25 @@ function RunRow({ run }: { run: RunWithStatus }) {
|
|||
|
||||
<span className="inline-flex items-center justify-end gap-1.5 font-mono text-xs text-fg-muted">
|
||||
{run.number != null && (
|
||||
<>
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
#{run.number}
|
||||
{run.checks != null && <span className={`size-1.5 rounded-full ${ciConfig[deriveCiStatus(run.checks)].dot}`} />}
|
||||
</>
|
||||
run.pullRequestUrl ? (
|
||||
<a
|
||||
href={run.pullRequestUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
className="inline-flex items-center gap-1.5 hover:text-fg"
|
||||
>
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
#{run.number}
|
||||
{run.checks != null && <span className={`size-1.5 rounded-full ${ciConfig[deriveCiStatus(run.checks)].dot}`} />}
|
||||
</a>
|
||||
) : (
|
||||
<>
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
#{run.number}
|
||||
{run.checks != null && <span className={`size-1.5 rounded-full ${ciConfig[deriveCiStatus(run.checks)].dot}`} />}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState } from "react";
|
||||
import { ChevronDownIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { Link, useParams } from "react-router";
|
||||
import { GitPullRequestIcon } from "../components/icons";
|
||||
import { ciConfig, columnForStatus, columnStatusDisplay, deriveCiStatus, mapRunSummaryToRunItem } from "../data/runs";
|
||||
import type { ColumnStatus, RunWithStatus } from "../data/runs";
|
||||
import { useWorkflowRuns } from "../lib/queries";
|
||||
|
|
@ -21,14 +22,6 @@ function mapWorkflowRuns(result: PaginatedRunList | null | undefined): RunWithSt
|
|||
.filter((run): run is RunWithStatus => run != null);
|
||||
}
|
||||
|
||||
function GitPullRequestIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" className={className} aria-hidden="true">
|
||||
<path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function RunRow({ run }: { run: RunWithStatus }) {
|
||||
const colors = columnStatusDisplay[run.status];
|
||||
return (
|
||||
|
|
@ -61,11 +54,25 @@ function RunRow({ run }: { run: RunWithStatus }) {
|
|||
|
||||
<span className="inline-flex items-center justify-end gap-1.5 font-mono text-xs text-fg-muted">
|
||||
{run.number != null && (
|
||||
<>
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
#{run.number}
|
||||
{run.checks != null && <span className={`size-1.5 rounded-full ${ciConfig[deriveCiStatus(run.checks)].dot}`} />}
|
||||
</>
|
||||
run.pullRequestUrl ? (
|
||||
<a
|
||||
href={run.pullRequestUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
className="inline-flex items-center gap-1.5 hover:text-fg"
|
||||
>
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
#{run.number}
|
||||
{run.checks != null && <span className={`size-1.5 rounded-full ${ciConfig[deriveCiStatus(run.checks)].dot}`} />}
|
||||
</a>
|
||||
) : (
|
||||
<>
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
#{run.number}
|
||||
{run.checks != null && <span className={`size-1.5 rounded-full ${ciConfig[deriveCiStatus(run.checks)].dot}`} />}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -6165,6 +6165,10 @@ components:
|
|||
oneOf:
|
||||
- $ref: "#/components/schemas/DiffSummary"
|
||||
- type: "null"
|
||||
pull_request:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/PullRequestRecord"
|
||||
- type: "null"
|
||||
|
||||
ForkRequest:
|
||||
description: Request body for creating a new run from a source run checkpoint.
|
||||
|
|
@ -6427,6 +6431,11 @@ components:
|
|||
type: integer
|
||||
description: Pull request number.
|
||||
example: 889
|
||||
html_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: Stored GitHub pull request URL.
|
||||
example: https://github.com/fabro-sh/fabro/pull/889
|
||||
additions:
|
||||
type: integer
|
||||
description: Lines added.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use fabro_api::types::{
|
|||
RepositoryReference as ApiRepositoryReference, RunSummary as ApiRunSummary,
|
||||
};
|
||||
use fabro_types::status::{RunStatus, SuccessReason, TerminalStatus};
|
||||
use fabro_types::{DiffSummary, RepositoryReference, RunId, RunSummary};
|
||||
use fabro_types::{DiffSummary, PullRequestRecord, RepositoryReference, RunId, RunSummary};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
|
|
@ -46,6 +46,15 @@ fn run_summary_json_matches_openapi_shape() {
|
|||
additions: 12,
|
||||
deletions: 4,
|
||||
}),
|
||||
Some(PullRequestRecord {
|
||||
html_url: "https://github.com/fabro-sh/fabro/pull/123".to_string(),
|
||||
number: 123,
|
||||
owner: "fabro-sh".to_string(),
|
||||
repo: "fabro".to_string(),
|
||||
base_branch: "main".to_string(),
|
||||
head_branch: "fabro/run/demo".to_string(),
|
||||
title: "Add run PR chip".to_string(),
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -84,6 +93,15 @@ fn run_summary_json_matches_openapi_shape() {
|
|||
"files_changed": 3,
|
||||
"additions": 12,
|
||||
"deletions": 4
|
||||
},
|
||||
"pull_request": {
|
||||
"html_url": "https://github.com/fabro-sh/fabro/pull/123",
|
||||
"number": 123,
|
||||
"owner": "fabro-sh",
|
||||
"repo": "fabro",
|
||||
"base_branch": "main",
|
||||
"head_branch": "fabro/run/demo",
|
||||
"title": "Add run PR chip"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
@ -128,6 +146,7 @@ fn run_summary_deserializes_when_optional_fields_are_absent() {
|
|||
assert_eq!(summary.total_usd_micros, None);
|
||||
assert_eq!(summary.superseded_by, None);
|
||||
assert_eq!(summary.diff_summary, None);
|
||||
assert_eq!(summary.pull_request, None);
|
||||
}
|
||||
|
||||
fn assert_same_type<T: 'static, U: 'static>() {
|
||||
|
|
|
|||
|
|
@ -944,6 +944,7 @@ mod runs {
|
|||
total_usd_micros,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1064,6 +1065,7 @@ mod runs {
|
|||
) -> RunPullRequest {
|
||||
RunPullRequest {
|
||||
number,
|
||||
html_url: Some(format!("https://github.com/demo/fabro/pull/{number}")),
|
||||
additions: Some(additions),
|
||||
deletions: Some(deletions),
|
||||
comments: Some(comments),
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ fn board_run_metadata_from_projection(
|
|||
"pull_request".to_string(),
|
||||
serde_json::json!({
|
||||
"number": pull_request.number,
|
||||
"html_url": pull_request.html_url,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
@ -183,6 +184,37 @@ fn board_run_metadata_from_projection(
|
|||
metadata
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fabro_types::PullRequestRecord;
|
||||
|
||||
use super::board_run_metadata_from_projection;
|
||||
|
||||
#[test]
|
||||
fn board_run_metadata_includes_pull_request_url() {
|
||||
let mut projection = fabro_store::RunProjection::default();
|
||||
projection.pull_request = Some(PullRequestRecord {
|
||||
html_url: "https://github.com/fabro-sh/fabro/pull/123".to_string(),
|
||||
number: 123,
|
||||
owner: "fabro-sh".to_string(),
|
||||
repo: "fabro".to_string(),
|
||||
base_branch: "main".to_string(),
|
||||
head_branch: "fabro/run/demo".to_string(),
|
||||
title: "Add run PR chip".to_string(),
|
||||
});
|
||||
|
||||
let metadata = board_run_metadata_from_projection(&projection);
|
||||
|
||||
assert_eq!(
|
||||
metadata.get("pull_request"),
|
||||
Some(&serde_json::json!({
|
||||
"number": 123,
|
||||
"html_url": "https://github.com/fabro-sh/fabro/pull/123"
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn paginate_items<T>(items: Vec<T>, pagination: &PaginationParams) -> (Vec<T>, bool) {
|
||||
let limit = pagination.limit.clamp(1, 100) as usize;
|
||||
let offset = pagination.offset.min(MAX_PAGE_OFFSET) as usize;
|
||||
|
|
|
|||
|
|
@ -560,6 +560,7 @@ pub(crate) fn build_summary(state: &RunProjection, run_id: &RunId) -> RunSummary
|
|||
.and_then(|billing| billing.total_usd_micros),
|
||||
state.superseded_by,
|
||||
state.diff_summary,
|
||||
state.pull_request.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1959,6 +1960,42 @@ mod tests {
|
|||
assert_eq!(summary.superseded_by, Some(fixtures::RUN_2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pull_request_created_populates_projection_and_summary() {
|
||||
use fabro_types::run_event::PullRequestCreatedProps;
|
||||
|
||||
let mut state = RunProjection::default();
|
||||
state
|
||||
.apply_event(&test_event(
|
||||
1,
|
||||
EventBody::PullRequestCreated(PullRequestCreatedProps {
|
||||
pr_url: "https://github.com/fabro-sh/fabro/pull/123".to_string(),
|
||||
pr_number: 123,
|
||||
owner: "fabro-sh".to_string(),
|
||||
repo: "fabro".to_string(),
|
||||
base_branch: "main".to_string(),
|
||||
head_branch: "fabro/run/demo".to_string(),
|
||||
title: "Add run PR chip".to_string(),
|
||||
draft: false,
|
||||
}),
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let pull_request = state
|
||||
.pull_request
|
||||
.as_ref()
|
||||
.expect("projection should store pull request");
|
||||
assert_eq!(
|
||||
pull_request.html_url,
|
||||
"https://github.com/fabro-sh/fabro/pull/123"
|
||||
);
|
||||
assert_eq!(pull_request.number, 123);
|
||||
|
||||
let summary = build_summary(&state, &fixtures::RUN_1);
|
||||
assert_eq!(summary.pull_request, state.pull_request);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_unarchived_restores_prior_status() {
|
||||
use fabro_types::run_event::{RunArchivedProps, RunCompletedProps, RunUnarchivedProps};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use chrono::{DateTime, Utc};
|
|||
use fabro_util::text::strip_goal_decoration;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{DiffSummary, RepositoryReference, RunControlAction, RunId, RunStatus};
|
||||
use crate::{
|
||||
DiffSummary, PullRequestRecord, RepositoryReference, RunControlAction, RunId, RunStatus,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RunSummary {
|
||||
|
|
@ -41,6 +43,8 @@ pub struct RunSummary {
|
|||
pub superseded_by: Option<RunId>,
|
||||
#[serde(default)]
|
||||
pub diff_summary: Option<DiffSummary>,
|
||||
#[serde(default)]
|
||||
pub pull_request: Option<PullRequestRecord>,
|
||||
}
|
||||
|
||||
impl RunSummary {
|
||||
|
|
@ -65,6 +69,7 @@ impl RunSummary {
|
|||
total_usd_micros: Option<i64>,
|
||||
superseded_by: Option<RunId>,
|
||||
diff_summary: Option<DiffSummary>,
|
||||
pull_request: Option<PullRequestRecord>,
|
||||
) -> Self {
|
||||
let title = truncate_goal(&goal);
|
||||
let repository = RepositoryReference {
|
||||
|
|
@ -94,6 +99,7 @@ impl RunSummary {
|
|||
total_usd_micros,
|
||||
superseded_by,
|
||||
diff_summary,
|
||||
pull_request,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -191,6 +197,7 @@ mod tests {
|
|||
Some(123),
|
||||
Some(fixtures::RUN_2),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(summary.title, "ship it");
|
||||
|
|
@ -249,6 +256,43 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summary_round_trips_pull_request() {
|
||||
let summary: RunSummary = serde_json::from_value(json!({
|
||||
"run_id": fixtures::RUN_1,
|
||||
"goal": "ship it",
|
||||
"title": "ship it",
|
||||
"labels": {},
|
||||
"status": { "kind": "running" },
|
||||
"repository": { "name": "fabro" },
|
||||
"created_at": fixtures::RUN_1.created_at(),
|
||||
"pull_request": {
|
||||
"html_url": "https://github.com/fabro-sh/fabro/pull/123",
|
||||
"number": 123,
|
||||
"owner": "fabro-sh",
|
||||
"repo": "fabro",
|
||||
"base_branch": "main",
|
||||
"head_branch": "fabro/run/demo",
|
||||
"title": "Add run PR chip"
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let value = serde_json::to_value(&summary).unwrap();
|
||||
assert_eq!(
|
||||
value["pull_request"],
|
||||
json!({
|
||||
"html_url": "https://github.com/fabro-sh/fabro/pull/123",
|
||||
"number": 123,
|
||||
"owner": "fabro-sh",
|
||||
"repo": "fabro",
|
||||
"base_branch": "main",
|
||||
"head_branch": "fabro/run/demo",
|
||||
"title": "Add run PR chip"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summary_falls_back_to_source_directory_then_unknown() {
|
||||
let source_only = RunSummary::new(
|
||||
|
|
@ -268,6 +312,7 @@ mod tests {
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(source_only.repository.name, "local-checkout");
|
||||
assert_eq!(source_only.last_event_at, None);
|
||||
|
|
@ -289,6 +334,7 @@ mod tests {
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(unknown.repository.name, "unknown");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ export interface RunPullRequest {
|
|||
* Pull request number.
|
||||
*/
|
||||
'number': number;
|
||||
/**
|
||||
* Stored GitHub pull request URL.
|
||||
*/
|
||||
'html_url'?: string;
|
||||
/**
|
||||
* Lines added.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@
|
|||
import type { DiffSummary } from './diff-summary';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { PullRequestRecord } from './pull-request-record';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { RepositoryReference } from './repository-reference';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
|
|
@ -50,6 +53,7 @@ export interface RunSummary {
|
|||
'total_usd_micros'?: number | null;
|
||||
'superseded_by'?: string | null;
|
||||
'diff_summary'?: DiffSummary | null;
|
||||
'pull_request'?: PullRequestRecord | null;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue