Skip to content

Commit

Permalink
dashboard: Indicate if a test was rerun
Browse files Browse the repository at this point in the history
Added code to get rerun information to both fetch scripts.
Display the reruns as a superscript in the rowExpansionTemplate with the result/URL.

Fixes: kata-containers#8

Signed-off-by: Anna Finn <[email protected]>
  • Loading branch information
afinn12 committed Nov 18, 2024
1 parent d2ba40b commit 8a3da7e
Show file tree
Hide file tree
Showing 4 changed files with 352 additions and 117 deletions.
15 changes: 4 additions & 11 deletions components/weatherTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,33 @@ const icons = [

export const getWeatherIndex = (stat) => {
let fail_rate = 0;
fail_rate = (stat["fails"] + stat["skips"]) / stat["runs"];
fail_rate = (stat["fails"]) / stat["runs"];
// e.g. failing 3/9 runs is .33, or idx=1
var idx = Math.floor((fail_rate * 10) / 2);
if (idx == icons.length) {
// edge case: if 100% failures, then we go past the end of icons[]
// back the idx down by 1
console.assert(fail_rate == 1.0);
idx -= 1;
}

// This error checks if there are zero runs.
// Currently, will display stormy weather.
if (isNaN(idx)) {
if (isNaN(idx) || idx > icons.length) {
idx = 4;
}
return idx;
};

const getWeatherIcon = (stat) => {
const idx = getWeatherIndex(stat);
return icons[idx];
};

export const weatherTemplate = (data) => {
const icon = getWeatherIcon(data);
return (
<div>
<Image
src={`${basePath}/${icon}`}
src={`${basePath}/${icons[getWeatherIndex(data)]}`}
alt="weather"
width={32}
height={32}
// priority
/>
</div>
);
};
};
169 changes: 116 additions & 53 deletions pages/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useEffect, useState } from "react";
import React, { useEffect, useState, useRef } from "react";
import { DataTable } from "primereact/datatable";
import { Column } from "primereact/column";
import Head from "next/head";
import { weatherTemplate, getWeatherIndex } from "../components/weatherTemplate";
import { OverlayPanel } from 'primereact/overlaypanel';



export default function Home() {
Expand Down Expand Up @@ -62,6 +64,8 @@ useEffect(() => {
skips : job.skips,
required : job.required,
weather : getWeatherIndex(job),
reruns : job.reruns,
total_reruns : job.reruns.reduce((total, r) => total + r, 0),
}))
);
setLoading(false);
Expand All @@ -81,6 +85,8 @@ useEffect(() => {
skips : check.skips,
required : check.required,
weather : getWeatherIndex(check),
reruns : check.reruns,
total_reruns : check.reruns.reduce((total, r) => total + r, 0),
}))
);
setLoading(false);
Expand All @@ -94,76 +100,129 @@ useEffect(() => {



const toggleRow = (rowData) => {
const isRowExpanded = expandedRows.includes(rowData);

let updatedExpandedRows;
if (isRowExpanded) {
updatedExpandedRows = expandedRows.filter((r) => r !== rowData);
} else {
updatedExpandedRows = [...expandedRows, rowData];
}

setExpandedRows(updatedExpandedRows);
};

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


// Template for rendering the Name column as a clickable item
const nameTemplate = (rowData) => {
return (
<span onClick={() => toggleRow(rowData)} style={{ cursor: "pointer" }}>
{rowData.name}
</span>
const nameTemplate = (rowData) => (
<div className="cursor-pointer" onClick={() => toggleRow(rowData)}>
<span style={{ userSelect: 'text' }}>{rowData.name}</span>
</div>
);

const toggleRow = (rowData) => {
setExpandedRows((prev) =>
prev.includes(rowData)
? prev.filter((r) => r !== rowData)
: [...prev, rowData]
);
};

const rerunRefs = useRef([]);

const rowExpansionTemplate = (data) => {
const job = (display === "nightly"
? jobs
: checks).find((job) => job.name === data.name);

if (!job) return (
<div className="p-3 bg-gray-100">
No data available for this job.
</div>);


const getRunStatusIcon = (runs) => {
if (Array.isArray(runs)) {
const allPass = runs.every(run => run === "Pass");
const allFail = runs.every(run => run === "Fail");

if (allPass) {return "✅";}
if (allFail) {return "❌";}
} else if (runs === "Pass") {
return "✅";
} else if (runs === "Fail") {
return "❌";
}
return "⚠️"; // return a warning if a mix of results
};

// Prepare run data
const runs = [];
for (let i = 0; i < job.runs; i++) {
runs.push({
run_num: job.run_nums[i],
result: job.results[i],
url: job.urls[i],
});
}
const runEntries = job.run_nums.map((run_num, idx) => ({
run_num,
result: job.results[idx],
reruns: job.reruns[idx],
rerun_result: job.rerun_results[idx],
url: job.urls[idx],
attempt_urls: job.attempt_urls[idx],
}));

return (
<div
key={`${job.name}-runs`}
className="p-3 bg-gray-100"
style={{ marginLeft: "4.5rem", marginTop: "-2.0rem" }}
>
<div>
{runs.length > 0 ? (
runs.map((run) => {
const emoji =
run.result === "Pass"
? "✅"
: run.result === "Fail"
? "❌"
: "⚠️";
return (
<span key={`${job.name}-runs-${run.run_num}`}>
<a href={run.url}>
{emoji} {run.run_num}
<div key={`${job.name}-runs`} className="p-3 bg-gray-100">
<div className="flex flex-wrap gap-4">
{runEntries.map(({
run_num,
result,
url,
reruns,
rerun_result,
attempt_urls
}, idx) => {
const allResults = rerun_result
? [result, ...rerun_result]
: [result];

const runStatuses = allResults.map((result, idx) =>
`${allResults.length - idx}. ${result === 'Pass'
? '✅ Success'
: result === 'Fail'
? '❌ Fail'
: '⚠️ Warning'}`);

// IDs can't have a '/'...
const sanitizedJobName = job.name.replace(/[^a-zA-Z0-9-_]/g, '');

const badgeReruns = `reruns-${sanitizedJobName}-${run_num}`;

rerunRefs.current[badgeReruns] = rerunRefs.current[badgeReruns]
|| React.createRef();

return (
<div key={run_num} className="flex">
<div key={idx} className="flex items-center">
{/* <a href={url} target="_blank" rel="noopener noreferrer"> */}
<a href={attempt_urls[0]} target="_blank" rel="noopener noreferrer">
{getRunStatusIcon(allResults)} {run_num}
</a>
&nbsp;&nbsp;&nbsp;&nbsp;
</span>
);
})
) : (
<div>No Nightly Runs associated with this job</div>
)}
</div>
{reruns > 0 &&(
<span className="p-overlay-badge">
<sup className="text-xs font-bold align-super ml-1"
onMouseEnter={(e) =>
rerunRefs.current[badgeReruns].current.toggle(e)}>
{reruns+1}
</sup>
<OverlayPanel ref={rerunRefs.current[badgeReruns]} dismissable
onMouseLeave={(e) =>
rerunRefs.current[badgeReruns].current.toggle(e)}>
<ul className="bg-white border rounded shadow-lg p-2">
{runStatuses.map((status, index) => (
<li key={index} className="p-2 hover:bg-gray-200">
<a
href={attempt_urls[index] || `${url}/attempts/${index}`}
target="_blank"
rel="noopener noreferrer">
{status}
</a>
</li>
))}
</ul>
</OverlayPanel>
</span>
)}
</div>
);
})}
</div>
</div>
);
Expand All @@ -179,6 +238,7 @@ useEffect(() => {
onRowToggle={(e) => setExpandedRows(e.data)}
loading={loading}
emptyMessage="No results found."
sortField="fails"
>
<Column expander/>
<Column
Expand All @@ -195,6 +255,7 @@ useEffect(() => {
header = "Runs"
className="whitespace-nowrap px-2"
sortable />
<Column field = "total_reruns" header = "Reruns" sortable/>
<Column field = "fails" header = "Fails" sortable/>
<Column field = "skips" header = "Skips" sortable/>
<Column
Expand All @@ -214,6 +275,7 @@ useEffect(() => {
onRowToggle={(e) => setExpandedRows(e.data)}
loading={loading}
emptyMessage="No results found."
sortField="fails"
>
<Column expander/>
<Column
Expand All @@ -230,6 +292,7 @@ useEffect(() => {
header = "Runs"
className="whitespace-nowrap px-2"
sortable />
<Column field = "total_reruns" header = "Reruns" sortable/>
<Column field = "fails" header = "Fails" sortable/>
<Column field = "skips" header = "Skips" sortable/>
<Column
Expand Down
Loading

0 comments on commit 8a3da7e

Please sign in to comment.