diff --git a/.github/workflows/fectch-ci-data.yml b/.github/workflows/fectch-ci-data.yml new file mode 100644 index 0000000..56e5517 --- /dev/null +++ b/.github/workflows/fectch-ci-data.yml @@ -0,0 +1,38 @@ +name: Fetch CI Data +run-name: Fetch CI Data +on: + schedule: + - cron: '0 */2 * * *' + workflow_dispatch: + +jobs: + fetch-and-commit-data: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Update dashboard data + run: | + # fetch ci nightly data as temporary file + TOKEN=${{ secrets.GITHUB_TOKEN }} node scripts/fetch-ci-nightly-data.js | tee tmp-data.json + TOKEN=${{ secrets.GITHUB_TOKEN }} node scripts/fetch-ci-pr-data.js | tee tmp-data2.json + + # switch to a branch specifically for holding latest data + git config --global user.name "GH Actions Workflow" + git config --global user.email "" + git fetch --all + git checkout latest-dashboard-data + + # back out whatever data was there + git reset HEAD~1 + + # overwrite the old data + mkdir -p data/ + mv tmp-data.json data/job_stats.json + mv tmp-data2.json data/check_stats.json + + # commit + git add data + git commit -m '[skip ci] latest ci nightly data' + git push --force \ No newline at end of file diff --git a/package.json b/package.json index b1391f9..b54bc49 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,13 @@ "private": true, "scripts": { "dev": "NODE_ENV=development next dev", + "win-dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { - "next": "^14.2.13", + "next": "^15.0.2", "primeflex": "^3.3.1", "primeicons": "^6.0.1", "primereact": "^10.8.3", @@ -18,9 +19,10 @@ }, "devDependencies": { "autoprefixer": "^10.4.20", + "dotenv": "^16.4.5", "eslint": "8.57.0", "eslint-config-next": "^14.2.13", "postcss": "^8.4.47", "tailwindcss": "^3.4.13" } -} +} \ No newline at end of file diff --git a/pages/index.js b/pages/index.js index 087c213..189a7fa 100644 --- a/pages/index.js +++ b/pages/index.js @@ -6,31 +6,38 @@ 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([]); + const [loading, setLoading] = useState(true); + const [jobs, setJobs] = useState([]); + const [checks, setChecks] = useState([]); + const [rowsPR, setRowsPR] = useState([]); + const [rowsNightly, setRowsNightly] = useState([]); + const [expandedRows, setExpandedRows] = useState([]); + const [display, setDisplay] = useState("nightly"); + useEffect(() => { const fetchData = async () => { - let data = {}; + let nightlyData = {}; + let prData = {}; if (process.env.NODE_ENV === "development") { - data = (await import("../job_stats.json")).default; + nightlyData = (await import("../localData/job_stats.json")).default; + prData = (await import("../localData/check_stats.json")).default; } else { - const response = await fetch( + nightlyData = 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(); + ).then((res) => res.json()); + prData = await fetch( + "https://raw.githubusercontent.com/kata-containers/kata-containers.github.io" + + "/refs/heads/latest-dashboard-data/data/check_stats.json" + ).then((res) => res.json()); } try { - const jobData = Object.keys(data).map((key) => { - const job = data[key]; - return { name: key, ...job }; - }); - setJobs(jobData); + const mapData = (data) => Object.keys(data).map((key) => ({ name: key, ...data[key] })); + setJobs(mapData(nightlyData)); + setChecks(mapData(prData)); } catch (error) { // TODO: Add pop-up/toast message for error console.error("Error fetching data:", error); @@ -42,17 +49,50 @@ export default function Home() { fetchData(); }, []); +// Filter and set the rows for Nightly view. +useEffect(() => { + setLoading(true); + let filteredJobs = jobs; + //Set the rows for the table. + setRowsNightly( + filteredJobs.map((job) => ({ + name : job.name, + runs : job.runs, + fails : job.fails, + skips : job.skips, + required : job.required, + weather : getWeatherIndex(job), + })) + ); + setLoading(false); +}, [jobs]); + +// Filter and set the rows for PR Checks view. +useEffect(() => { + setLoading(true); + let filteredChecks = checks + + //Set the rows for the table. + setRowsPR( + filteredChecks.map((check) => ({ + name : check.name, + runs : check.runs, + fails : check.fails, + skips : check.skips, + required : check.required, + weather : getWeatherIndex(check), + })) + ); + setLoading(false); +}, [checks]); + + // Close all rows on view switch. + // Needed because if view is switched, breaks expanded row toggling. useEffect(() => { - setLoading(true); + setExpandedRows([]) + }, [display]); + - // 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); @@ -67,6 +107,11 @@ export default function Home() { 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 ( @@ -77,7 +122,10 @@ export default function Home() { }; const rowExpansionTemplate = (data) => { - const job = jobs.find((job) => job.name === data.name); + const job = (display === "nightly" + ? jobs + : checks).find((job) => job.name === data.name); + // Prepare run data const runs = []; @@ -121,9 +169,10 @@ export default function Home() { ); }; - const renderTable = () => ( + // Render table for nightly view. + const renderNightlyTable = () => ( - + - - - - + + + + + + + ); + + const renderPRTable = () => ( + setExpandedRows(e.data)} + loading={loading} + emptyMessage="No results found." + > + + + + + + ); + return ( -
+ <> + Kata CI Dashboard +
+

+ + Kata CI Dashboard + +

-

- - Kata CI Dashboard - -

- -
-
{renderTable()}
-
Total Rows: {rows.length}
-
-
+
+
+ + +
+
+ +
+ Total Rows: {display === "prchecks" ? rowsPR.length : rowsNightly.length} +
+ +
+
{display === "prchecks" ? renderPRTable() : renderNightlyTable()}
+
+
+ ); -} +} \ No newline at end of file diff --git a/scripts/fetch-ci-nightly-data.js b/scripts/fetch-ci-nightly-data.js index 804745b..c5392db 100644 --- a/scripts/fetch-ci-nightly-data.js +++ b/scripts/fetch-ci-nightly-data.js @@ -11,110 +11,117 @@ // 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 - +// To run locally: +// node --require dotenv/config scripts/fetch-ci-nightly-data.js +// +// .env file with: +// NODE_ENV=development +// TOKEN=token + +// Set token used for making Authorized GitHub API calls. +// In dev, set by .env file; in prod, set by GitHub Secret. +const TOKEN = process.env.TOKEN; + // 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'). +// will only get the most recent 10 runs ('per_page=10'). +const total_runs = 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 + `ci-nightly.yaml/runs?per_page=${total_runs}`; + // NOTE: For checks run on main after push/merge, + // do similar call with: payload-after-push.yaml. // 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"; +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 + +// 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, { + const response = await fetch(ci_nightly_runs_url, { headers: { Accept: "application/vnd.github+json", + Authorization: `token ${TOKEN}`, "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() { + if (!response.ok) { + throw new Error(`Failed to fetch workflow runs: ${response.status}: ` + + `${response.statusText}`); + } + + const json = await response.json(); 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(); - }); + return await json; } -// Perform a github API request for a list of "Required" jobs + +// Perform a github API request for a list of "Required" jobs. async function fetch_main_branch() { - return fetch(main_branch_url, { + const response = await fetch(main_branch_url, { headers: { Accept: "application/vnd.github+json", + Authorization: `token ${TOKEN}`, "X-GitHub-Api-Version": "2022-11-28", }, - }).then(function (response) { - return response.json(); }); + + if (!response.ok) { + throw new Error(`Failed to main branch: ${response.status}: ` + + `${response.statusText}`); + } + + const json = await response.json(); + fetch_count++; + // const contexts = json?.protection?.required_status_checks?.contexts; + return json; } -// Get job data about a workflow run + +// Extract list of required jobs. +// (i.e. main branch details: protection: required_status_checks: contexts) +function get_required_jobs(main_branch) { + return main_branch["protection"]["required_status_checks"]["contexts"]; +} + + +// 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 +// '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, { + const jobs_url = `${run["jobs_url"]}?per_page=${jobs_per_request}` + + `&page=${which_page}`; + const response = await fetch(jobs_url, { headers: { Accept: "application/vnd.github+json", + Authorization: `token ${TOKEN}`, "X-GitHub-Api-Version": "2022-11-28", }, - }).then(function (response) { - return response.json(); }); + + if (!response.ok) { + throw new Error(`Failed to fetch jobs: ${response.status}: ` + + `${response.statusText}`); + + } + + const json = await response.json(); + fetch_count++; + return await json; } // Fetch the jobs for a run. Extract a few details from the response, @@ -122,9 +129,6 @@ function get_job_data(run) { 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"], @@ -146,7 +150,7 @@ function get_job_data(run) { conclusion: null, jobs: [], }; - if (run["status"] == "in_progress") { + if (run["status"] === "in_progress") { return new Promise((resolve) => { resolve(run_with_job_data); }); @@ -154,70 +158,8 @@ function get_job_data(run) { 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) { +function compute_job_stats(runs_with_job_data, required_jobs) { const job_stats = {}; for (const run of runs_with_job_data) { for (const job of run["jobs"]) { @@ -229,13 +171,6 @@ function compute_job_stats(runs_with_job_data, prs_with_check_data, required_job 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"]]; @@ -257,31 +192,10 @@ function compute_job_stats(runs_with_job_data, prs_with_check_data, required_job 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(); @@ -296,27 +210,16 @@ async function main() { 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); - + let runs_with_job_data = await Promise.all(promises_buf); + // 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); + const job_stats = compute_job_stats(runs_with_job_data, required_jobs); // Write the job_stats to console as a JSON object console.log(JSON.stringify(job_stats)); } -main(); +main(); \ No newline at end of file diff --git a/scripts/fetch-ci-pr-data.js b/scripts/fetch-ci-pr-data.js new file mode 100644 index 0000000..ddba934 --- /dev/null +++ b/scripts/fetch-ci-pr-data.js @@ -0,0 +1,218 @@ +// +// This script is designed to query the github API for a useful summary +// of recent PR CI tests. +// +// The general flow is as follows: +// 1. retrieve the last 10 closed PRs +// 2. fetch the checks for each PR (using the head commit SHA) +// +// To run locally: +// node --require dotenv/config scripts/fetch-ci-pr-data.js +// .env file with: +// NODE_ENV=development +// TOKEN=token + + +// Set token used for making Authorized GitHub API calls +const TOKEN = process.env.TOKEN; // In dev, set by .env file; in prod, set by GitHub Secret + +// pull_requests attribute often empty if commit/branch from a fork: https://github.com/orgs/community/discussions/25220 +var pull_requests_url = + "https://api.github.com/repos/" + + "kata-containers/kata-containers/pulls?state=closed&per_page="; +var 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/checks. +var main_branch_url = + "https://api.github.com/repos/" + + "kata-containers/kata-containers/branches/main"; + + +// The number of checks to fetch from the github API on each paged request. +var results_per_request = 100; +// The last X closed PRs to retrieve +var pr_count = 10; +// Count of the number of fetches +var fetch_count = 0; + + +// Perform a github API request for the last pr_count closed PRs +async function fetch_pull_requests() { + const prs_url = `${pull_requests_url}${pr_count}`; + const response = await fetch(prs_url, { + headers: { + Accept: "application/vnd.github+json", + Authorization: `token ${TOKEN}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch pull requests: ${response.status}`); + } + + const json = await response.json(); + fetch_count++; + return json; +} + + +// Perform a github API request for a list of "Required" jobs +async function fetch_main_branch() { + const response = await fetch(main_branch_url, { + headers: { + Accept: "application/vnd.github+json", + Authorization: `token ${TOKEN}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch main branch data: ${response.status}`); + } + + const json = await response.json(); + fetch_count++; + const contexts = json?.protection?.required_status_checks?.contexts; + return json; +} + + +// Perform a github API request for a list of commits for a PR (takes in the PR's head commit SHA) +function get_check_data(pr) { + async function fetch_checks_by_page(which_page) { + var checks_url = `${pr_checks_url}${prs_with_check_data["commit_sha"]}/check-runs?per_page=${results_per_request}&page=${which_page}`; + const response = await fetch(checks_url, { + headers: { + Accept: "application/vnd.github+json", + Authorization: `token ${TOKEN}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch check data: ${response.status}`); + } + + const json = await response.json(); + fetch_count++; + return json; + } + + // Fetch the checks for a PR. + function fetch_checks(p) { + return fetch_checks_by_page(p) + .then(function (checks_request) { + // console.log('checks_request', checks_request); + for (const check of checks_request["check_runs"]) { + prs_with_check_data["checks"].push({ + name: check["name"], + conclusion: check["conclusion"] + }); + } + if (p * results_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) + var 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 + // commit_sha: pr["merge_commit_sha"], // For getting checks run on main branch after merge + // 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) { + var required_jobs = main_branch["protection"]["required_status_checks"]["contexts"]; + return required_jobs; +} + + +// Calculate and return check stats across all runs +function compute_check_stats(prs_with_check_data, required_jobs) { + var check_stats = {}; + for (const pr of prs_with_check_data) { + for (const check of pr["checks"]) { + if (!(check["name"] in check_stats)) { + check_stats[check["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: [], // list of PR URLs that this check is associated with + results: [], // list of check statuses for the PRs in which the check was run + run_nums: [], // list of PR numbers that this check is associated with + }; + } + if ((check["name"] in check_stats) && !check_stats[check["name"]]["run_nums"].includes(pr["number"])) { + var check_stat = check_stats[check["name"]]; + check_stat["runs"] += 1; + check_stat["urls"].push(pr["html_url"]) + check_stat["run_nums"].push(pr["number"]) + if (check["conclusion"] != "success") { + if (check["conclusion"] == "skipped") { + check_stat["skips"] += 1; + check_stat["results"].push("Skip"); + } else { + // failed or cancelled + check_stat["fails"] += 1; + check_stat["results"].push("Fail"); + } + } else { + check_stat["results"].push("Pass"); + } + check_stat["required"] = required_jobs.includes(check["name"]); + } + } + } + return check_stats; +} + + +async function main() { + // Fetch required jobs from main branch + var main_branch = await fetch_main_branch(); + var required_jobs = get_required_jobs(main_branch); + + // Fetch recent pull requests via the github API + var 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. + var promises_buffer = []; + for (const pr of pull_requests) { + promises_buffer.push(get_check_data(pr)); + } + prs_with_check_data = await Promise.all(promises_buffer); + // console.log("prs_with_check_data: ", prs_with_check_data); + + // Transform the raw details of each run and its checks' results into a + // an array of just the checks and their overall results (e.g. pass or fail, + // and the URLs associated with them). + var check_stats = compute_check_stats(prs_with_check_data, required_jobs); + + // Write the check_stats to console as a JSON object + console.log(JSON.stringify(check_stats)); + // console.log(JSON.stringify(required_jobs)); + +} + + +main(); \ No newline at end of file