Skip to content

Commit a503cbb

Browse files
authored
[CRCR] Align downstream repo page UI with main HUD style (#8330)
## Summary - Aligns the CRCR downstream repo page (`/crcr/{org}/{repo}`) visually with the main HUD page - Adds rotated (tilted) column headers to accommodate more job columns without horizontal scrolling - Adds SHA column (7-char truncated, linked) matching the main HUD layout - Fixes "U" character for in-progress jobs — now shows "?" (pending) like the main HUD - Replaces relative time ("10h ago") with absolute time format (e.g., "12:27 PM") matching the main HUD - Truncates long PR titles with ellipsis ("...") and shows full title on hover - Groups jobs with common numeric suffixes (e.g., test-matrix-1 through test-matrix-16) into collapsed columns showing worst-case status - Replaces MUI Table components with compact HTML table matching the main HUD's dense styling ## Motivation The CRCR downstream repo page looked significantly different from the main HUD — MUI table with wide spacing, relative timestamps, no SHA column, "U" for in-progress, and no job grouping. This made it harder for users to switch between pages. This change makes the CRCR page visually consistent with the main HUD so users get the same dense, information-rich view they are familiar with. ## Test plan - [ ] Verify rotated job column headers render correctly and are readable - [ ] Verify SHA column links to correct commit on GitHub - [ ] Verify in-progress jobs show "?" instead of "U" - [ ] Verify time shows "h:mm a" format without "ago" suffix - [ ] Verify long PR titles truncate with "..." and full title on tooltip hover - [ ] Verify grouped jobs (e.g., test-matrix-*) collapse into single column - [ ] Verify grouped cell tooltip shows individual job statuses - [ ] Verify pagination still works
1 parent 2c63095 commit a503cbb

3 files changed

Lines changed: 439 additions & 148 deletions

File tree

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { getOctokit } from "lib/github";
22
import type { NextApiRequest, NextApiResponse } from "next";
33

4+
const ALLOWED_UPSTREAM_REPOS = new Set(["pytorch/pytorch"]);
5+
46
interface CommitInfo {
57
sha: string;
68
title: string;
@@ -19,6 +21,11 @@ export default async function handler(
1921
}
2022

2123
const repoStr = Array.isArray(repo) ? repo[0] : repo;
24+
25+
if (!ALLOWED_UPSTREAM_REPOS.has(repoStr)) {
26+
return res.status(403).json({ error: "Repository not allowed" });
27+
}
28+
2229
const shaList = (Array.isArray(shas) ? shas[0] : shas)
2330
.split(",")
2431
.filter(Boolean)
@@ -29,42 +36,44 @@ export default async function handler(
2936
}
3037

3138
const [owner, name] = repoStr.split("/");
32-
if (!owner || !name) {
33-
return res.status(400).json({ error: "repo must be owner/name format" });
34-
}
3539

3640
try {
3741
const octokit = await getOctokit(owner, name);
38-
const results: CommitInfo[] = [];
3942

40-
// Fetch commits in parallel (bounded to 50 max)
41-
const promises = shaList.map(async (sha) => {
42-
try {
43-
const { data } = await octokit.rest.repos.getCommit({
44-
owner,
45-
repo: name,
46-
ref: sha,
47-
});
48-
const message = data.commit.message.split("\n")[0];
49-
return {
50-
sha,
51-
title: message,
52-
author: data.author?.login ?? data.commit.author?.name ?? "unknown",
53-
};
54-
} catch {
55-
return { sha, title: "", author: "" };
43+
// Single GraphQL query instead of N REST calls
44+
const commitFragments = shaList.map(
45+
(sha, i) => `c${i}: object(oid: "${sha}") {
46+
... on Commit {
47+
oid
48+
messageHeadline
49+
author { user { login } name }
50+
}
51+
}`
52+
);
53+
const query = `query {
54+
repository(owner: "${owner}", name: "${name}") {
55+
${commitFragments.join("\n")}
5656
}
57-
});
57+
}`;
5858

59-
const settled = await Promise.all(promises);
60-
results.push(...settled);
59+
const gqlResult: any = await octokit.graphql(query);
60+
const repoData = gqlResult?.repository ?? {};
61+
62+
const results: CommitInfo[] = shaList.map((sha, i) => {
63+
const commit = repoData[`c${i}`];
64+
return {
65+
sha,
66+
title: commit?.messageHeadline ?? "",
67+
author: commit?.author?.user?.login ?? commit?.author?.name ?? "",
68+
};
69+
});
6170

62-
// Commits are immutable — cache aggressively
6371
res
6472
.setHeader("Cache-Control", "s-maxage=86400, stale-while-revalidate=3600")
6573
.status(200)
6674
.json(results);
67-
} catch (error: any) {
68-
res.status(500).json({ error: error.message });
75+
} catch (error: unknown) {
76+
console.error("commit-info error:", error);
77+
res.status(500).json({ error: "Failed to fetch commit info" });
6978
}
7079
}

torchci/pages/api/crcr/pr-info.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { getOctokit } from "lib/github";
2+
import type { NextApiRequest, NextApiResponse } from "next";
3+
4+
const ALLOWED_UPSTREAM_REPOS = new Set(["pytorch/pytorch"]);
5+
6+
interface PrInfo {
7+
prNumber: number;
8+
title: string;
9+
author: string;
10+
}
11+
12+
export default async function handler(
13+
req: NextApiRequest,
14+
res: NextApiResponse
15+
) {
16+
const { repo, prs } = req.query;
17+
if (!repo || !prs) {
18+
return res
19+
.status(400)
20+
.json({ error: "Missing required params: repo, prs" });
21+
}
22+
23+
const repoStr = Array.isArray(repo) ? repo[0] : repo;
24+
25+
if (!ALLOWED_UPSTREAM_REPOS.has(repoStr)) {
26+
return res.status(403).json({ error: "Repository not allowed" });
27+
}
28+
29+
const prList = (Array.isArray(prs) ? prs[0] : prs)
30+
.split(",")
31+
.map(Number)
32+
.filter((n) => n > 0)
33+
.slice(0, 50);
34+
35+
if (prList.length === 0) {
36+
return res.status(200).json([]);
37+
}
38+
39+
const [owner, name] = repoStr.split("/");
40+
41+
try {
42+
const octokit = await getOctokit(owner, name);
43+
44+
// Single GraphQL query instead of N REST calls
45+
const prFragments = prList.map(
46+
(pr, i) => `pr${i}: pullRequest(number: ${pr}) {
47+
number
48+
title
49+
author { login }
50+
}`
51+
);
52+
const query = `query {
53+
repository(owner: "${owner}", name: "${name}") {
54+
${prFragments.join("\n")}
55+
}
56+
}`;
57+
58+
const gqlResult: any = await octokit.graphql(query);
59+
const repoData = gqlResult?.repository ?? {};
60+
61+
const results: PrInfo[] = prList.map((prNumber, i) => {
62+
const pr = repoData[`pr${i}`];
63+
return {
64+
prNumber,
65+
title: pr?.title ?? "",
66+
author: pr?.author?.login ?? "",
67+
};
68+
});
69+
70+
res
71+
.setHeader("Cache-Control", "s-maxage=300, stale-while-revalidate=600")
72+
.status(200)
73+
.json(results);
74+
} catch (error: unknown) {
75+
console.error("pr-info error:", error);
76+
res.status(500).json({ error: "Failed to fetch PR info" });
77+
}
78+
}

0 commit comments

Comments
 (0)