From d2ba40b4d0c8d01824f4aa7bd160c047c7b2fdba Mon Sep 17 00:00:00 2001 From: Anna Finn Date: Mon, 18 Nov 2024 15:38:59 -0500 Subject: [PATCH] dashboard: Add view for PR runs Added a script that fetches PR data and created a separate view on the dashboard. Fixes #1 Signed-off-by: Anna Finn --- .github/workflows/fectch-ci-data.yml | 38 ++ package-lock.json | 619 +++++++++++++++++++++++---- package.json | 6 +- pages/index.js | 235 +++++++--- scripts/fetch-ci-nightly-data.js | 250 +++-------- scripts/fetch-ci-pr-data.js | 194 +++++++++ 6 files changed, 1010 insertions(+), 332 deletions(-) create mode 100644 .github/workflows/fectch-ci-data.yml create mode 100644 scripts/fetch-ci-pr-data.js diff --git a/.github/workflows/fectch-ci-data.yml b/.github/workflows/fectch-ci-data.yml new file mode 100644 index 0000000..030cd03 --- /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 diff --git a/package-lock.json b/package-lock.json index 0f1bbb7..27233a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "nextjs-basic", "dependencies": { - "next": "^14.2.13", + "next": "^15.0.2", "primeflex": "^3.3.1", "primeicons": "^6.0.1", "primereact": "^10.8.3", @@ -16,6 +16,7 @@ }, "devDependencies": { "autoprefixer": "^10.4.20", + "dotenv": "^16.4.5", "eslint": "8.57.0", "eslint-config-next": "^14.2.13", "postcss": "^8.4.47", @@ -47,6 +48,16 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -145,6 +156,367 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -246,9 +618,9 @@ } }, "node_modules/@next/env": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.13.tgz", - "integrity": "sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.3.tgz", + "integrity": "sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -262,9 +634,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.13.tgz", - "integrity": "sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.3.tgz", + "integrity": "sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==", "cpu": [ "arm64" ], @@ -278,9 +650,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.13.tgz", - "integrity": "sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.3.tgz", + "integrity": "sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==", "cpu": [ "x64" ], @@ -294,9 +666,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.13.tgz", - "integrity": "sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.3.tgz", + "integrity": "sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==", "cpu": [ "arm64" ], @@ -310,9 +682,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.13.tgz", - "integrity": "sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.3.tgz", + "integrity": "sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==", "cpu": [ "arm64" ], @@ -326,9 +698,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.13.tgz", - "integrity": "sha512-aVc7m4YL7ViiRv7SOXK3RplXzOEe/qQzRA5R2vpXboHABs3w8vtFslGTz+5tKiQzWUmTmBNVW0UQdhkKRORmGA==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.3.tgz", + "integrity": "sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==", "cpu": [ "x64" ], @@ -342,9 +714,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.13.tgz", - "integrity": "sha512-4wWY7/OsSaJOOKvMsu1Teylku7vKyTuocvDLTZQq0TYv9OjiYYWt63PiE1nTuZnqQ4RPvME7Xai+9enoiN0Wrg==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.3.tgz", + "integrity": "sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==", "cpu": [ "x64" ], @@ -358,9 +730,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.13.tgz", - "integrity": "sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.3.tgz", + "integrity": "sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==", "cpu": [ "arm64" ], @@ -373,26 +745,10 @@ "node": ">= 10" } }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.13.tgz", - "integrity": "sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.13.tgz", - "integrity": "sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.3.tgz", + "integrity": "sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==", "cpu": [ "x64" ], @@ -485,12 +841,11 @@ "license": "Apache-2.0" }, "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", "license": "Apache-2.0", "dependencies": { - "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, @@ -1308,11 +1663,25 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1325,9 +1694,20 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1534,6 +1914,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1571,6 +1961,19 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2655,6 +3058,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -2857,6 +3261,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, "node_modules/is-async-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", @@ -3579,41 +3990,42 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.2.13", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.13.tgz", - "integrity": "sha512-BseY9YNw8QJSwLYD7hlZzl6QVDoSFHL/URN5K64kVEVpCsSOWeyjbIGK+dZUaRViHTaMQX8aqmnn0PHBbGZezg==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/next/-/next-15.0.3.tgz", + "integrity": "sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==", "license": "MIT", "dependencies": { - "@next/env": "14.2.13", - "@swc/helpers": "0.5.5", + "@next/env": "15.0.3", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.13", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1" + "styled-jsx": "5.1.6" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=18.17.0" + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.13", - "@next/swc-darwin-x64": "14.2.13", - "@next/swc-linux-arm64-gnu": "14.2.13", - "@next/swc-linux-arm64-musl": "14.2.13", - "@next/swc-linux-x64-gnu": "14.2.13", - "@next/swc-linux-x64-musl": "14.2.13", - "@next/swc-win32-arm64-msvc": "14.2.13", - "@next/swc-win32-ia32-msvc": "14.2.13", - "@next/swc-win32-x64-msvc": "14.2.13" + "@next/swc-darwin-arm64": "15.0.3", + "@next/swc-darwin-x64": "15.0.3", + "@next/swc-linux-arm64-gnu": "15.0.3", + "@next/swc-linux-arm64-musl": "15.0.3", + "@next/swc-linux-x64-gnu": "15.0.3", + "@next/swc-linux-x64-musl": "15.0.3", + "@next/swc-win32-arm64-msvc": "15.0.3", + "@next/swc-win32-x64-msvc": "15.0.3", + "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-66855b96-20241106", + "react-dom": "^18.2.0 || 19.0.0-rc-66855b96-20241106", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -3623,6 +4035,9 @@ "@playwright/test": { "optional": true }, + "babel-plugin-react-compiler": { + "optional": true + }, "sass": { "optional": true } @@ -4533,7 +4948,7 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4576,6 +4991,46 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4631,6 +5086,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4883,9 +5348,9 @@ } }, "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", "dependencies": { "client-only": "0.0.1" @@ -4894,7 +5359,7 @@ "node": ">= 12.0.0" }, "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" }, "peerDependenciesMeta": { "@babel/core": { @@ -5079,9 +5544,9 @@ } }, "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/type-check": { 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 ac0e58a..d681d24 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,36 +1,43 @@ import { useEffect, useState } from "react"; import { DataTable } from "primereact/datatable"; import { Column } from "primereact/column"; -import { Head } from "next/head"; +import Head from "next/head"; 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()}
+
+
+ ); } diff --git a/scripts/fetch-ci-nightly-data.js b/scripts/fetch-ci-nightly-data.js index 804745b..7cb4525 100644 --- a/scripts/fetch-ci-nightly-data.js +++ b/scripts/fetch-ci-nightly-data.js @@ -11,110 +11,89 @@ // 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. +require('dotenv').config(); +const TOKEN = process.env.TOKEN; + +// The number of jobs to fetch from the github API on each paged request. +const jobs_per_request = 100; +// The last X closed runs to retrieve +const total_runs = 10; +// Count of the number of fetches. +var fetch_count = 0; -// 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 + `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 -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, { +// Perform a fetch request (to Github's API). +async function fetch_url(url) { + const response = await fetch(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 from ${url}: ${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 -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(); - }); +// 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 +// 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 +101,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 +122,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 +130,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,21 +143,14 @@ 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"]]; 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") { + if (job["conclusion"] !== "success") { + if (job["conclusion"] === "skipped") { job_stat["skips"] += 1; job_stat["results"].push("Skip"); } else { @@ -257,37 +164,16 @@ 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(); + const workflow_runs = await fetch_url(ci_nightly_runs_url); // Fetch required jobs from main branch - const main_branch = await fetch_main_branch(); + const main_branch = await fetch_url(main_branch_url); const required_jobs = get_required_jobs(main_branch); // Fetch job data for each of the runs. @@ -296,27 +182,15 @@ 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(); diff --git a/scripts/fetch-ci-pr-data.js b/scripts/fetch-ci-pr-data.js new file mode 100644 index 0000000..98908b5 --- /dev/null +++ b/scripts/fetch-ci-pr-data.js @@ -0,0 +1,194 @@ +// +// 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 +require('dotenv').config(); +const TOKEN = process.env.TOKEN; // In dev, set by .env file; in prod, set by GitHub Secret + + +// The number of checks to fetch from the github API on each paged request. +const results_per_request = 100; +// The last X closed PRs to retrieve +const pr_count = 10; +// Count of the number of fetches +var fetch_count = 0; + +// pull_requests attribute often empty if commit/branch from a fork: https://github.com/orgs/community/discussions/25220 +const pull_requests_url = + "https://api.github.com/repos/" + + `kata-containers/kata-containers/pulls?state=closed&per_page=${pr_count}`; + 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/checks. +const main_branch_url = + "https://api.github.com/repos/" + + "kata-containers/kata-containers/branches/main"; + + +// Perform a github API request for the given url. +async function fetch_url(url) { + const response = await fetch(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 workflow runs: ${response.status}: ` + + `${response.statusText}`); + } + + const json = await response.json(); + fetch_count++; + return await 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) { + const 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) { + return main_branch["protection"]["required_status_checks"]["contexts"]; +} + + +// 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 recent pull requests via the github API + const pull_requests = await fetch_url(pull_requests_url); + + // Fetch required jobs from main branch + const main_branch = await fetch_url(main_branch_url); + const required_jobs = get_required_jobs(main_branch); + + // 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 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). + const 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)); +} + + +main();