Skip to content

Commit

Permalink
Add ITP code review tracking dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
illicitonion committed Nov 26, 2024
1 parent 08233ac commit 80611dd
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 0 deletions.
18 changes: 18 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&amp;family=Raleway:wght@300;600;800;900&amp;family=Lato:wght@500;800&amp;display=swap" rel="stylesheet" media="all" onload="this.media=&quot;all&quot;">
<meta charset="UTF-8">
<style type="text/css">
body {
font-family: Raleway, sans-serif;
}
</style>
</head>
<body>
<h1>Tracking for programme progress</h1>
<ul>
<li><a href="itd-prs">ITD code reviews</a></li>
</ul>
</body>
</html>
66 changes: 66 additions & 0 deletions docs/itd-prs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<title>ITP PRs needing review</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&amp;family=Raleway:wght@300;600;800;900&amp;family=Lato:wght@500;800&amp;display=swap" rel="stylesheet" media="all" onload="this.media=&quot;all&quot;">
<meta charset="UTF-8">
<style type="text/css">
body {
font-family: Raleway, sans-serif;
}
#overview {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 40px;
}
.overview-card {
padding: 0px 5px 20px 5px;
text-align: center;
}
.age-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.age-bucket .count {
font-weight: bolder;
}
.status-bad {
background-color: red;
}
.status-medium {
background-color: orange;
}
.status-good {
background-color: green;
}
</style>
</head>
<body>
<h1>ITP PRs needing review</h1>
<div id="overview"></div>
<div id="pr-list">Loading...</div>
<script type="text/javascript" src="index.js"></script>

<template class="overview-card">
<div class="overview-card">
<h3 class="module"></h2>
<div class="summary-heading">
PRs needing review from:
<div class="age-container">
<div class="age-bucket this-week">This week<br /><span class="count"></span></div>
<div class="age-bucket this-month">This month<br /><span class="count"></span></div>
<div class="age-bucket old">Older<br /><span class="count"></span></div>
</div>
<div class="summary-values"></div>
</div>
</template>
<template class="pr-list">
<h2 class="module"></h2>
<ul class="pr-list">
</ul>
</template>
<template class="pr-in-list">
<li><span class="emoji"></span> <a class="pr-link"></a> (<a class="user-link"></a> - #<span class="pr-number"></span>)</li>
</template>
</body>
</html>
132 changes: 132 additions & 0 deletions docs/itd-prs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
const modules = [
"Module-User-Focused-Data",
"Module-Structuring-And-Testing-Data",
"Module-Data-Groups",
"Module-Data-Flows",
];
const ageToEmoji = {
"this week": "🟢",
"this month": "🟠",
"old": "🔴",
};
const now = new Date();

class PR {
constructor(url, number, userName, userUrl, title, module, createdAge, updatedAge) {
this.url = url;
this.number = number;
this.userName = userName;
this.userUrl = userUrl;
this.title = title;
this.module = module;
this.createdAge = createdAge;
this.updatedAge = updatedAge;
}
}

const awaitingReviewByAge = {};
const prsByModule = {};

function computeStatusClass(awaitingReview) {
if (awaitingReview["old"] > 0 || awaitingReview["this month"] > 0 || awaitingReview["this week"] > 20) {
return "status-bad";
} else if (awaitingReview["this week"] > 10) {
return "status-medium";
} else {
return "status-good";
}
}

function daysToMilliseconds(days) {
return days * 24 * 60 * 60 * 1000;
}

function identifyAge(date) {
const millis = now - date;
if (millis < daysToMilliseconds(7)) {
return "this week";
} else if (millis < daysToMilliseconds(7 * 4)) {
return "this month";
} else {
return "old";
}
}

async function onLoad() {
for (const module of modules) {
awaitingReviewByAge[module] = {
"this week": 0,
"this month": 0,
"old": 0,
};
prsByModule[module] = [];

let response = await fetch(`https://github-issue-proxy.illicitonion.com/cached/1/repos/CodeYourFuture/${module}/pulls`);
let prs = await response.json();

for (const pr of prs) {
if (pr.state !== "open") {
continue;
}
const needsReview = pr.labels.some((label) => label.name === "Needs Review");
if (!needsReview) {
continue;
}
const createdAt = new Date(Date.parse(pr["created_at"]));
const updatedAt = new Date(Date.parse(pr["updated_at"]));

const prObj = new PR(pr.html_url, pr.number, pr.user.login, pr.user.html_url, pr.title, module, identifyAge(createdAt), identifyAge(updatedAt));
awaitingReviewByAge[module][prObj.updatedAge]++;
prsByModule[module].push(prObj);
}
prsByModule[module].sort((l, r) => {
if (l.updatedAge > r.updatedAge) {
return 1;
} else if (l.updatedAge < r.updatedAge) {
return -1;
} else {
return l.number - r.number;
}
});
}

document.querySelector("#pr-list").innerText = "";

for (const module of modules) {
const awaitingReview = awaitingReviewByAge[module];
const totalPending = Object.values(awaitingReview).reduce((acc, cur) => acc + cur, 0);

const overviewCard = document.querySelector("template.overview-card").content.cloneNode(true);
overviewCard.querySelector(".module").innerText = `${module} (${totalPending})`;
for (const [age, count] of Object.entries(awaitingReview)) {
overviewCard.querySelector(`.age-bucket.${age.replaceAll(" ", "-")} .count`).innerText = count;
}
//`${} / ${awaitingReview["this month"]} / ${awaitingReview["old"]}`;
overviewCard.querySelector(".overview-card").classList.add(computeStatusClass(awaitingReview));
document.querySelector("#overview").appendChild(overviewCard);

if (totalPending) {
const modulePrList = document.querySelector("template.pr-list").content.cloneNode(true);
modulePrList.querySelector(".module").innerText = `${module} (${totalPending})`;
for (const pr of prsByModule[module]) {
const prInList = document.querySelector("template.pr-in-list").content.cloneNode(true);

prInList.querySelector(".emoji").innerText = ageToEmoji[pr.updatedAge];

const prLink = prInList.querySelector("a.pr-link");
prLink.href = pr.url;
prLink.innerText = `${pr.title}`;

const userLink = prInList.querySelector("a.user-link");
userLink.href = pr.userUrl;
userLink.innerText = `${pr.userName}`;

prInList.querySelector(".pr-number").innerText = pr.number;
modulePrList.appendChild(prInList);
}
document.querySelector("#pr-list").appendChild(modulePrList);
}
}
}

onLoad();

0 comments on commit 80611dd

Please sign in to comment.