Skip to content

Commit

Permalink
Merge pull request kata-containers#16 from a1icja/main
Browse files Browse the repository at this point in the history
migration: Move from vanilla JS to Next.JS
sprt authored Nov 4, 2024
2 parents 19ec495 + 74d9dbd commit 9b993f0
Showing 24 changed files with 6,393 additions and 302 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
100 changes: 100 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Reference: https://github.com/actions/starter-workflows/blob/main/pages/nextjs.yml

# Sample workflow for building and deploying a Next.js site to GitHub Pages
#
# To get started with Next.js see: https://nextjs.org/docs/getting-started
#
name: Deploy Next.js site to Pages

on:
# When a commit is made to the main branch
push:
branches:
- main

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Detect package manager
id: detect-package-manager
run: |
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=yarn" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/package.json" ]; then
echo "manager=npm" >> $GITHUB_OUTPUT
echo "command=ci" >> $GITHUB_OUTPUT
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
exit 0
else
echo "Unable to determine package manager"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: ${{ steps.detect-package-manager.outputs.manager }}

- name: Setup Pages
uses: actions/configure-pages@v4

- name: Restore cache
uses: actions/cache@v4
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}

- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next build

- name: Add .nojekyll to out directory
run: touch out/.nojekyll

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./out

# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
6 changes: 4 additions & 2 deletions .github/workflows/fetch-ci-nightly-data.yaml
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@ run-name: Fetch CI Nightly Data
on:
schedule:
- cron: '0 4 * * *'
workflow_dispatch:

jobs:
fetch-and-commit-data:
runs-on: ubuntu-22.04
@@ -13,7 +15,7 @@ jobs:
- name: Update dashboard data
run: |
# fetch ci nightly data as temporary file
node js/fetch-ci-nightly-data.js | tee tmp-data.js
node scripts/fetch-ci-nightly-data.js | tee tmp-data.json
# switch to a branch specifically for holding latest data
git config --global user.name "GH Actions Workflow"
git config --global user.email "<gha@runner>"
@@ -23,7 +25,7 @@ jobs:
git reset HEAD~1
# overwrite the old data
mkdir -p data/
mv tmp-data.js data/ci-nightly-data.js
mv tmp-data.json data/job_stats.json
# commit
git add data
git commit -m '[skip ci] latest ci nightly data'
41 changes: 39 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,39 @@
_site/
*.swp
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem
.vscode/*

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

/job_stats.json
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Kata Containers Test Dashboard

This repository contains the **Kata Containers Test Dashboard**, a web application that visualizes data for the nightly tests run by the Kata Containers repository. Built using **Next.js** and styled with **TailwindCSS**, this dashboard provides a simple and efficient interface to monitor test results, leveraging modern frontend technologies to ensure responsive and scalable performance.

## Features
- Fetches nightly CI test data using custom scripts.
- Displays weather-like icons to reflect test statuses (e.g., sunny for success, stormy for failures).
- Utilizes **Next.js** for server-side rendering and optimized builds.
- **TailwindCSS** for responsive, utility-first styling.
- Integration of **PrimeReact** components for UI elements.

---

## Project Structure

```bash
.
├── next.config.js # Next.js configuration
├── package.json # Project dependencies and scripts
├── package-lock.json # Dependency lock file
├── pages
│ ├── _app.js # Application wrapper for global setup
│ └── index.js # Main dashboard page
├── postcss.config.js # PostCSS configuration for TailwindCSS
├── public
│ ├── cloudy.svg # Weather icons for test statuses
│ ├── favicon.ico
│ ├── partially-sunny.svg
│ ├── rainy.svg
│ ├── stormy.svg
│ └── sunny.svg
├── README.md # Project documentation (this file)
├── scripts
│ └── fetch-ci-nightly-data.js # Script to fetch nightly test data
├── styles
│ └── globals.css # Global CSS imports
└── tailwind.config.js # TailwindCSS configuration
```

### Key Files
- **`pages/index.js`**: The main entry point of the dashboard, displaying test results and their statuses.
- **`scripts/fetch-ci-nightly-data.js`**: Custom script to retrieve CI data for the dashboard.
- **`styles/globals.css`**: Custom global styles, mainly extending the TailwindCSS base.
- **`public/`**: Contains static assets like icons representing different test statuses.

---

## Setup Instructions

Follow these steps to set up the development environment for the Kata Containers Test Dashboard:

### Prerequisites
- [**Node.js**](https://nodejs.org/en/download) (version 18.x or later recommended)
- **npm** (comes with Node.js)

### Installation

1. **Clone the repository**:
```bash
git clone https://github.com/kata-containers/kata-containers.github.io.git
cd kata-containers.github.io
```

2. **Install dependencies**:
Run the following command to install both the project dependencies and development dependencies.
```bash
npm install
```

### Development

3. **Run the development server**:
Start the Next.js development server with hot-reloading enabled.
```bash
node scripts/fetch-ci-nightly-data.js > job_stats.json
npm run dev
```

The app will be available at [http://localhost:3000](http://localhost:3000).

### Production

4. **Build for production**:
To create an optimized production build, run:
```bash
npm run build
```

5. **Start the production server**:
After building, you can start the server:
```bash
npm start
```

### Scripts

- **Fetch CI Nightly Data**:
The `fetch-ci-nightly-data.js` script can be executed manually to pull the latest CI test data from the Kata Containers repository:
```bash
node scripts/fetch-ci-nightly-data.js > job_stats.json
```
50 changes: 50 additions & 0 deletions components/weatherTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Image from "next/image";
import { basePath } from "../next.config.js";

const icons = [
"sunny.svg",
"partially-sunny.svg",
"cloudy.svg",
"rainy.svg",
"stormy.svg",
];

export const getWeatherIndex = (stat) => {
let fail_rate = 0;
fail_rate = (stat["fails"] + stat["skips"]) / 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)) {
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}`}
alt="weather"
width={32}
height={32}
// priority
/>
</div>
);
};
34 changes: 0 additions & 34 deletions index.html

This file was deleted.

109 changes: 0 additions & 109 deletions js/dashboard.js

This file was deleted.

155 changes: 0 additions & 155 deletions js/fetch-ci-nightly-data.js

This file was deleted.

14 changes: 14 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
reactStrictMode: true,
output: 'export',
basePath: "",
images: {
unoptimized: true,
},
webpack: (config, { dev }) => {
if (dev) {
config.devtool = false;
}
return config;
},
}
5,513 changes: 5,513 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "nextjs-basic",
"private": true,
"scripts": {
"dev": "NODE_ENV=development next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "^14.2.13",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
"primereact": "^10.8.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-transition-group": "^4.4.5"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"eslint": "8.57.0",
"eslint-config-next": "^14.2.13",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13"
}
}
11 changes: 11 additions & 0 deletions pages/_app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import '../styles/globals.css';
import 'primereact/resources/themes/saga-blue/theme.css';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
import 'primeflex/primeflex.css';

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp
186 changes: 186 additions & 0 deletions pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { useEffect, useState } from "react";
import { DataTable } from "primereact/datatable";
import { Column } from "primereact/column";
import { weatherTemplate, getWeatherIndex } from "../components/weatherTemplate";


export default function Home() {
const [loading, setLoading] = useState(true);
const [jobs, setJobs] = useState([]);
const [rows, setRows] = useState([]);
const [expandedRows, setExpandedRows] = useState([]);

useEffect(() => {
const fetchData = async () => {
let data = {};

if (process.env.NODE_ENV === "development") {
data = (await import("../job_stats.json")).default;
} else {
const response = await fetch(
"https://raw.githubusercontent.com/kata-containers/kata-containers.github.io" +
"/refs/heads/latest-dashboard-data/data/job_stats.json"
);
data = await response.json();
}

try {
const jobData = Object.keys(data).map((key) => {
const job = data[key];
return { name: key, ...job };
});
setJobs(jobData);
} catch (error) {
// TODO: Add pop-up/toast message for error
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
};

fetchData();
}, []);

useEffect(() => {
setLoading(true);

// Create rows to set into table.
const rows = jobs.map((job) => ({
...job,
weather: getWeatherIndex(job),
}));
setRows(rows);
setLoading(false);
}, [jobs]);

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

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

setExpandedRows(updatedExpandedRows);
};

// 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 rowExpansionTemplate = (data) => {
const job = jobs.find((job) => job.name === data.name);

// 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],
});
}

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}
</a>
&nbsp;&nbsp;&nbsp;&nbsp;
</span>
);
})
) : (
<div>No Nightly Runs associated with this job</div>
)}
</div>
</div>
);
};

const renderTable = () => (
<DataTable
value={rows}
expandedRows={expandedRows}
stripedRows
rowExpansionTemplate={rowExpansionTemplate}
onRowToggle={(e) => setExpandedRows(e.data)}
loading={loading}
emptyMessage="No results found."
>
<Column expander style={{ width: "5rem" }} />
<Column
field="name"
header="Name"
body={nameTemplate}
filter
sortable
maxConstraints={4}
filterHeader="Filter by Name"
filterPlaceholder="Search..."
/>
<Column field="required" header="Required" sortable />
<Column field="runs" header="Runs" sortable />
<Column field="fails" header="Fails" sortable />
<Column field="skips" header="Skips" sortable />
<Column
field="weather"
header="Weather"
body={weatherTemplate}
sortable
/>
</DataTable>
);

return (
<div className="text-center">
<h1
className={
"text-4xl mt-4 mb-0 underline text-inherit hover:text-blue-500"
}
>
<a
href={
"https://github.com/kata-containers/kata-containers/" +
"actions/workflows/ci-nightly.yaml"
}
target="_blank"
rel="noopener noreferrer"
>
Kata CI Dashboard
</a>
</h1>

<main
className={
"m-0 h-full p-4 overflow-x-hidden overflow-y-auto bg-surface-ground font-normal text-text-color antialiased select-text"
}
>
<div>{renderTable()}</div>
<div className="mt-4 text-lg">Total Rows: {rows.length}</div>
</main>
</div>
);
}
6 changes: 6 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
322 changes: 322 additions & 0 deletions scripts/fetch-ci-nightly-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
//
// This script is designed to query the github API for a useful summary
// of recent nightly CI results (though this could be expanded later).
//
// The general flow is as follows:
// - It queries the github API for the workflow runs for the nightly CI (e.g.
// the last 10 nights/runs).
// - For each of those runs, it queries the API for all the jobs data (e.g.
// data on the tdx or snp jobs in each run).
// - It reorganizes and summarizes those results in an array, where each
// entry is information about a job and how it has performed over the last
// few runs (e.g. pass or fail).
//
// Count of API calls:
// 1 for the batch of last 10 nightly run
// 20 for 2 batches of >100 jobs for each of the 10 runs
// 1 for the main branch details (for list of 'Required' jobs)
// 1 for the last 10 closed pull requests
// 20 for all 4 batches of checks (max of a hundred each) for each of the 5 PRs
// X Other?
// TOTAL: 43?
// LIMIT: 60 per hour // curl https://api.github.com/rate_limit
// TODO: Further explore using the GraphQL API, which permits more narrowly targeted queries

// Github API URL for the kata-container ci-nightly workflow's runs. This
// will only get the most recent 10 runs ('page' is empty, and 'per_page=10').
const ci_nightly_runs_url =
"https://api.github.com/repos/" +
"kata-containers/kata-containers/actions/workflows/" +
"ci-nightly.yaml/runs?per_page=10";
// NOTE: For checks run on main after push/merge, do similar call with: payload-after-push.yaml

// NOTE: pull_requests attribute often empty if commit/branch from a fork: https://github.com/orgs/community/discussions/25220
// Current approach (there may be better alternatives) is to:
// 1. retrieve the last 10 closed PRs
// 2. fetch the checks for each PR (using the head commit SHA)
const pull_requests_url =
"https://api.github.com/repos/" +
"kata-containers/kata-containers/pulls?state=closed&per_page=";
const pr_checks_url = // for our purposes, 'check' refers to a job in the context of a PR
"https://api.github.com/repos/" +
"kata-containers/kata-containers/commits/"; // will be followed by {commit_sha}/check-runs
// Max of 100 per page, w/ little *over* 300 checks total, so that's 40 calls total for 10 PRs

// Github API URL for the main branch of the kata-containers repo.
// Used to get the list of required jobs.
const main_branch_url =
"https://api.github.com/repos/" +
"kata-containers/kata-containers/branches/main";

// The number of jobs to fetch from the github API on each paged request.
const jobs_per_request = 100;
// The last X closed PRs to retrieve
const pr_count = 5;
// Complete list of jobs (from the CI nightly run)
const job_names = new Set();
// Count of the number of fetches
let fetch_count = 0;

// Perform a github API request for workflow runs.
async function fetch_workflow_runs() {
fetch_count++;
return fetch(ci_nightly_runs_url, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
}).then(function (response) {
return response.json();
});
}

// Perform a github API request for the last pr_count closed PRs
async function fetch_pull_requests() {
fetch_count++;
const prs_url = `${pull_requests_url}${pr_count}`;
return fetch(prs_url, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
}).then(function (response) {
return response.json();
});
}

// Perform a github API request for a list of "Required" jobs
async function fetch_main_branch() {
return fetch(main_branch_url, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
}).then(function (response) {
return response.json();
});
}

// Get job data about a workflow run
// Returns a map that has information about a run, e.g.
// ID assigned by github
// run number assigned by github
// 'jobs' array, which has some details about each job from that run
function get_job_data(run) {
// Perform the actual (paged) request
async function fetch_jobs_by_page(which_page) {
fetch_count++;
const jobs_url =
run["jobs_url"] + "?per_page=" + jobs_per_request + "&page=" + which_page;
return fetch(jobs_url, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
}).then(function (response) {
return response.json();
});
}

// Fetch the jobs for a run. Extract a few details from the response,
// including the job name and whether it concluded successfully.
function fetch_jobs(p) {
return fetch_jobs_by_page(p).then(function (jobs_request) {
for (const job of jobs_request["jobs"]) {
if (!job_names.has(job["name"])) {
job_names.add(job["name"])
};
run_with_job_data["jobs"].push({
name: job["name"],
run_id: job["run_id"],
html_url: job["html_url"],
conclusion: job["conclusion"],
});
}
if (p * jobs_per_request >= jobs_request["total_count"]) {
return run_with_job_data;
}
return fetch_jobs(p + 1);
});
}

const run_with_job_data = {
id: run["id"],
run_number: run["run_number"],
created_at: run["created_at"],
conclusion: null,
jobs: [],
};
if (run["status"] == "in_progress") {
return new Promise((resolve) => {
resolve(run_with_job_data);
});
}
run_with_job_data["conclusion"] = run["conclusion"];
return fetch_jobs(1);
}

function get_check_data(pr) {
// Perform a github API request for a list of commits for a PR (takes in the PR's head commit SHA)
async function fetch_checks_by_page(which_page) {
fetch_count++;
const checks_url =
pr_checks_url + prs_with_check_data["commit_sha"] + "/check-runs" + "?per_page=" + jobs_per_request + "&page=" + which_page;
return fetch(checks_url, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
}).then(function (response) {
return response.json();
});
}

// Fetch the checks for a PR.
function fetch_checks(p) {
if (p > 60) {
throw new Error(`Attempting to make too many API calls: ${p}`)
}
return fetch_checks_by_page(p)
.then(function (checks_request) {
for (const check of checks_request["check_runs"]) {
// NOTE: For now, excluding checks that are not also run in CI Nightly
if (job_names.has(check["name"])) {
prs_with_check_data["checks"].push({
name: check["name"],
conclusion: check["conclusion"]
});
}
}
if (p * jobs_per_request >= checks_request["total_count"]) {
return prs_with_check_data;
}
return fetch_checks(p + 1);
})
.catch(function (error) {
console.error("Error fetching checks:", error);
throw error;
});
}

// Extract list of objects with PR commit SHAs, PR URLs, and PR number (i.e. id)
const prs_with_check_data = {
html_url: pr["html_url"], // link to PR page
number: pr["number"], // PR number (used as PR id); displayed on dashboard
commit_sha: pr["head"]["sha"], // For getting checks run on PR branch
// NOTE: using for now b/c we'll be linking to the PR page, where these checks are listed...
checks: [], // will be populated later with fetch_checks
};

return fetch_checks(1);
}

// Extract list of required jobs (i.e. main branch details: protection: required_status_checks: contexts)
function get_required_jobs(main_branch) {
const required_jobs = main_branch["protection"]["required_status_checks"]["contexts"];
return required_jobs;
}

// Calculate and return job stats across all runs
function compute_job_stats(runs_with_job_data, prs_with_check_data, required_jobs) {
const job_stats = {};
for (const run of runs_with_job_data) {
for (const job of run["jobs"]) {
if (!(job["name"] in job_stats)) {
job_stats[job["name"]] = {
runs: 0, // e.g. 10, if it ran 10 times
fails: 0, // e.g. 3, if it failed 3 out of 10 times
skips: 0, // e.g. 7, if it got skipped the other 7 times
urls: [], // ordered list of URLs associated w/ each run
results: [], // an array of strings, e.g. 'Pass', 'Fail', ...
run_nums: [], // ordered list of github-assigned run numbers

pr_runs: 0,
pr_fails: 0,
pr_skips: 0,
pr_urls: [], // list of PR URLs that this job is associated with
pr_results: [], // list of job statuses for the PRs in which the job was run
pr_nums: [], // list of PR numbers that this job is associated with
};
}
const job_stat = job_stats[job["name"]];
job_stat["runs"] += 1;
job_stat["run_nums"].push(run["run_number"]);
job_stat["urls"].push(job["html_url"]);
if (job["conclusion"] != "success") {
if (job["conclusion"] == "skipped") {
job_stat["skips"] += 1;
job_stat["results"].push("Skip");
} else {
// failed or cancelled
job_stat["fails"] += 1;
job_stat["results"].push("Fail");
}
} else {
job_stat["results"].push("Pass");
}
job_stat["required"] = required_jobs.includes(job["name"]);
}
}
for (const pr of prs_with_check_data) {
for (const check of pr["checks"]) {
if ((check["name"] in job_stats)) {
const job_stat = job_stats[check["name"]];
job_stat["pr_runs"] += 1;
job_stat["pr_urls"].push(pr["html_url"])
job_stat["pr_nums"].push(pr["number"])
if (check["conclusion"] != "success") {
if (check["conclusion"] == "skipped") {
job_stat["pr_skips"] += 1;
job_stat["pr_results"].push("Skip");
} else {
// failed or cancelled
job_stat["pr_fails"] += 1;
job_stat["pr_results"].push("Fail");
}
} else {
job_stat["pr_results"].push("Pass");
}
}
}
}
return job_stats;
}

async function main() {
// Fetch recent workflow runs via the github API
const workflow_runs = await fetch_workflow_runs();

// Fetch required jobs from main branch
const main_branch = await fetch_main_branch();
const required_jobs = get_required_jobs(main_branch);

// Fetch job data for each of the runs.
// Store all of this in an array of maps, runs_with_job_data.
const promises_buf = [];
for (const run of workflow_runs["workflow_runs"]) {
promises_buf.push(get_job_data(run));
}
runs_with_job_data = await Promise.all(promises_buf);

// Fetch recent pull requests via the github API
const pull_requests = await fetch_pull_requests();

// Fetch last pr_count closed PRs
// Store all of this in an array of maps, prs_with_check_data.
const promises_buffer = [];
for (const pr of pull_requests) {
promises_buffer.push(get_check_data(pr));
}
prs_with_check_data = await Promise.all(promises_buffer);

// Transform the raw details of each run and its jobs' results into a
// an array of just the jobs and their overall results (e.g. pass or fail,
// and the URLs associated with them).
const job_stats = compute_job_stats(runs_with_job_data, prs_with_check_data, required_jobs);

// Write the job_stats to console as a JSON object
console.log(JSON.stringify(job_stats));
}


main();
3 changes: 3 additions & 0 deletions styles/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
15 changes: 15 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",

// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {},
},
plugins: [],
}

0 comments on commit 9b993f0

Please sign in to comment.