Skip to content

Commit 7c3834a

Browse files
committed
dashboard: Add view for PR runs
Added a script that fetches PR data and created a separate view on the dashboard. Tweaked dotenv require. Fixes kata-containers#1 Signed-off-by: Anna Finn <[email protected]>
1 parent 7549895 commit 7c3834a

File tree

4 files changed

+384
-53
lines changed

4 files changed

+384
-53
lines changed

.github/workflows/fectch-ci-data.yml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Fetch CI Data
2+
run-name: Fetch CI Data
3+
on:
4+
schedule:
5+
- cron: '0 */2 * * *'
6+
workflow_dispatch:
7+
8+
jobs:
9+
fetch-and-commit-data:
10+
runs-on: ubuntu-22.04
11+
12+
env:
13+
NODE_ENV: production
14+
TOKEN: ${{ secrets.GITHUB_TOKEN }}
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
- name: Update dashboard data
20+
run: |
21+
# fetch ci nightly data as temporary file
22+
node scripts/fetch-ci-nightly-data.js | tee tmp-data.json
23+
node scripts/fetch-ci-pr-data.js | tee tmp-data2.json
24+
25+
# switch to a branch specifically for holding latest data
26+
git config --global user.name "GH Actions Workflow"
27+
git config --global user.email "<gha@runner>"
28+
git fetch --all
29+
git checkout latest-dashboard-data
30+
31+
# back out whatever data was there
32+
git reset HEAD~1
33+
34+
# overwrite the old data
35+
mkdir -p data/
36+
mv tmp-data.json data/job_stats.json
37+
mv tmp-data2.json data/check_stats.json
38+
39+
# commit
40+
git add data
41+
git commit -m '[skip ci] latest ci nightly data'
42+
git push --force

pages/index.js

+146-52
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,39 @@ import MaintainerMapping from "../maintainers.yml";
99

1010
export default function Home() {
1111
const [loading, setLoading] = useState(true);
12+
const [checks, setChecks] = useState([]);
1213
const [jobs, setJobs] = useState([]);
13-
const [rows, setRows] = useState([]);
14+
const [rowsPR, setRowsPR] = useState([]);
15+
const [rowsNightly, setRowsNightly] = useState([]);
1416
const [expandedRows, setExpandedRows] = useState([]);
1517
const [requiredFilter, setRequiredFilter] = useState(false);
18+
const [display, setDisplay] = useState("nightly");
1619

1720
useEffect(() => {
1821
const fetchData = async () => {
19-
let data = {};
22+
let nightlyData = {};
23+
let prData = {};
2024

2125
if (process.env.NODE_ENV === "development") {
22-
data = (await import("../localData/job_stats.json")).default;
26+
nightlyData = (await import("../localData/job_stats.json")).default;
27+
prData = (await import("../localData/check_stats.json")).default;
2328
} else {
24-
const response = await fetch(
29+
nightlyData = await fetch(
2530
"https://raw.githubusercontent.com/kata-containers/kata-containers.github.io" +
2631
"/refs/heads/latest-dashboard-data/data/job_stats.json"
27-
);
28-
data = await response.json();
32+
).then((res) => res.json());
33+
prData = await fetch(
34+
"https://raw.githubusercontent.com/kata-containers/kata-containers.github.io" +
35+
"/refs/heads/latest-dashboard-data/data/check_stats.json"
36+
).then((res) => res.json());
2937
}
3038

3139
try {
32-
const jobData = Object.keys(data).map((key) => {
33-
const job = data[key];
34-
return { name: key, ...job };
35-
});
36-
setJobs(jobData);
40+
const mapData = (data) => Object.keys(data).map((key) =>
41+
({ name: key, ...data[key] })
42+
);
43+
setJobs(mapData(nightlyData));
44+
setChecks(mapData(prData));
3745
} catch (error) {
3846
// TODO: Add pop-up/toast message for error
3947
console.error("Error fetching data:", error);
@@ -54,14 +62,12 @@ export default function Home() {
5462
return filteredJobs;
5563
};
5664

65+
// Filter and set the rows for Nightly view.
5766
useEffect(() => {
5867
setLoading(true);
59-
60-
// Filter based on required tag.
6168
let filteredJobs = filterRequired(jobs);
62-
6369
//Set the rows for the table.
64-
setRows(
70+
setRowsNightly(
6571
filteredJobs.map((job) => ({
6672
name : job.name,
6773
runs : job.runs,
@@ -74,6 +80,31 @@ export default function Home() {
7480
setLoading(false);
7581
}, [jobs, requiredFilter]);
7682

83+
// Filter and set the rows for PR Checks view.
84+
useEffect(() => {
85+
setLoading(true);
86+
let filteredChecks = filterRequired(checks)
87+
88+
//Set the rows for the table.
89+
setRowsPR(
90+
filteredChecks.map((check) => ({
91+
name : check.name,
92+
runs : check.runs,
93+
fails : check.fails,
94+
skips : check.skips,
95+
required : check.required,
96+
weather : getWeatherIndex(check),
97+
}))
98+
);
99+
setLoading(false);
100+
}, [checks, requiredFilter]);
101+
102+
// Close all rows on view switch.
103+
// Needed because if view is switched, breaks expanded row toggling.
104+
useEffect(() => {
105+
setExpandedRows([])
106+
}, [display]);
107+
77108
const toggleRow = (rowData) => {
78109
const isRowExpanded = expandedRows.includes(rowData);
79110

@@ -91,6 +122,10 @@ export default function Home() {
91122
${active ? "border-blue-500 bg-blue-500 text-white"
92123
: "border-gray-300 bg-white hover:bg-gray-100"}`;
93124

125+
const tabClass = (active) => `tab md:px-4 px-2 py-2 border-b-2 focus:outline-none
126+
${active ? "border-blue-500 bg-gray-300"
127+
: "border-gray-300 bg-white hover:bg-gray-100"}`;
128+
94129

95130
// Template for rendering the Name column as a clickable item
96131
const nameTemplate = (rowData) => {
@@ -104,7 +139,9 @@ export default function Home() {
104139
const maintainRefs = useRef([]);
105140

106141
const rowExpansionTemplate = (data) => {
107-
const job = jobs.find((job) => job.name === data.name);
142+
const job = (display === "nightly"
143+
? jobs
144+
: checks).find((job) => job.name === data.name);
108145

109146
// Prepare run data
110147
const runs = [];
@@ -115,7 +152,7 @@ export default function Home() {
115152
url: job.urls[i],
116153
});
117154
}
118-
155+
119156
// Find maintainers for the given job
120157
const maintainerData = MaintainerMapping.mappings
121158
.filter(({ regex }) => new RegExp(regex).test(job.name))
@@ -135,6 +172,7 @@ export default function Home() {
135172
return acc;
136173
}, {});
137174

175+
138176
return (
139177
<div key={`${job.name}-runs`} className="p-3 bg-gray-100">
140178
{/* Display last 10 runs */}
@@ -149,7 +187,7 @@ export default function Home() {
149187
: "⚠️";
150188
return (
151189
<span key={`${job.name}-runs-${run.run_num}`}>
152-
<a href={run.url}>
190+
<a href={run.url} target="_blank" rel="noopener noreferrer">
153191
{emoji} {run.run_num}
154192
</a>
155193
&nbsp;&nbsp;&nbsp;&nbsp;
@@ -251,9 +289,10 @@ export default function Home() {
251289
);
252290
};
253291

254-
const renderTable = () => (
292+
// Render table for nightly view.
293+
const renderNightlyTable = () => (
255294
<DataTable
256-
value={rows}
295+
value={rowsNightly}
257296
expandedRows={expandedRows}
258297
stripedRows
259298
rowExpansionTemplate={rowExpansionTemplate}
@@ -263,27 +302,63 @@ export default function Home() {
263302
sortField="fails"
264303
sortOrder={-1}
265304
>
266-
<Column expander style={{ width: "5rem" }} />
305+
<Column expander/>
267306
<Column
268307
field="name"
269308
header="Name"
270309
body={nameTemplate}
271-
filter
310+
className="select-text"
272311
sortable
273-
maxConstraints={4}
274-
filterHeader="Filter by Name"
275-
filterPlaceholder="Search..."
276312
/>
277-
<Column field="required" header="Required" sortable />
278-
<Column field="runs" header="Runs" sortable />
279-
<Column field="fails" header="Fails" sortable />
280-
<Column field="skips" header="Skips" sortable />
313+
<Column field = "required" header = "Required" sortable/>
314+
<Column
315+
field = "runs"
316+
header = "Runs"
317+
className="whitespace-nowrap px-2"
318+
sortable />
319+
<Column field = "fails" header = "Fails" sortable/>
320+
<Column field = "skips" header = "Skips" sortable/>
321+
<Column
322+
field = "weather"
323+
header = "Weather"
324+
body = {weatherTemplate}
325+
sortable />
326+
</DataTable>
327+
);
328+
329+
const renderPRTable = () => (
330+
<DataTable
331+
value={rowsPR}
332+
expandedRows={expandedRows}
333+
stripedRows
334+
rowExpansionTemplate={rowExpansionTemplate}
335+
onRowToggle={(e) => setExpandedRows(e.data)}
336+
loading={loading}
337+
emptyMessage="No results found."
338+
sortField="fails"
339+
sortOrder={-1}
340+
>
341+
<Column expander/>
281342
<Column
282-
field="weather"
283-
header="Weather"
284-
body={weatherTemplate}
343+
field="name"
344+
header="Name"
345+
body={nameTemplate}
346+
className="select-text"
285347
sortable
286348
/>
349+
<Column field = "required" header = "Required" sortable/>
350+
<Column
351+
field = "runs"
352+
header = "Runs"
353+
className="whitespace-nowrap px-2"
354+
sortable />
355+
<Column field = "fails" header = "Fails" sortable/>
356+
<Column field = "skips" header = "Skips" sortable/>
357+
<Column
358+
field = "weather"
359+
header = "Weather"
360+
body = {weatherTemplate}
361+
sortable />
287362
</DataTable>
288363
);
289364

@@ -299,30 +374,49 @@ export default function Home() {
299374
}
300375
>
301376
<a
302-
href={
303-
"https://github.com/kata-containers/kata-containers/" +
304-
"actions/workflows/ci-nightly.yaml"
305-
}
306-
target="_blank"
307-
rel="noopener noreferrer"
308-
>
309-
Kata CI Dashboard
310-
</a>
377+
href={display === 'nightly'
378+
? "https://github.com/kata-containers/kata-containers/" +
379+
"actions/workflows/ci-nightly.yaml"
380+
: "https://github.com/kata-containers/kata-containers/" +
381+
"/pulls?state=closed"}
382+
target="_blank"
383+
rel="noopener noreferrer"
384+
>
385+
Kata CI Dashboard
386+
</a>
311387
</h1>
388+
<div className="flex flex-wrap mt-2 p-4 md:text-base text-xs">
389+
<div className="space-x-2 pb-2 pr-3 mx-auto flex">
390+
<button
391+
className={tabClass(display === "nightly")}
392+
onClick={() => {
393+
setDisplay("nightly");
394+
}}>
395+
Nightly Jobs
396+
</button>
397+
<button
398+
className={tabClass(display === "prchecks")}
399+
onClick={() => {
400+
setDisplay("prchecks");
401+
}}>
402+
PR Checks
403+
</button>
404+
</div>
405+
</div>
312406

313-
<main
314-
className={
315-
"m-0 h-full p-4 overflow-x-hidden overflow-y-auto bg-surface-ground font-normal text-text-color antialiased select-text"
316-
}
317-
>
407+
408+
<div className={"m-0 h-full px-4 overflow-x-hidden overflow-y-auto \
409+
bg-surface-ground antialiased select-text"}>
318410
<button
319-
className={buttonClass(requiredFilter)}
320-
onClick={() => setRequiredFilter(!requiredFilter)}>
321-
Required Jobs Only
411+
className={buttonClass(requiredFilter)}
412+
onClick={() => setRequiredFilter(!requiredFilter)}>
413+
Required Jobs Only
322414
</button>
323-
<div className="mt-4 text-lg">Total Rows: {rows.length}</div>
324-
<div>{renderTable()}</div>
325-
</main>
415+
<div className="mt-4 text-center md:text-lg text-base">
416+
Total Rows: {display === "prchecks" ? rowsPR.length : rowsNightly.length}
417+
</div>
418+
<div>{display === "prchecks" ? renderPRTable() : renderNightlyTable()}</div>
419+
</div>
326420
</div>
327421
);
328422
}

scripts/fetch-ci-nightly-data.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
// Set token used for making Authorized GitHub API calls.
2222
// In dev, set by .env file; in prod, set by GitHub Secret.
23-
if(process.env.NODE_ENV === "development"){
23+
if(process.env.NODE_ENV !== "production"){
2424
require('dotenv').config();
2525
}
2626
const TOKEN = process.env.TOKEN;

0 commit comments

Comments
 (0)