From b04efab41675faa33d7b63471789516aac06d29b Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 6 Jun 2024 10:32:18 +0530 Subject: [PATCH 01/71] GSoC_Week_1:Refactor present scrapper into typescript and addition in type.ts and gh_events.ts --- scraper/.gitignore | 2 + scraper/package-lock.json | 626 ++++++++++++++++++++++++++++++++++++++ scraper/package.json | 22 ++ scraper/src/github.py | 6 +- scraper/src/github.ts | 532 ++++++++++++++++++++++++++++++++ scraper/tsconfig.json | 36 +++ 6 files changed, 1221 insertions(+), 3 deletions(-) create mode 100644 scraper/.gitignore create mode 100644 scraper/package-lock.json create mode 100644 scraper/package.json create mode 100644 scraper/src/github.ts create mode 100644 scraper/tsconfig.json diff --git a/scraper/.gitignore b/scraper/.gitignore new file mode 100644 index 00000000..b2af6e00 --- /dev/null +++ b/scraper/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +!package-lock.json \ No newline at end of file diff --git a/scraper/package-lock.json b/scraper/package-lock.json new file mode 100644 index 00000000..840b621b --- /dev/null +++ b/scraper/package-lock.json @@ -0,0 +1,626 @@ +{ + "name": "scraper", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scraper", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@octokit/graphql": "^8.1.1", + "@octokit/types": "^13.5.0", + "date-fns": "^3.6.0", + "octokit": "^4.0.2", + "yargs": "^17.7.2" + }, + "devDependencies": { + "typescript": "^5.4.5" + } + }, + "node_modules/@octokit/app": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.0.1.tgz", + "integrity": "sha512-nwSjC349E6/wruMCo944y1dBC7uKzUYrBMoC4Qx/xfLLBmD+R66oMKB1jXS2HYRF9Hqh/Alq3UNRggVWZxjvUg==", + "dependencies": { + "@octokit/auth-app": "^7.0.0", + "@octokit/auth-unauthenticated": "^6.0.0", + "@octokit/core": "^6.1.2", + "@octokit/oauth-app": "^7.0.0", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/types": "^13.0.0", + "@octokit/webhooks": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.0.tgz", + "integrity": "sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==", + "dependencies": { + "@octokit/auth-oauth-app": "^8.1.0", + "@octokit/auth-oauth-user": "^5.1.0", + "@octokit/request": "^9.1.1", + "@octokit/request-error": "^6.1.1", + "@octokit/types": "^13.4.1", + "lru-cache": "^10.0.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz", + "integrity": "sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==", + "dependencies": { + "@octokit/auth-oauth-device": "^7.0.0", + "@octokit/auth-oauth-user": "^5.0.1", + "@octokit/request": "^9.0.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz", + "integrity": "sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==", + "dependencies": { + "@octokit/oauth-methods": "^5.0.0", + "@octokit/request": "^9.0.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz", + "integrity": "sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==", + "dependencies": { + "@octokit/auth-oauth-device": "^7.0.1", + "@octokit/oauth-methods": "^5.0.0", + "@octokit/request": "^9.0.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz", + "integrity": "sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==", + "dependencies": { + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", + "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.0.0", + "@octokit/request": "^9.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", + "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", + "dependencies": { + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", + "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", + "dependencies": { + "@octokit/request": "^9.0.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.2.tgz", + "integrity": "sha512-4ntCOZIiTozKwuYQroX/ZD722tzMH8Eicv/cgDM/3F3lyrlwENHDv4flTCBpSJbfK546B2SrkKMWB+/HbS84zQ==", + "dependencies": { + "@octokit/auth-oauth-app": "^8.0.0", + "@octokit/auth-oauth-user": "^5.0.1", + "@octokit/auth-unauthenticated": "^6.0.0-beta.1", + "@octokit/core": "^6.0.0", + "@octokit/oauth-authorization-url": "^7.0.0", + "@octokit/oauth-methods": "^5.0.0", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", + "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz", + "integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==", + "dependencies": { + "@octokit/oauth-authorization-url": "^7.0.0", + "@octokit/request": "^9.1.0", + "@octokit/request-error": "^6.1.0", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "node_modules/@octokit/openapi-webhooks-types": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.2.1.tgz", + "integrity": "sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.2.tgz", + "integrity": "sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.0.tgz", + "integrity": "sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.1.tgz", + "integrity": "sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.1.tgz", + "integrity": "sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==", + "dependencies": { + "@octokit/request-error": "^6.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.3.0.tgz", + "integrity": "sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==", + "dependencies": { + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^6.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.1.tgz", + "integrity": "sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.1.tgz", + "integrity": "sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==", + "dependencies": { + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "13.2.7", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.2.7.tgz", + "integrity": "sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==", + "dependencies": { + "@octokit/openapi-webhooks-types": "8.2.1", + "@octokit/request-error": "^6.0.1", + "@octokit/webhooks-methods": "^5.0.0", + "aggregate-error": "^5.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz", + "integrity": "sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.138", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.138.tgz", + "integrity": "sha512-71EHMl70TPWIAsFuHd85NHq6S6T2OOjiisPTrH7RgcjzpJpPh4RQJv7PvVvIxc6PIp8CLV7F9B+TdjcAES5vcA==" + }, + "node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + }, + "node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/octokit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-4.0.2.tgz", + "integrity": "sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==", + "dependencies": { + "@octokit/app": "^15.0.0", + "@octokit/core": "^6.0.0", + "@octokit/oauth-app": "^7.0.0", + "@octokit/plugin-paginate-graphql": "^5.0.0", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-rest-endpoint-methods": "^13.0.0", + "@octokit/plugin-retry": "^7.0.0", + "@octokit/plugin-throttling": "^9.0.0", + "@octokit/request-error": "^6.0.0", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/universal-github-app-jwt": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz", + "integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==" + }, + "node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/scraper/package.json b/scraper/package.json new file mode 100644 index 00000000..4eecf52e --- /dev/null +++ b/scraper/package.json @@ -0,0 +1,22 @@ + { + "name": "scraper", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@octokit/graphql": "^8.1.1", + "@octokit/types": "^13.5.0", + "date-fns": "^3.6.0", + "octokit": "^4.0.2", + "yargs": "^17.7.2" + }, + "devDependencies": { + "typescript": "^5.4.5" + } + } diff --git a/scraper/src/github.py b/scraper/src/github.py index 797a5899..85ee202f 100755 --- a/scraper/src/github.py +++ b/scraper/src/github.py @@ -459,7 +459,7 @@ def merge_data(self): self.log.debug(f"Merging user data for {user}") old_data = self.load_user_data(user) data = self.data.get(user) - new_unique_events = [] + new_unique_events = [] for event in data["activity"]: if event not in old_data["activity"]: new_unique_events.append(event) @@ -512,7 +512,7 @@ def main(): if args.date is None: date = datetime.now(tz=ZoneInfo("UTC")) - timedelta(days=1) else: - date = datetime.strptime(date, "%Y-%m-%d").replace(tzinfo=ZoneInfo("UTC")) + date = datetime.strptime(args.date, "%Y-%m-%d").replace(tzinfo=ZoneInfo("UTC")) scraper = GitHubScraper( args.org_name, @@ -526,4 +526,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/scraper/src/github.ts b/scraper/src/github.ts new file mode 100644 index 00000000..6ff9ce3d --- /dev/null +++ b/scraper/src/github.ts @@ -0,0 +1,532 @@ +import { formatISO, parseISO, startOfDay, subDays } from "date-fns"; +import fs from "fs"; +import path from "path"; +import { Activity, ActivityData, ProcessData, Action } from "../../lib/types"; +import { PullRequestEvent, IGitHubEvent } from "../../lib/gh_events"; +import { Octokit } from "octokit"; + +let processedData: ProcessData = {}; + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +if (!GITHUB_TOKEN) { + console.error("GITHUB_TOKEN not found in environment"); + process.exit(1); +} +const octokit = new Octokit({ + auth: GITHUB_TOKEN, +}); + +const fetchEvents = async (org: string, startDate: Date, endDate: Date) => { + const events = await octokit.paginate( + "GET /orgs/{org}/events", + { + org: org, + per_page: 1000, + }, + (response: { data: IGitHubEvent[] }) => { + return response.data; + }, + ); + + let eventsCount: number = 0; + let filteredEvents = []; + for (const event of events) { + const eventTime: Date = new Date(event.created_at ?? 0); + + if (eventTime > endDate) { + continue; + } else if (eventTime <= startDate) { + return filteredEvents; + } + const isBlacklisted: boolean = [ + "dependabot", + "snyk-bot", + "codecov-commenter", + "github-actions[bot]", + ].includes(event.actor.login); + const isRequiredEventType: boolean = [ + "IssueCommentEvent", + "IssuesEvent", + "PullRequestEvent", + "PullRequestReviewEvent", + ].includes(event.type ?? ""); + console.log(isRequiredEventType); + if (!isBlacklisted && isRequiredEventType) { + console.log(event.type); + filteredEvents.push(event); + } + eventsCount++; + } + console.log("Fetched " + { eventsCount } + " events"); + + return filteredEvents; +}; +function appendEvent(user: string, event: Activity) { + console.log(`Appending event for ${user}`); + if (!processedData[user]) { + console.log(`Creating new user data for ${user}`); + processedData[user] = { + last_updated: event.time, + activity: [event], + open_prs: [], + authored_issue_and_pr: [], + }; + } else { + processedData[user]["activity"].push(event); + if (event["time"] > (processedData[user]["last_updated"] ?? 0)) { + processedData[user]["last_updated"] = event["time"]; + } + } +} +const userBlacklist = new Set(["dependabot", "snyk-bot", "codecov-commenter"]); + +const isBlacklisted = (login: string): boolean => { + return login.includes("[bot]") || userBlacklist.has(login); +}; +function parseISODate(isoDate: Date) { + return new Date(isoDate); +} +async function calculateTurnaroundTime(event: PullRequestEvent) { + const user: string = event.payload.pull_request.user.login; + const mergedAt: Date = parseISODate(event.payload.pull_request.merged_at); + const createdAt: Date = parseISODate(event.payload.pull_request.created_at); + + const linkedIssues: [string, string][] = []; + const body = event.payload.pull_request.body || ""; + const regex = + /(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved) ([\w\/.-]*)(#\d+)/gi; + let match: RegExpExecArray | null; + + while ((match = regex.exec(body)) !== null) { + linkedIssues.push([match[2], match[3]]); + } + + const prTimelineResponse = await octokit.request( + `GET ${event.payload?.pull_request?.issue_url}/timeline`, + ); + + const prTimeline = prTimelineResponse.data; + + prTimeline.forEach((action: Action) => { + if ( + action.event === "cross-referenced" && + action.source.type === "issue" && + !action.source.issue.pull_request + ) { + linkedIssues.push([ + action.source.issue.repository.full_name, + `#${action.source.issue.number}`, + ]); + } + + if (action.event === "connected") { + // TODO: currently there is no way to get the issue number from the timeline, handle this case while moving to graphql + } + }); + const uniqueLinkedIssues: [string, string][] = Array.from( + new Set(linkedIssues.map((issue) => JSON.stringify(issue))), + ).map((item) => JSON.parse(item) as [string, string]); + const assignedAts: { issue: string; time: Date }[] = []; + + for (const [org_repo, issue] of uniqueLinkedIssues) { + const org = org_repo.split("/")[0] || event.repo.name.split("/")[0]; + const repo = org_repo.split("/")[-1] || event.repo.name.split("/")[1]; + const issueNumber = parseInt(issue.split("#")[1]); + + const issueTimelineResponse = await octokit.request( + "GET /repos/{owner}/{repo}/issues/{issue_number}/timeline", + { + owner: org, + repo: repo, + issue_number: issueNumber, + }, + ); + + const issueTimeline = issueTimelineResponse.data; + issueTimeline.forEach((action: Action) => { + if (action.event === "assigned" && action.assignee.login === user) { + assignedAts.push({ + issue: `${org}/${repo}#${issueNumber}`, + time: parseISODate(action.created_at), + }); + } + + if (action.event === "unassigned" && action.assignee.login === user) { + assignedAts.pop(); + } + }); + } + + const assignedAt: Date | null = + assignedAts.length === 0 + ? null + : assignedAts.reduce((min, current) => + current.time < min.time ? current : min, + ).time; + const turnaroundTime = + (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; + return turnaroundTime; +} +async function addCollaborations(event: PullRequestEvent, eventTime: Date) { + let nameUserCache: { [key: string]: string } = {}; + let emailUserCache: { [key: string]: string } = {}; + const collaborators: Set = new Set(); + + const url: string | undefined = event.payload.pull_request?.commits_url; + + const response = await octokit.request("GET " + url); + const commits = response.data; + for (const commit of commits) { + let authorLogin = commit.author && commit.author.login; + if (!authorLogin) { + authorLogin = commit.commit.author.name; + } + + if (isBlacklisted(authorLogin)) { + continue; + } + + collaborators.add(authorLogin); + + const coAuthors = commit.commit.message.match( + /Co-authored-by: (.+) <(.+)>/, + ); + if (coAuthors) { + for (const [name, email] of coAuthors) { + if (isBlacklisted(name)) { + continue; + } + + if (name in nameUserCache) { + collaborators.add(nameUserCache[name]); + continue; + } + + if (email in emailUserCache) { + collaborators.add(emailUserCache[email]); + continue; + } + + try { + const usersByEmail = await octokit.request("GET /search/users", { + q: email, + }); + + if (usersByEmail.data.total_count > 0) { + const login = usersByEmail.data.items[0].login; + emailUserCache[email] = login; + collaborators.add(login); + continue; + } + const usersByName = await octokit.request("GET /search/users", { + q: name, + }); + + if (usersByName.data.total_count === 1) { + const login = usersByName.data.items[0].login; + nameUserCache[name] = login; + collaborators.add(login); + } + } catch (e) { + console.error( + `Error fetching co-authors for commit ${commit} - ${name} <${email}>: ${e}`, + ); + } + } + } + } + + if (collaborators.size > 1) { + const collaboratorArray = Array.from(collaborators); // Convert Set to Array + for (const user of collaboratorArray) { + const others = new Set(collaborators); + const othersArray = Array.from(others); + + others.delete(user); + appendEvent(user, { + type: "pr_collaborated", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + collaborated_with: [...othersArray], + }); + } + } +} +async function resolve_autonomy_responsibility(event: Action, user: string) { + if (event.event === "cross-referenced" && event.source.type === "issue") { + return event.source.issue.user.login === user; + } + return false; +} +const fetch_merge_events = async (user: string, org: string) => { + console.log("Merge events for : ", user); + + // Fetching closed issues authored by the user + const { data: issues } = await octokit.request("GET /search/issues", { + q: `is:issue is:closed org:${org} author:${user}`, + }); + + let merged_prs = []; + + for (const issue of issues.items) { + const { data: timeline_events } = await octokit.request( + "GET " + issue.timeline_url, + ); + + for (const event of timeline_events) { + if (await resolve_autonomy_responsibility(event, user)) { + const pull_request = event.source.issue.pull_request; + if (pull_request && pull_request.merged_at) { + merged_prs.push({ + issue_link: issue.html_url, + pr_link: pull_request.html_url, + }); + } + } + } + } + + if (!processedData[user]) { + processedData[user] = { + authored_issue_and_pr: [], + last_updated: "", + activity: [], + open_prs: [], + }; + } + + for (const pr of merged_prs) { + processedData[user].authored_issue_and_pr.push(pr); + } + + return processedData; +}; + +const fetchOpenPulls = async (user: string, org: string) => { + console.log(`Fetching open pull requests for ${user}`); + const { data } = await octokit.request("GET /search/issues", { + q: `is:pr is:open org:${org} author:${user}`, + }); + + type PullsData = (typeof data.items)[0]; + let pulls: PullsData[] = data.items; + + pulls.forEach((pr: PullsData) => { + let today: Date = new Date(); + let prLastUpdated: Date = new Date(pr.updated_at); + let staleFor: number = Math.floor( + (today.getTime() - prLastUpdated.getTime()) / (1000 * 60 * 60 * 24), + ); + + if (!processedData[user]) { + processedData[user] = { + last_updated: "", + activity: [], + authored_issue_and_pr: [], + open_prs: [], + }; + } + processedData[user].open_prs.push({ + link: pr.html_url, + title: pr.title, + stale_for: staleFor, + labels: pr.labels.map((label: { name: string }) => label.name), + }); + }); + + console.log(`Fetched ${pulls.length} open pull requests for ${user}`); + return processedData; +}; +const parse_event = async (events: IGitHubEvent[]) => { + for (const event of events) { + const eventTime: Date = parseISO(event.created_at); + const user: string = event.actor.login; + if (isBlacklisted(user)) continue; + + console.log("Processing event for user: ", user); + console.log("event_id : ", event.id); + + switch (event.type) { + case "IssueCommentEvent": + if (event.payload.action === "created") { + appendEvent(user, { + type: "comment_created", + title: `${event.repo.name}#${event.payload.issue.number}`, + time: eventTime.toISOString(), + link: event.payload.comment.html_url, + text: event.payload.comment.body, + }); + } + break; + case "IssuesEvent": + if (["opened", "assigned", "closed"].includes(event.payload.action)) { + appendEvent(user, { + type: `issue_${event.payload.action}`, + title: `${event.repo.name}#${event.payload.issue?.number}`, + time: eventTime.toISOString(), + link: event.payload.issue.html_url, + text: event.payload.issue.title, + }); + } + break; + case "PullRequestEvent": + if (event.payload.action === "opened") { + appendEvent(user, { + type: "pr_opened", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + }); + } else if ( + event.payload.action === "closed" && + event.payload.pull_request?.merged + ) { + const turnaroundTime: number = await calculateTurnaroundTime(event); + appendEvent(user, { + type: "pr_merged", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + turnaround_time: turnaroundTime, + }); + await addCollaborations(event, eventTime); + } + break; + case "PullRequestReviewEvent": + appendEvent(user, { + type: "pr_reviewed", + time: eventTime.toISOString(), + title: `${event.repo.name}#${event.payload.pull_request.number}`, + link: event.payload.review.html_url, + text: event.payload.pull_request.title, + }); + break; + default: + break; + } + } + return processedData; +}; +function loadUserData(user: string, dataDir: string) { + const file = path.join(dataDir, `${user}.json`); + console.log(`Loading user data from ${file}`); + + try { + const response = fs.readFileSync(file); + const data: ActivityData = JSON.parse(response.toString()); + return data; + } catch (error: any) { + if (error.code === "ENOENT" || error.name === "SyntaxError") { + console.log(`User data not found for ${user}`); + return { activity: [] }; + } else { + throw error; // rethrow unexpected errors + } + } +} +function saveUserData( + user: string, + data: ActivityData, + dataDir: string, + serializer: any, +) { + const file = path.join(dataDir, `${user}.json`); + console.log(`Saving user data to ${file}`); + + try { + const jsonData = JSON.stringify(data, serializer, 2); + fs.writeFileSync(file, jsonData); + } catch (error: any) { + console.error(`Failed to save user data for ${user}: ${error.message}`); + throw error; + } +} +const merged_data = async (dataDir: string) => { + console.log("Updating data"); + fs.mkdirSync(dataDir, { recursive: true }); + + for (let user in processedData) { + if (processedData.hasOwnProperty(user)) { + console.log(`Merging user data for ${user}`); + let oldData = await loadUserData(user, dataDir); + let userData = processedData[user]; + let newUniqueEvents = []; + + for (let event of userData.activity) { + if ( + !oldData.activity.some( + (oldEvent) => JSON.stringify(oldEvent) === JSON.stringify(event), + ) + ) { + newUniqueEvents.push(event); + } + } + + userData.activity = newUniqueEvents.concat(oldData.activity); + saveUserData(user, userData, dataDir, null); + } + } +}; + +const scrapeGitHub = async ( + org: string, + date: string, + numDays: number = 1, +): Promise => { + const endDate: Date = startOfDay(parseISO(date)); + const startDate: Date = startOfDay(subDays(endDate, numDays)); + console.log( + `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, + ); + + const events: IGitHubEvent[] = (await fetchEvents( + org, + startDate, + endDate, + )) as IGitHubEvent[]; + processedData = await parse_event(events); + + for (const user of Object.keys(processedData)) { + try { + await fetch_merge_events(user, org); + } catch (e) { + console.error(`Error fetching merge events for ${user}: ${e}`); + } + try { + await fetchOpenPulls(user, org); + } catch (e) { + console.error(`Error fetching open pulls for ${user}: ${e}`); + } + } + + console.log("Scraping completed"); +}; + +// Type Done and check done +const main = async () => { + // Extract command line arguments (skip the first two default arguments) + const args: string[] = process.argv.slice(2); + + // Destructure arguments with default values + const [ + orgName, + dataDir, + date = formatISO(subDays(new Date(), 1), { representation: "date" }), + numDays = 1, + ] = args; + + if (!orgName || !dataDir) { + console.error("Usage: node script.js [date] [numDays]"); + process.exit(1); + } + + await scrapeGitHub(orgName, date, Number(numDays)); + await merged_data(dataDir); + console.log("Done"); +}; + +main(); diff --git a/scraper/tsconfig.json b/scraper/tsconfig.json new file mode 100644 index 00000000..61d87b0c --- /dev/null +++ b/scraper/tsconfig.json @@ -0,0 +1,36 @@ + +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "module": "ESNext", + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules", "data-repo", "data"], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } +} + From 6669679f96e0e87278f9e6862913a0c5669e70bd Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 6 Jun 2024 10:35:05 +0530 Subject: [PATCH 02/71] GSoC_Week_1:Refactor present scrapper into typescript and addition in type.ts and gh_events.ts --- app/api/leaderboard/functions.ts | 4 +- app/feed/page.tsx | 4 +- components/gh_events/GitHubEvents.tsx | 32 +- lib/gh_events.ts | 12 +- lib/octokit.ts | 4 +- lib/types.ts | 28 +- newGt.ts | 555 ++++++++++++++++++++++++++ package.json | 4 +- pnpm-lock.yaml | 163 ++++++-- public/logo.png | Bin 0 -> 44232 bytes tsconfig.json | 2 +- 11 files changed, 748 insertions(+), 60 deletions(-) create mode 100644 newGt.ts create mode 100644 public/logo.png diff --git a/app/api/leaderboard/functions.ts b/app/api/leaderboard/functions.ts index bd67f363..0c1fa2f5 100644 --- a/app/api/leaderboard/functions.ts +++ b/app/api/leaderboard/functions.ts @@ -28,7 +28,7 @@ export const getLeaderboardData = async ( ) .sort((a, b) => { if (sortBy === "pr_stale") { - return b.activityData.pr_stale - a.activityData.pr_stale; + return (b.activityData.pr_stale ?? 0) - (a.activityData.pr_stale ?? 0); } return b.summary[sortBy] - a.summary[sortBy]; }); @@ -55,7 +55,7 @@ export const getLeaderboardData = async ( }, highlights: { ...contributor.summary, - pr_stale: contributor.activityData.pr_stale, + pr_stale: contributor.activityData.pr_stale ?? 0, }, }; }); diff --git a/app/feed/page.tsx b/app/feed/page.tsx index 6b5b458b..a13781f2 100644 --- a/app/feed/page.tsx +++ b/app/feed/page.tsx @@ -1,10 +1,10 @@ import LoadingText from "@/components/LoadingText"; import { IGitHubEvent } from "@/lib/gh_events"; import GitHubEvent from "@/components/gh_events/GitHubEvent"; -import { env } from "@/env.mjs"; +import { env } from "../../env.mjs"; import octokit from "@/lib/octokit"; -const GITHUB_ORG: string = env.NEXT_PUBLIC_GITHUB_ORG; +const GITHUB_ORG: string = env.NEXT_PUBLIC_GITHUB_ORG ?? ""; export const revalidate = 600; diff --git a/components/gh_events/GitHubEvents.tsx b/components/gh_events/GitHubEvents.tsx index 842f3cda..907002dc 100644 --- a/components/gh_events/GitHubEvents.tsx +++ b/components/gh_events/GitHubEvents.tsx @@ -33,20 +33,22 @@ export default async function GitHubEvents({ minimal }: { minimal?: boolean }) { .slice(0, 5); return ( -
-
    - {events ? ( - events.map((e) => ) - ) : ( - <> - - - - - - - )} -
-
+ <> +
+
    + {events ? ( + events.map((e) => ) + ) : ( + <> + + + + + + + )} +
+
+ ); } diff --git a/lib/gh_events.ts b/lib/gh_events.ts index eecb2d21..845d1f3f 100644 --- a/lib/gh_events.ts +++ b/lib/gh_events.ts @@ -37,21 +37,25 @@ interface Comment { updated_at: string; } -interface PullRequest { +export interface PullRequest { html_url: string; user: Actor; title: string; body: string; number: number; - labels: string[]; + labels: string[] | { name: string }[]; comments: number; review_comments: number; commits: number; additions: number; deletions: number; changed_files: number; - created_at: string; + created_at: Date; updated_at: string; + merged_at: Date; + issue_url: string; + merged?: string; + commits_url?: string; } interface Review { @@ -139,7 +143,7 @@ interface IssueCommentEvent extends GitHubEvent { }; } -interface PullRequestEvent extends GitHubEvent { +export interface PullRequestEvent extends GitHubEvent { type: "PullRequestEvent"; payload: { action: string; diff --git a/lib/octokit.ts b/lib/octokit.ts index 7c1e872a..d58dc495 100644 --- a/lib/octokit.ts +++ b/lib/octokit.ts @@ -1,12 +1,12 @@ import "server-only"; -import { env } from "@/env.mjs"; +import { env } from "../env.mjs"; import { Octokit } from "octokit"; export const getGitHubAccessToken = () => { const accessToken = env.GITHUB_PAT as string | null; if (!accessToken) { - if (process.env.NODE_ENV === "development") { + if (env.NODE_ENV === "development") { console.warn("GITHUB_PAT is not configured in the environment."); return; } diff --git a/lib/types.ts b/lib/types.ts index a9959c99..143a830d 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -28,9 +28,12 @@ export interface ActivityData { last_updated?: string; activity: Activity[]; open_prs: OpenPr[]; - pr_stale: number; + pr_stale?: number; authored_issue_and_pr: AuthoredIssueAndPr[]; } +export interface ProcessData { + [key: string]: ActivityData; +} export interface Highlights { points: number; @@ -67,9 +70,28 @@ export const ACTIVITY_TYPES = [ "pr_merged", "pr_collaborated", ] as const; - +export interface Action { + event: string; + source: { + type: string; + issue: { + pull_request: boolean; + repository: { + full_name: string; + }; + user: { + login: string; + }; + number: number; + }; + }; + assignee: { + login: string; + }; + created_at: Date; +} export interface Activity { - type: (typeof ACTIVITY_TYPES)[number]; + type: (typeof ACTIVITY_TYPES)[number] | string; title: string; time: string; link: string; diff --git a/newGt.ts b/newGt.ts new file mode 100644 index 00000000..63fce7c5 --- /dev/null +++ b/newGt.ts @@ -0,0 +1,555 @@ +import fs from "fs"; +import path from "path"; +import { parseISO, subDays, formatISO, startOfDay } from "date-fns"; +import dotenv from "dotenv"; +import { Octokit } from "octokit"; +import { IGitHubEvent } from "./lib/gh_events"; +import { + Action, + Activity, + ProcessData, + PullRequest, + UserData, + PullRequestEvent, +} from "./lib/scraper_types"; +dotenv.config(); + +const userBlacklist = new Set(["dependabot", "snyk-bot", "codecov-commenter"]); +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +let processedData: ProcessData = {}; +if (!GITHUB_TOKEN) { + console.error("GITHUB_TOKEN not found in environment"); + process.exit(1); +} + +const headers = { + Authorization: GITHUB_TOKEN, + Accept: "application/vnd.github.v3+json", +}; + +const octokit = new Octokit({ + auth: GITHUB_TOKEN, +}); +type FetchEventsReturnType = ReturnType; +async function addCollaborations(event: PullRequestEvent, eventTime: Date) { + let nameUserCache: { [key: string]: string } = {}; + let emailUserCache: { [key: string]: string } = {}; + const collaborators: Set = new Set(); + const url: string | undefined = event.payload.pull_request?.commits_url; + const commits = await fetch(url ?? "", { + headers: headers, + }).then((response) => response.json()); + + for (const commit of commits) { + let authorLogin = commit.author && commit.author.login; + if (!authorLogin) { + authorLogin = commit.commit.author.name; + } + + if (isBlacklisted(authorLogin)) { + continue; + } + + collaborators.add(authorLogin); + + const coAuthors = commit.commit.message.match( + /Co-authored-by: (.+) <(.+)>/, + ); + if (coAuthors) { + for (const [name, email] of coAuthors) { + if (isBlacklisted(name)) { + continue; + } + + if (name in nameUserCache) { + collaborators.add(nameUserCache[name]); + continue; + } + + if (email in emailUserCache) { + collaborators.add(emailUserCache[email]); + continue; + } + + try { + const usersByEmail = await fetch( + `https://api.github.com/search/users?q=${encodeURIComponent(email)}`, + { + headers: headers, + }, + ).then((response) => response.json()); + + if (usersByEmail.total_count > 0) { + const login = usersByEmail.items[0].login; + emailUserCache[email] = login; + collaborators.add(login); + continue; + } + + const usersByName = await fetch( + `https://api.github.com/search/users?q=${encodeURIComponent(name)}`, + { + headers: headers, + }, + ).then((response) => response.json()); + + if (usersByName.total_count === 1) { + const login = usersByName.items[0].login; + nameUserCache[name] = login; + collaborators.add(login); + } + } catch (e) { + console.error( + `Error fetching co-authors for commit ${commit} - ${name} <${email}>: ${e}`, + ); + } + } + } + } + + if (collaborators.size > 1) { + for (const user of collaborators) { + const others = new Set(collaborators); + others.delete(user); + appendEvent(user, { + type: "pr_collaborated", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + collaborated_with: [...others], + }); + } + } +} + +const fetchEvents = async ( + org: string, + startDate: Date, + endDate: Date, + page: number = 1, +) => { + const events = await octokit.paginate( + "GET /orgs/{org}/events", + { + org: org, + per_page: 1000, + }, + (response: any) => { + return response.data; + }, + ); + + let eventsCount: number = 0; + let filteredEvents = []; + for (const event of events) { + const eventTime: Date = new Date(event.created_at); + + if (eventTime > endDate) { + continue; + } else if (eventTime <= startDate) { + return filteredEvents; + } + const isBlacklisted: boolean = [ + "dependabot", + "snyk-bot", + "codecov-commenter", + "github-actions[bot]", + ].includes(event.actor.login); + const isRequiredEventType: boolean = [ + "IssueCommentEvent", + "IssuesEvent", + "PullRequestEvent", + "PullRequestReviewEvent", + ].includes(event.type); + + if (!isBlacklisted && isRequiredEventType) { + filteredEvents.push(event); + eventsCount++; + } + } + console.log("Fetched " + { eventsCount } + " events"); + + return filteredEvents; +}; +const isBlacklisted = (login: string): boolean => { + return login.includes("[bot]") || userBlacklist.has(login); +}; +function appendEvent(user: string, event: Activity) { + console.log(`Appending event for ${user}`); + if (!processedData[user]) { + console.log(`Creating new user data for ${user}`); + processedData[user] = { + last_updated: event.time, + activity: [event], + open_prs: [], + authored_issue_and_pr: [], + }; + } else { + processedData[user]["activity"].push(event); + if (event["time"] > processedData[user]["last_updated"]) { + processedData[user]["last_updated"] = event["time"]; + } + } +} +function parseISODate(isoDate: Date) { + return new Date(isoDate); +} + +async function calculateTurnaroundTime(event: PullRequestEvent) { + const user: string = event.payload.pull_request.user.login; + const mergedAt: Date = parseISODate(event.payload.pull_request.merged_at); + const createdAt: Date = parseISODate(event.payload.pull_request.created_at); + + const linkedIssues: [string, string][] = []; + const body = event.payload.pull_request.body || ""; + const regex = + /(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved) ([\w\/.-]*)(#\d+)/gi; + let match: RegExpExecArray | null; + + while ((match = regex.exec(body)) !== null) { + linkedIssues.push([match[2], match[3]]); + } + + const prTimelineResponse = await octokit.request( + `GET ${event.payload?.pull_request?.issue_url}/timeline`, + { + headers: headers, + }, + ); + + const prTimeline = prTimelineResponse.data; + + prTimeline.forEach((action: Action) => { + if ( + action.event === "cross-referenced" && + action.source.type === "issue" && + !action.source.issue.pull_request + ) { + linkedIssues.push([ + action.source.issue.repository.full_name, + `#${action.source.issue.number}`, + ]); + } + + if (action.event === "connected") { + // TODO: currently there is no way to get the issue number from the timeline, handle this case while moving to graphql + } + }); + const uniqueLinkedIssues: [string, string][] = Array.from( + new Set(linkedIssues.map((issue) => JSON.stringify(issue))), + ).map((item) => JSON.parse(item) as [string, string]); + const assignedAts: { issue: string; time: Date }[] = []; + + for (const [org_repo, issue] of uniqueLinkedIssues) { + const org = org_repo.split("/")[0] || event.repo.name.split("/")[0]; + const repo = org_repo.split("/")[-1] || event.repo.name.split("/")[1]; + const issueNumber = parseInt(issue.split("#")[1]); + + const issueTimelineResponse = await octokit.request( + "GET /repos/{owner}/{repo}/issues/{issue_number}/timeline", + { + owner: org, + repo: repo, + issue_number: issueNumber, + }, + ); + + const issueTimeline = issueTimelineResponse.data; + + issueTimeline.forEach((action: Action) => { + if (action.event === "assigned" && action.assignee.login === user) { + assignedAts.push({ + issue: `${org}/${repo}#${issueNumber}`, + time: parseISODate(action.created_at), + }); + } + + if (action.event === "unassigned" && action.assignee.login === user) { + assignedAts.pop(); + } + }); + } + + const assignedAt: Date | null = + assignedAts.length === 0 + ? null + : assignedAts.reduce((min, current) => + current.time < min.time ? current : min, + ).time; + const turnaroundTime = + (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; + return turnaroundTime; +} +const parse_event = async (events: IGitHubEvent[]) => { + for (const event of events) { + const eventTime: Date = parseISO(event.created_at); + const user: string = event.actor.login; + if (isBlacklisted(user)) continue; + + console.log("Processing event for user: ", user); + console.log("event_id : ", event.id); + + switch (event.type) { + case "IssueCommentEvent": + if (event.payload.action === "created") { + appendEvent(user, { + type: "comment_created", + title: `${event.repo.name}#${event.payload.issue.number}`, + time: eventTime.toISOString(), + link: event.payload.comment.html_url, + text: event.payload.comment.body, + }); + } + break; + case "IssuesEvent": + if (["opened", "assigned", "closed"].includes(event.payload.action)) { + appendEvent(user, { + type: `issue_${event.payload.action}`, + title: `${event.repo.name}#${event.payload.issue.number}`, + time: eventTime.toISOString(), + link: event.payload.issue.html_url, + text: event.payload.issue.title, + }); + } + break; + case "PullRequestEvent": + if (event.payload.action === "opened") { + appendEvent(user, { + type: "pr_opened", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + }); + } else if ( + event.payload.action === "closed" && + event.payload.pull_request.merged + ) { + const turnaroundTime: number = await calculateTurnaroundTime(event); + appendEvent(user, { + type: "pr_merged", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + turnaround_time: turnaroundTime, + }); + await addCollaborations(event, eventTime); + } + break; + case "PullRequestReviewEvent": + appendEvent(user, { + type: "pr_reviewed", + time: eventTime.toISOString(), + title: `${event.repo.name}#${event.payload.pull_request.number}`, + link: event.payload.review.html_url, + text: event.payload.pull_request.title, + }); + break; + default: + break; + } + } + return processedData; +}; +async function resolve_autonomy_responsibility(event: any, user: string) { + if (event.event === "cross-referenced" && event.source.type === "issue") { + return event.source.issue.user.login === user; + } + return false; +} +const fetch_merge_events = async (user: string, org: string) => { + console.log("Merge events for : ", user); + + // Fetching closed issues authored by the user + const { data: issues } = await octokit.request("GET /search/issues", { + q: `is:issue is:closed org:${org} author:${user}`, + }); + + let merged_prs = []; + + for (const issue of issues.items) { + const { data: timeline_events } = await octokit.request( + "GET " + issue.timeline_url, + ); + + for (const event of timeline_events) { + if (await resolve_autonomy_responsibility(event, user)) { + const pull_request = event.source.issue.pull_request; + if (pull_request && pull_request.merged_at) { + merged_prs.push({ + issue_link: issue.html_url, + pr_link: pull_request.html_url, + }); + } + } + } + } + + if (!processedData[user]) { + processedData[user] = { + authored_issue_and_pr: [], + last_updated: "", + activity: [], + open_prs: [], + }; + } + + for (const pr of merged_prs) { + processedData[user].authored_issue_and_pr.push(pr); + } + + return processedData; +}; + +const fetchOpenPulls = async (user: string, org: string) => { + console.log(`Fetching open pull requests for ${user}`); + const { data } = await octokit.request("GET /search/issues", { + q: `is:pr is:open org:${org} author:${user}`, + }); + + let pulls: PullRequest[] = data.items; + + pulls.forEach((pr) => { + let today: Date = new Date(); + let prLastUpdated: Date = new Date(pr.updated_at); + let staleFor: number = Math.floor( + (today.getTime() - prLastUpdated.getTime()) / (1000 * 60 * 60 * 24), + ); + + if (!processedData[user]) { + processedData[user] = { + last_updated: "", + activity: [], + authored_issue_and_pr: [], + open_prs: [], + }; + } + processedData[user].open_prs.push({ + link: pr.html_url, + title: pr.title, + stale_for: staleFor, + labels: pr.labels.map((label: { name: string }) => label.name), + }); + }); + + console.log(`Fetched ${pulls.length} open pull requests for ${user}`); + return processedData; +}; +const scrapeGitHub = async ( + org: string, + dataDir: string, + date: string, + numDays: number = 1, +): Promise => { + const endDate: Date = startOfDay(parseISO(date)); + const startDate: Date = startOfDay(subDays(endDate, numDays)); + + console.log( + `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, + ); + + const events = await fetchEvents(org, startDate, endDate); + processedData = await parse_event(events); + + for (const user of Object.keys(processedData)) { + try { + await fetch_merge_events(user, org); + } catch (e) { + console.error(`Error fetching merge events for ${user}: ${e}`); + } + try { + await fetchOpenPulls(user, org); + } catch (e) { + console.error(`Error fetching open pulls for ${user}: ${e}`); + } + } + + console.log("Scraping completed"); +}; +function loadUserData(user: string, dataDir: string) { + const file = path.join(dataDir, `${user}.json`); + console.log(`Loading user data from ${file}`); + + try { + const response = fs.readFileSync(file); + const data: UserData = JSON.parse(response.toString()); + return data; + } catch (error: any) { + if (error.code === "ENOENT" || error.name === "SyntaxError") { + console.log(`User data not found for ${user}`); + return { activity: [] }; + } else { + throw error; // rethrow unexpected errors + } + } +} +function saveUserData( + user: string, + data: UserData, + dataDir: string, + serializer: any, +) { + const file = path.join(dataDir, `${user}.json`); + console.log(`Saving user data to ${file}`); + + try { + const jsonData = JSON.stringify(data, serializer, 2); + fs.writeFileSync(file, jsonData); + } catch (error: any) { + console.error(`Failed to save user data for ${user}: ${error.message}`); + throw error; + } +} +const merged_data = async (dataDir: string) => { + console.log("Updating data"); + fs.mkdirSync(dataDir, { recursive: true }); + + for (let user in processedData) { + if (processedData.hasOwnProperty(user)) { + console.log(`Merging user data for ${user}`); + let oldData = await loadUserData(user, dataDir); + let userData = processedData[user]; + let newUniqueEvents = []; + + for (let event of userData.activity) { + if ( + !oldData.activity.some( + (oldEvent: Activity) => + JSON.stringify(oldEvent) === JSON.stringify(event), + ) + ) { + newUniqueEvents.push(event); + } + } + + userData.activity = newUniqueEvents.concat(oldData.activity); + saveUserData(user, userData, dataDir, null); + } + } +}; + +const main = async () => { + // Extract command line arguments (skip the first two default arguments) + const args: string[] = process.argv.slice(2); + + // Destructure arguments with default values + const [ + orgName, + dataDir, + date = formatISO(subDays(new Date(), 1), { representation: "date" }), + numDays = 1, + ] = args; + + if (!orgName || !dataDir) { + console.error("Usage: node script.js [date] [numDays]"); + process.exit(1); + } + + await scrapeGitHub(orgName, dataDir, date, Number(numDays)); + await merged_data(dataDir); + console.log("Done"); +}; + +main(); diff --git a/package.json b/package.json index 9bfd9fbe..f390282e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@vercel/kv": "^1.0.1", "clsx": "^1.2.1", "date-fns": "^2.30.0", + "fs": "^0.0.1-security", "gray-matter": "^4.0.3", "next": "^14.1.3", "next-themes": "^0.2.1", @@ -52,6 +53,7 @@ "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", "tailwindcss": "^3.4.1", - "typescript": "^5.3.3" + "ts-node": "^10.9.2", + "typescript": "^5.4.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80229cfc..6959b320 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ dependencies: version: 1.7.18(react-dom@18.2.0)(react@18.2.0) '@t3-oss/env-nextjs': specifier: ^0.9.2 - version: 0.9.2(typescript@5.3.3)(zod@3.22.4) + version: 0.9.2(typescript@5.4.5)(zod@3.22.4) '@vercel/kv': specifier: ^1.0.1 version: 1.0.1 @@ -20,6 +20,9 @@ dependencies: date-fns: specifier: ^2.30.0 version: 2.30.0 + fs: + specifier: ^0.0.1-security + version: 0.0.1-security gray-matter: specifier: ^4.0.3 version: 4.0.3 @@ -90,7 +93,7 @@ devDependencies: version: 8.16.0 eslint-config-next: specifier: ^14.1.0 - version: 14.1.0(eslint@8.16.0)(typescript@5.3.3) + version: 14.1.0(eslint@8.16.0)(typescript@5.4.5) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.16.0) @@ -117,10 +120,13 @@ devDependencies: version: 0.5.11(prettier@3.2.5) tailwindcss: specifier: ^3.4.1 - version: 3.4.1 + version: 3.4.1(ts-node@10.9.2) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.11.21)(typescript@5.4.5) typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^5.4.5 + version: 5.4.5 packages: @@ -140,6 +146,13 @@ packages: dependencies: regenerator-runtime: 0.14.1 + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + /@eslint/eslintrc@1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -227,6 +240,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /@next/env@14.1.3: resolution: {integrity: sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ==} dev: false @@ -597,7 +617,7 @@ packages: tslib: 2.6.2 dev: false - /@t3-oss/env-core@0.9.2(typescript@5.3.3)(zod@3.22.4): + /@t3-oss/env-core@0.9.2(typescript@5.4.5)(zod@3.22.4): resolution: {integrity: sha512-KgWXljUTHgO3o7GMZQPAD5+P+HqpauMNNHowlm7V2b9IeMitSUpNKwG6xQrup/xARWHTdxRVIl0mSI4wCevQhQ==} peerDependencies: typescript: '>=5.0.0' @@ -606,11 +626,11 @@ packages: typescript: optional: true dependencies: - typescript: 5.3.3 + typescript: 5.4.5 zod: 3.22.4 dev: false - /@t3-oss/env-nextjs@0.9.2(typescript@5.3.3)(zod@3.22.4): + /@t3-oss/env-nextjs@0.9.2(typescript@5.4.5)(zod@3.22.4): resolution: {integrity: sha512-dklHrgKLESStNVB67Jdbu6osxDYA+xNKaPBRerlnkEvzbCccSKMvZENx6EZebJuR4snqB3/yRykNMn/bdIAyiQ==} peerDependencies: typescript: '>=5.0.0' @@ -619,8 +639,8 @@ packages: typescript: optional: true dependencies: - '@t3-oss/env-core': 0.9.2(typescript@5.3.3)(zod@3.22.4) - typescript: 5.3.3 + '@t3-oss/env-core': 0.9.2(typescript@5.4.5)(zod@3.22.4) + typescript: 5.4.5 zod: 3.22.4 dev: false @@ -633,7 +653,7 @@ packages: lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.1 + tailwindcss: 3.4.1(ts-node@10.9.2) dev: true /@tanstack/react-virtual@3.1.3(react-dom@18.2.0)(react@18.2.0): @@ -651,6 +671,22 @@ packages: resolution: {integrity: sha512-Y5B4EYyv1j9V8LzeAoOVeTg0LI7Fo5InYKgAjkY1Pu9GjtUwX/EKxNcU7ng3sKr99WEf+bPTcktAeybyMOYo+g==} dev: false + /@tsconfig/node10@1.0.11: + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + /@types/aws-lambda@8.10.134: resolution: {integrity: sha512-cfv422ivDMO+EeA3N4YcshbTHBL+5lLXe+Uz+4HXvIcsCuWvqNFpOs28ZprL8NA3qRCzt95ETiNAJDn4IcC/PA==} dev: true @@ -725,7 +761,7 @@ packages: resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} dev: false - /@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3): + /@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.4.5): resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -737,11 +773,11 @@ packages: dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.5) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.3.4 eslint: 8.16.0 - typescript: 5.3.3 + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true @@ -759,7 +795,7 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3): + /@typescript-eslint/typescript-estree@6.21.0(typescript@5.4.5): resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -775,8 +811,8 @@ packages: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.6.0 - ts-api-utils: 1.2.1(typescript@5.3.3) - typescript: 5.3.3 + ts-api-utils: 1.2.1(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true @@ -810,6 +846,11 @@ packages: acorn: 8.11.3 dev: true + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} @@ -867,6 +908,10 @@ packages: picomatch: 2.3.1 dev: true + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} dev: true @@ -1193,6 +1238,10 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1293,6 +1342,11 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + /diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} @@ -1481,7 +1535,7 @@ packages: engines: {node: '>=12'} dev: false - /eslint-config-next@14.1.0(eslint@8.16.0)(typescript@5.3.3): + /eslint-config-next@14.1.0(eslint@8.16.0)(typescript@5.4.5): resolution: {integrity: sha512-SBX2ed7DoRFXC6CQSLc/SbLY9Ut6HxNB2wPTcoIWjUMd7aF7O/SIE7111L8FdZ9TXsNV4pulUDnfthpyPtbFUg==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 @@ -1492,7 +1546,7 @@ packages: dependencies: '@next/eslint-plugin-next': 14.1.0 '@rushstack/eslint-patch': 1.7.2 - '@typescript-eslint/parser': 6.21.0(eslint@8.16.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.16.0)(typescript@5.4.5) eslint: 8.16.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.16.0) @@ -1500,7 +1554,7 @@ packages: eslint-plugin-jsx-a11y: 6.8.0(eslint@8.16.0) eslint-plugin-react: 7.33.2(eslint@8.16.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.16.0) - typescript: 5.3.3 + typescript: 5.4.5 transitivePeerDependencies: - eslint-import-resolver-webpack - supports-color @@ -1569,7 +1623,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.16.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.16.0)(typescript@5.4.5) debug: 3.2.7 eslint: 8.16.0 eslint-import-resolver-node: 0.3.9 @@ -1588,7 +1642,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.16.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.16.0)(typescript@5.4.5) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.4 array.prototype.flat: 1.3.2 @@ -1701,7 +1755,7 @@ packages: dependencies: fast-glob: 3.3.2 postcss: 8.4.35 - tailwindcss: 3.4.1 + tailwindcss: 3.4.1(ts-node@10.9.2) dev: true /eslint-scope@7.2.2: @@ -1908,6 +1962,10 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true + /fs@0.0.1-security: + resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==} + dev: false + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2607,6 +2665,10 @@ packages: yallist: 4.0.0 dev: true + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + /markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} dev: false @@ -3308,7 +3370,7 @@ packages: postcss: 8.4.35 dev: true - /postcss-load-config@4.0.2(postcss@8.4.35): + /postcss-load-config@4.0.2(postcss@8.4.35)(ts-node@10.9.2): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} peerDependencies: @@ -3322,6 +3384,7 @@ packages: dependencies: lilconfig: 3.1.1 postcss: 8.4.35 + ts-node: 10.9.2(@types/node@20.11.21)(typescript@5.4.5) yaml: 2.4.0 dev: true @@ -3928,7 +3991,7 @@ packages: tslib: 2.6.2 dev: true - /tailwindcss@3.4.1: + /tailwindcss@3.4.1(ts-node@10.9.2): resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==} engines: {node: '>=14.0.0'} hasBin: true @@ -3950,7 +4013,7 @@ packages: postcss: 8.4.35 postcss-import: 15.1.0(postcss@8.4.35) postcss-js: 4.0.1(postcss@8.4.35) - postcss-load-config: 4.0.2(postcss@8.4.35) + postcss-load-config: 4.0.2(postcss@8.4.35)(ts-node@10.9.2) postcss-nested: 6.0.1(postcss@8.4.35) postcss-selector-parser: 6.0.15 resolve: 1.22.8 @@ -3996,19 +4059,50 @@ packages: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} dev: false - /ts-api-utils@1.2.1(typescript@5.3.3): + /ts-api-utils@1.2.1(typescript@5.4.5): resolution: {integrity: sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' dependencies: - typescript: 5.3.3 + typescript: 5.4.5 dev: true /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /ts-node@10.9.2(@types/node@20.11.21)(typescript@5.4.5): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.11.21 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.4.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} dependencies: @@ -4077,8 +4171,8 @@ packages: possible-typed-array-names: 1.0.0 dev: true - /typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + /typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true @@ -4196,6 +4290,10 @@ packages: sade: 1.8.1 dev: false + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + /v8-compile-cache@2.4.0: resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} dev: true @@ -4315,6 +4413,11 @@ packages: hasBin: true dev: true + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} dev: false diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0050b13ba30c4c5527af5ae8ca3da4e7d30ede49 GIT binary patch literal 44232 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%0D0+(wyvDN-bo!{gez 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 Date: Thu, 6 Jun 2024 10:58:28 +0530 Subject: [PATCH 03/71] Delete newGt.ts --- newGt.ts | 555 ------------------------------------------------------- 1 file changed, 555 deletions(-) delete mode 100644 newGt.ts diff --git a/newGt.ts b/newGt.ts deleted file mode 100644 index 63fce7c5..00000000 --- a/newGt.ts +++ /dev/null @@ -1,555 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { parseISO, subDays, formatISO, startOfDay } from "date-fns"; -import dotenv from "dotenv"; -import { Octokit } from "octokit"; -import { IGitHubEvent } from "./lib/gh_events"; -import { - Action, - Activity, - ProcessData, - PullRequest, - UserData, - PullRequestEvent, -} from "./lib/scraper_types"; -dotenv.config(); - -const userBlacklist = new Set(["dependabot", "snyk-bot", "codecov-commenter"]); -const GITHUB_TOKEN = process.env.GITHUB_TOKEN; -let processedData: ProcessData = {}; -if (!GITHUB_TOKEN) { - console.error("GITHUB_TOKEN not found in environment"); - process.exit(1); -} - -const headers = { - Authorization: GITHUB_TOKEN, - Accept: "application/vnd.github.v3+json", -}; - -const octokit = new Octokit({ - auth: GITHUB_TOKEN, -}); -type FetchEventsReturnType = ReturnType; -async function addCollaborations(event: PullRequestEvent, eventTime: Date) { - let nameUserCache: { [key: string]: string } = {}; - let emailUserCache: { [key: string]: string } = {}; - const collaborators: Set = new Set(); - const url: string | undefined = event.payload.pull_request?.commits_url; - const commits = await fetch(url ?? "", { - headers: headers, - }).then((response) => response.json()); - - for (const commit of commits) { - let authorLogin = commit.author && commit.author.login; - if (!authorLogin) { - authorLogin = commit.commit.author.name; - } - - if (isBlacklisted(authorLogin)) { - continue; - } - - collaborators.add(authorLogin); - - const coAuthors = commit.commit.message.match( - /Co-authored-by: (.+) <(.+)>/, - ); - if (coAuthors) { - for (const [name, email] of coAuthors) { - if (isBlacklisted(name)) { - continue; - } - - if (name in nameUserCache) { - collaborators.add(nameUserCache[name]); - continue; - } - - if (email in emailUserCache) { - collaborators.add(emailUserCache[email]); - continue; - } - - try { - const usersByEmail = await fetch( - `https://api.github.com/search/users?q=${encodeURIComponent(email)}`, - { - headers: headers, - }, - ).then((response) => response.json()); - - if (usersByEmail.total_count > 0) { - const login = usersByEmail.items[0].login; - emailUserCache[email] = login; - collaborators.add(login); - continue; - } - - const usersByName = await fetch( - `https://api.github.com/search/users?q=${encodeURIComponent(name)}`, - { - headers: headers, - }, - ).then((response) => response.json()); - - if (usersByName.total_count === 1) { - const login = usersByName.items[0].login; - nameUserCache[name] = login; - collaborators.add(login); - } - } catch (e) { - console.error( - `Error fetching co-authors for commit ${commit} - ${name} <${email}>: ${e}`, - ); - } - } - } - } - - if (collaborators.size > 1) { - for (const user of collaborators) { - const others = new Set(collaborators); - others.delete(user); - appendEvent(user, { - type: "pr_collaborated", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - collaborated_with: [...others], - }); - } - } -} - -const fetchEvents = async ( - org: string, - startDate: Date, - endDate: Date, - page: number = 1, -) => { - const events = await octokit.paginate( - "GET /orgs/{org}/events", - { - org: org, - per_page: 1000, - }, - (response: any) => { - return response.data; - }, - ); - - let eventsCount: number = 0; - let filteredEvents = []; - for (const event of events) { - const eventTime: Date = new Date(event.created_at); - - if (eventTime > endDate) { - continue; - } else if (eventTime <= startDate) { - return filteredEvents; - } - const isBlacklisted: boolean = [ - "dependabot", - "snyk-bot", - "codecov-commenter", - "github-actions[bot]", - ].includes(event.actor.login); - const isRequiredEventType: boolean = [ - "IssueCommentEvent", - "IssuesEvent", - "PullRequestEvent", - "PullRequestReviewEvent", - ].includes(event.type); - - if (!isBlacklisted && isRequiredEventType) { - filteredEvents.push(event); - eventsCount++; - } - } - console.log("Fetched " + { eventsCount } + " events"); - - return filteredEvents; -}; -const isBlacklisted = (login: string): boolean => { - return login.includes("[bot]") || userBlacklist.has(login); -}; -function appendEvent(user: string, event: Activity) { - console.log(`Appending event for ${user}`); - if (!processedData[user]) { - console.log(`Creating new user data for ${user}`); - processedData[user] = { - last_updated: event.time, - activity: [event], - open_prs: [], - authored_issue_and_pr: [], - }; - } else { - processedData[user]["activity"].push(event); - if (event["time"] > processedData[user]["last_updated"]) { - processedData[user]["last_updated"] = event["time"]; - } - } -} -function parseISODate(isoDate: Date) { - return new Date(isoDate); -} - -async function calculateTurnaroundTime(event: PullRequestEvent) { - const user: string = event.payload.pull_request.user.login; - const mergedAt: Date = parseISODate(event.payload.pull_request.merged_at); - const createdAt: Date = parseISODate(event.payload.pull_request.created_at); - - const linkedIssues: [string, string][] = []; - const body = event.payload.pull_request.body || ""; - const regex = - /(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved) ([\w\/.-]*)(#\d+)/gi; - let match: RegExpExecArray | null; - - while ((match = regex.exec(body)) !== null) { - linkedIssues.push([match[2], match[3]]); - } - - const prTimelineResponse = await octokit.request( - `GET ${event.payload?.pull_request?.issue_url}/timeline`, - { - headers: headers, - }, - ); - - const prTimeline = prTimelineResponse.data; - - prTimeline.forEach((action: Action) => { - if ( - action.event === "cross-referenced" && - action.source.type === "issue" && - !action.source.issue.pull_request - ) { - linkedIssues.push([ - action.source.issue.repository.full_name, - `#${action.source.issue.number}`, - ]); - } - - if (action.event === "connected") { - // TODO: currently there is no way to get the issue number from the timeline, handle this case while moving to graphql - } - }); - const uniqueLinkedIssues: [string, string][] = Array.from( - new Set(linkedIssues.map((issue) => JSON.stringify(issue))), - ).map((item) => JSON.parse(item) as [string, string]); - const assignedAts: { issue: string; time: Date }[] = []; - - for (const [org_repo, issue] of uniqueLinkedIssues) { - const org = org_repo.split("/")[0] || event.repo.name.split("/")[0]; - const repo = org_repo.split("/")[-1] || event.repo.name.split("/")[1]; - const issueNumber = parseInt(issue.split("#")[1]); - - const issueTimelineResponse = await octokit.request( - "GET /repos/{owner}/{repo}/issues/{issue_number}/timeline", - { - owner: org, - repo: repo, - issue_number: issueNumber, - }, - ); - - const issueTimeline = issueTimelineResponse.data; - - issueTimeline.forEach((action: Action) => { - if (action.event === "assigned" && action.assignee.login === user) { - assignedAts.push({ - issue: `${org}/${repo}#${issueNumber}`, - time: parseISODate(action.created_at), - }); - } - - if (action.event === "unassigned" && action.assignee.login === user) { - assignedAts.pop(); - } - }); - } - - const assignedAt: Date | null = - assignedAts.length === 0 - ? null - : assignedAts.reduce((min, current) => - current.time < min.time ? current : min, - ).time; - const turnaroundTime = - (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; - return turnaroundTime; -} -const parse_event = async (events: IGitHubEvent[]) => { - for (const event of events) { - const eventTime: Date = parseISO(event.created_at); - const user: string = event.actor.login; - if (isBlacklisted(user)) continue; - - console.log("Processing event for user: ", user); - console.log("event_id : ", event.id); - - switch (event.type) { - case "IssueCommentEvent": - if (event.payload.action === "created") { - appendEvent(user, { - type: "comment_created", - title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), - link: event.payload.comment.html_url, - text: event.payload.comment.body, - }); - } - break; - case "IssuesEvent": - if (["opened", "assigned", "closed"].includes(event.payload.action)) { - appendEvent(user, { - type: `issue_${event.payload.action}`, - title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), - link: event.payload.issue.html_url, - text: event.payload.issue.title, - }); - } - break; - case "PullRequestEvent": - if (event.payload.action === "opened") { - appendEvent(user, { - type: "pr_opened", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - }); - } else if ( - event.payload.action === "closed" && - event.payload.pull_request.merged - ) { - const turnaroundTime: number = await calculateTurnaroundTime(event); - appendEvent(user, { - type: "pr_merged", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - turnaround_time: turnaroundTime, - }); - await addCollaborations(event, eventTime); - } - break; - case "PullRequestReviewEvent": - appendEvent(user, { - type: "pr_reviewed", - time: eventTime.toISOString(), - title: `${event.repo.name}#${event.payload.pull_request.number}`, - link: event.payload.review.html_url, - text: event.payload.pull_request.title, - }); - break; - default: - break; - } - } - return processedData; -}; -async function resolve_autonomy_responsibility(event: any, user: string) { - if (event.event === "cross-referenced" && event.source.type === "issue") { - return event.source.issue.user.login === user; - } - return false; -} -const fetch_merge_events = async (user: string, org: string) => { - console.log("Merge events for : ", user); - - // Fetching closed issues authored by the user - const { data: issues } = await octokit.request("GET /search/issues", { - q: `is:issue is:closed org:${org} author:${user}`, - }); - - let merged_prs = []; - - for (const issue of issues.items) { - const { data: timeline_events } = await octokit.request( - "GET " + issue.timeline_url, - ); - - for (const event of timeline_events) { - if (await resolve_autonomy_responsibility(event, user)) { - const pull_request = event.source.issue.pull_request; - if (pull_request && pull_request.merged_at) { - merged_prs.push({ - issue_link: issue.html_url, - pr_link: pull_request.html_url, - }); - } - } - } - } - - if (!processedData[user]) { - processedData[user] = { - authored_issue_and_pr: [], - last_updated: "", - activity: [], - open_prs: [], - }; - } - - for (const pr of merged_prs) { - processedData[user].authored_issue_and_pr.push(pr); - } - - return processedData; -}; - -const fetchOpenPulls = async (user: string, org: string) => { - console.log(`Fetching open pull requests for ${user}`); - const { data } = await octokit.request("GET /search/issues", { - q: `is:pr is:open org:${org} author:${user}`, - }); - - let pulls: PullRequest[] = data.items; - - pulls.forEach((pr) => { - let today: Date = new Date(); - let prLastUpdated: Date = new Date(pr.updated_at); - let staleFor: number = Math.floor( - (today.getTime() - prLastUpdated.getTime()) / (1000 * 60 * 60 * 24), - ); - - if (!processedData[user]) { - processedData[user] = { - last_updated: "", - activity: [], - authored_issue_and_pr: [], - open_prs: [], - }; - } - processedData[user].open_prs.push({ - link: pr.html_url, - title: pr.title, - stale_for: staleFor, - labels: pr.labels.map((label: { name: string }) => label.name), - }); - }); - - console.log(`Fetched ${pulls.length} open pull requests for ${user}`); - return processedData; -}; -const scrapeGitHub = async ( - org: string, - dataDir: string, - date: string, - numDays: number = 1, -): Promise => { - const endDate: Date = startOfDay(parseISO(date)); - const startDate: Date = startOfDay(subDays(endDate, numDays)); - - console.log( - `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, - ); - - const events = await fetchEvents(org, startDate, endDate); - processedData = await parse_event(events); - - for (const user of Object.keys(processedData)) { - try { - await fetch_merge_events(user, org); - } catch (e) { - console.error(`Error fetching merge events for ${user}: ${e}`); - } - try { - await fetchOpenPulls(user, org); - } catch (e) { - console.error(`Error fetching open pulls for ${user}: ${e}`); - } - } - - console.log("Scraping completed"); -}; -function loadUserData(user: string, dataDir: string) { - const file = path.join(dataDir, `${user}.json`); - console.log(`Loading user data from ${file}`); - - try { - const response = fs.readFileSync(file); - const data: UserData = JSON.parse(response.toString()); - return data; - } catch (error: any) { - if (error.code === "ENOENT" || error.name === "SyntaxError") { - console.log(`User data not found for ${user}`); - return { activity: [] }; - } else { - throw error; // rethrow unexpected errors - } - } -} -function saveUserData( - user: string, - data: UserData, - dataDir: string, - serializer: any, -) { - const file = path.join(dataDir, `${user}.json`); - console.log(`Saving user data to ${file}`); - - try { - const jsonData = JSON.stringify(data, serializer, 2); - fs.writeFileSync(file, jsonData); - } catch (error: any) { - console.error(`Failed to save user data for ${user}: ${error.message}`); - throw error; - } -} -const merged_data = async (dataDir: string) => { - console.log("Updating data"); - fs.mkdirSync(dataDir, { recursive: true }); - - for (let user in processedData) { - if (processedData.hasOwnProperty(user)) { - console.log(`Merging user data for ${user}`); - let oldData = await loadUserData(user, dataDir); - let userData = processedData[user]; - let newUniqueEvents = []; - - for (let event of userData.activity) { - if ( - !oldData.activity.some( - (oldEvent: Activity) => - JSON.stringify(oldEvent) === JSON.stringify(event), - ) - ) { - newUniqueEvents.push(event); - } - } - - userData.activity = newUniqueEvents.concat(oldData.activity); - saveUserData(user, userData, dataDir, null); - } - } -}; - -const main = async () => { - // Extract command line arguments (skip the first two default arguments) - const args: string[] = process.argv.slice(2); - - // Destructure arguments with default values - const [ - orgName, - dataDir, - date = formatISO(subDays(new Date(), 1), { representation: "date" }), - numDays = 1, - ] = args; - - if (!orgName || !dataDir) { - console.error("Usage: node script.js [date] [numDays]"); - process.exit(1); - } - - await scrapeGitHub(orgName, dataDir, date, Number(numDays)); - await merged_data(dataDir); - console.log("Done"); -}; - -main(); From 95804ed987f82203404657b725a27376cc9f3dee Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 6 Jun 2024 11:24:11 +0530 Subject: [PATCH 04/71] remove unwated changes --- lib/octokit.ts | 6 +- newGt.ts | 555 ------------------------------------------ scraper/src/github.py | 4 +- scraper/tsconfig.json | 45 ++-- tsconfig.json | 2 +- 5 files changed, 23 insertions(+), 589 deletions(-) delete mode 100644 newGt.ts diff --git a/lib/octokit.ts b/lib/octokit.ts index d58dc495..0a472213 100644 --- a/lib/octokit.ts +++ b/lib/octokit.ts @@ -1,12 +1,12 @@ import "server-only"; -import { env } from "../env.mjs"; +import { env } from "@/env.mjs"; import { Octokit } from "octokit"; export const getGitHubAccessToken = () => { const accessToken = env.GITHUB_PAT as string | null; if (!accessToken) { - if (env.NODE_ENV === "development") { + if (process.env.NODE_ENV === "development") { console.warn("GITHUB_PAT is not configured in the environment."); return; } @@ -21,4 +21,4 @@ const octokit = new Octokit({ auth: getGitHubAccessToken(), }); -export default octokit; +export default octokit; \ No newline at end of file diff --git a/newGt.ts b/newGt.ts deleted file mode 100644 index 63fce7c5..00000000 --- a/newGt.ts +++ /dev/null @@ -1,555 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { parseISO, subDays, formatISO, startOfDay } from "date-fns"; -import dotenv from "dotenv"; -import { Octokit } from "octokit"; -import { IGitHubEvent } from "./lib/gh_events"; -import { - Action, - Activity, - ProcessData, - PullRequest, - UserData, - PullRequestEvent, -} from "./lib/scraper_types"; -dotenv.config(); - -const userBlacklist = new Set(["dependabot", "snyk-bot", "codecov-commenter"]); -const GITHUB_TOKEN = process.env.GITHUB_TOKEN; -let processedData: ProcessData = {}; -if (!GITHUB_TOKEN) { - console.error("GITHUB_TOKEN not found in environment"); - process.exit(1); -} - -const headers = { - Authorization: GITHUB_TOKEN, - Accept: "application/vnd.github.v3+json", -}; - -const octokit = new Octokit({ - auth: GITHUB_TOKEN, -}); -type FetchEventsReturnType = ReturnType; -async function addCollaborations(event: PullRequestEvent, eventTime: Date) { - let nameUserCache: { [key: string]: string } = {}; - let emailUserCache: { [key: string]: string } = {}; - const collaborators: Set = new Set(); - const url: string | undefined = event.payload.pull_request?.commits_url; - const commits = await fetch(url ?? "", { - headers: headers, - }).then((response) => response.json()); - - for (const commit of commits) { - let authorLogin = commit.author && commit.author.login; - if (!authorLogin) { - authorLogin = commit.commit.author.name; - } - - if (isBlacklisted(authorLogin)) { - continue; - } - - collaborators.add(authorLogin); - - const coAuthors = commit.commit.message.match( - /Co-authored-by: (.+) <(.+)>/, - ); - if (coAuthors) { - for (const [name, email] of coAuthors) { - if (isBlacklisted(name)) { - continue; - } - - if (name in nameUserCache) { - collaborators.add(nameUserCache[name]); - continue; - } - - if (email in emailUserCache) { - collaborators.add(emailUserCache[email]); - continue; - } - - try { - const usersByEmail = await fetch( - `https://api.github.com/search/users?q=${encodeURIComponent(email)}`, - { - headers: headers, - }, - ).then((response) => response.json()); - - if (usersByEmail.total_count > 0) { - const login = usersByEmail.items[0].login; - emailUserCache[email] = login; - collaborators.add(login); - continue; - } - - const usersByName = await fetch( - `https://api.github.com/search/users?q=${encodeURIComponent(name)}`, - { - headers: headers, - }, - ).then((response) => response.json()); - - if (usersByName.total_count === 1) { - const login = usersByName.items[0].login; - nameUserCache[name] = login; - collaborators.add(login); - } - } catch (e) { - console.error( - `Error fetching co-authors for commit ${commit} - ${name} <${email}>: ${e}`, - ); - } - } - } - } - - if (collaborators.size > 1) { - for (const user of collaborators) { - const others = new Set(collaborators); - others.delete(user); - appendEvent(user, { - type: "pr_collaborated", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - collaborated_with: [...others], - }); - } - } -} - -const fetchEvents = async ( - org: string, - startDate: Date, - endDate: Date, - page: number = 1, -) => { - const events = await octokit.paginate( - "GET /orgs/{org}/events", - { - org: org, - per_page: 1000, - }, - (response: any) => { - return response.data; - }, - ); - - let eventsCount: number = 0; - let filteredEvents = []; - for (const event of events) { - const eventTime: Date = new Date(event.created_at); - - if (eventTime > endDate) { - continue; - } else if (eventTime <= startDate) { - return filteredEvents; - } - const isBlacklisted: boolean = [ - "dependabot", - "snyk-bot", - "codecov-commenter", - "github-actions[bot]", - ].includes(event.actor.login); - const isRequiredEventType: boolean = [ - "IssueCommentEvent", - "IssuesEvent", - "PullRequestEvent", - "PullRequestReviewEvent", - ].includes(event.type); - - if (!isBlacklisted && isRequiredEventType) { - filteredEvents.push(event); - eventsCount++; - } - } - console.log("Fetched " + { eventsCount } + " events"); - - return filteredEvents; -}; -const isBlacklisted = (login: string): boolean => { - return login.includes("[bot]") || userBlacklist.has(login); -}; -function appendEvent(user: string, event: Activity) { - console.log(`Appending event for ${user}`); - if (!processedData[user]) { - console.log(`Creating new user data for ${user}`); - processedData[user] = { - last_updated: event.time, - activity: [event], - open_prs: [], - authored_issue_and_pr: [], - }; - } else { - processedData[user]["activity"].push(event); - if (event["time"] > processedData[user]["last_updated"]) { - processedData[user]["last_updated"] = event["time"]; - } - } -} -function parseISODate(isoDate: Date) { - return new Date(isoDate); -} - -async function calculateTurnaroundTime(event: PullRequestEvent) { - const user: string = event.payload.pull_request.user.login; - const mergedAt: Date = parseISODate(event.payload.pull_request.merged_at); - const createdAt: Date = parseISODate(event.payload.pull_request.created_at); - - const linkedIssues: [string, string][] = []; - const body = event.payload.pull_request.body || ""; - const regex = - /(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved) ([\w\/.-]*)(#\d+)/gi; - let match: RegExpExecArray | null; - - while ((match = regex.exec(body)) !== null) { - linkedIssues.push([match[2], match[3]]); - } - - const prTimelineResponse = await octokit.request( - `GET ${event.payload?.pull_request?.issue_url}/timeline`, - { - headers: headers, - }, - ); - - const prTimeline = prTimelineResponse.data; - - prTimeline.forEach((action: Action) => { - if ( - action.event === "cross-referenced" && - action.source.type === "issue" && - !action.source.issue.pull_request - ) { - linkedIssues.push([ - action.source.issue.repository.full_name, - `#${action.source.issue.number}`, - ]); - } - - if (action.event === "connected") { - // TODO: currently there is no way to get the issue number from the timeline, handle this case while moving to graphql - } - }); - const uniqueLinkedIssues: [string, string][] = Array.from( - new Set(linkedIssues.map((issue) => JSON.stringify(issue))), - ).map((item) => JSON.parse(item) as [string, string]); - const assignedAts: { issue: string; time: Date }[] = []; - - for (const [org_repo, issue] of uniqueLinkedIssues) { - const org = org_repo.split("/")[0] || event.repo.name.split("/")[0]; - const repo = org_repo.split("/")[-1] || event.repo.name.split("/")[1]; - const issueNumber = parseInt(issue.split("#")[1]); - - const issueTimelineResponse = await octokit.request( - "GET /repos/{owner}/{repo}/issues/{issue_number}/timeline", - { - owner: org, - repo: repo, - issue_number: issueNumber, - }, - ); - - const issueTimeline = issueTimelineResponse.data; - - issueTimeline.forEach((action: Action) => { - if (action.event === "assigned" && action.assignee.login === user) { - assignedAts.push({ - issue: `${org}/${repo}#${issueNumber}`, - time: parseISODate(action.created_at), - }); - } - - if (action.event === "unassigned" && action.assignee.login === user) { - assignedAts.pop(); - } - }); - } - - const assignedAt: Date | null = - assignedAts.length === 0 - ? null - : assignedAts.reduce((min, current) => - current.time < min.time ? current : min, - ).time; - const turnaroundTime = - (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; - return turnaroundTime; -} -const parse_event = async (events: IGitHubEvent[]) => { - for (const event of events) { - const eventTime: Date = parseISO(event.created_at); - const user: string = event.actor.login; - if (isBlacklisted(user)) continue; - - console.log("Processing event for user: ", user); - console.log("event_id : ", event.id); - - switch (event.type) { - case "IssueCommentEvent": - if (event.payload.action === "created") { - appendEvent(user, { - type: "comment_created", - title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), - link: event.payload.comment.html_url, - text: event.payload.comment.body, - }); - } - break; - case "IssuesEvent": - if (["opened", "assigned", "closed"].includes(event.payload.action)) { - appendEvent(user, { - type: `issue_${event.payload.action}`, - title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), - link: event.payload.issue.html_url, - text: event.payload.issue.title, - }); - } - break; - case "PullRequestEvent": - if (event.payload.action === "opened") { - appendEvent(user, { - type: "pr_opened", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - }); - } else if ( - event.payload.action === "closed" && - event.payload.pull_request.merged - ) { - const turnaroundTime: number = await calculateTurnaroundTime(event); - appendEvent(user, { - type: "pr_merged", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - turnaround_time: turnaroundTime, - }); - await addCollaborations(event, eventTime); - } - break; - case "PullRequestReviewEvent": - appendEvent(user, { - type: "pr_reviewed", - time: eventTime.toISOString(), - title: `${event.repo.name}#${event.payload.pull_request.number}`, - link: event.payload.review.html_url, - text: event.payload.pull_request.title, - }); - break; - default: - break; - } - } - return processedData; -}; -async function resolve_autonomy_responsibility(event: any, user: string) { - if (event.event === "cross-referenced" && event.source.type === "issue") { - return event.source.issue.user.login === user; - } - return false; -} -const fetch_merge_events = async (user: string, org: string) => { - console.log("Merge events for : ", user); - - // Fetching closed issues authored by the user - const { data: issues } = await octokit.request("GET /search/issues", { - q: `is:issue is:closed org:${org} author:${user}`, - }); - - let merged_prs = []; - - for (const issue of issues.items) { - const { data: timeline_events } = await octokit.request( - "GET " + issue.timeline_url, - ); - - for (const event of timeline_events) { - if (await resolve_autonomy_responsibility(event, user)) { - const pull_request = event.source.issue.pull_request; - if (pull_request && pull_request.merged_at) { - merged_prs.push({ - issue_link: issue.html_url, - pr_link: pull_request.html_url, - }); - } - } - } - } - - if (!processedData[user]) { - processedData[user] = { - authored_issue_and_pr: [], - last_updated: "", - activity: [], - open_prs: [], - }; - } - - for (const pr of merged_prs) { - processedData[user].authored_issue_and_pr.push(pr); - } - - return processedData; -}; - -const fetchOpenPulls = async (user: string, org: string) => { - console.log(`Fetching open pull requests for ${user}`); - const { data } = await octokit.request("GET /search/issues", { - q: `is:pr is:open org:${org} author:${user}`, - }); - - let pulls: PullRequest[] = data.items; - - pulls.forEach((pr) => { - let today: Date = new Date(); - let prLastUpdated: Date = new Date(pr.updated_at); - let staleFor: number = Math.floor( - (today.getTime() - prLastUpdated.getTime()) / (1000 * 60 * 60 * 24), - ); - - if (!processedData[user]) { - processedData[user] = { - last_updated: "", - activity: [], - authored_issue_and_pr: [], - open_prs: [], - }; - } - processedData[user].open_prs.push({ - link: pr.html_url, - title: pr.title, - stale_for: staleFor, - labels: pr.labels.map((label: { name: string }) => label.name), - }); - }); - - console.log(`Fetched ${pulls.length} open pull requests for ${user}`); - return processedData; -}; -const scrapeGitHub = async ( - org: string, - dataDir: string, - date: string, - numDays: number = 1, -): Promise => { - const endDate: Date = startOfDay(parseISO(date)); - const startDate: Date = startOfDay(subDays(endDate, numDays)); - - console.log( - `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, - ); - - const events = await fetchEvents(org, startDate, endDate); - processedData = await parse_event(events); - - for (const user of Object.keys(processedData)) { - try { - await fetch_merge_events(user, org); - } catch (e) { - console.error(`Error fetching merge events for ${user}: ${e}`); - } - try { - await fetchOpenPulls(user, org); - } catch (e) { - console.error(`Error fetching open pulls for ${user}: ${e}`); - } - } - - console.log("Scraping completed"); -}; -function loadUserData(user: string, dataDir: string) { - const file = path.join(dataDir, `${user}.json`); - console.log(`Loading user data from ${file}`); - - try { - const response = fs.readFileSync(file); - const data: UserData = JSON.parse(response.toString()); - return data; - } catch (error: any) { - if (error.code === "ENOENT" || error.name === "SyntaxError") { - console.log(`User data not found for ${user}`); - return { activity: [] }; - } else { - throw error; // rethrow unexpected errors - } - } -} -function saveUserData( - user: string, - data: UserData, - dataDir: string, - serializer: any, -) { - const file = path.join(dataDir, `${user}.json`); - console.log(`Saving user data to ${file}`); - - try { - const jsonData = JSON.stringify(data, serializer, 2); - fs.writeFileSync(file, jsonData); - } catch (error: any) { - console.error(`Failed to save user data for ${user}: ${error.message}`); - throw error; - } -} -const merged_data = async (dataDir: string) => { - console.log("Updating data"); - fs.mkdirSync(dataDir, { recursive: true }); - - for (let user in processedData) { - if (processedData.hasOwnProperty(user)) { - console.log(`Merging user data for ${user}`); - let oldData = await loadUserData(user, dataDir); - let userData = processedData[user]; - let newUniqueEvents = []; - - for (let event of userData.activity) { - if ( - !oldData.activity.some( - (oldEvent: Activity) => - JSON.stringify(oldEvent) === JSON.stringify(event), - ) - ) { - newUniqueEvents.push(event); - } - } - - userData.activity = newUniqueEvents.concat(oldData.activity); - saveUserData(user, userData, dataDir, null); - } - } -}; - -const main = async () => { - // Extract command line arguments (skip the first two default arguments) - const args: string[] = process.argv.slice(2); - - // Destructure arguments with default values - const [ - orgName, - dataDir, - date = formatISO(subDays(new Date(), 1), { representation: "date" }), - numDays = 1, - ] = args; - - if (!orgName || !dataDir) { - console.error("Usage: node script.js [date] [numDays]"); - process.exit(1); - } - - await scrapeGitHub(orgName, dataDir, date, Number(numDays)); - await merged_data(dataDir); - console.log("Done"); -}; - -main(); diff --git a/scraper/src/github.py b/scraper/src/github.py index 85ee202f..2d2353c2 100755 --- a/scraper/src/github.py +++ b/scraper/src/github.py @@ -459,7 +459,7 @@ def merge_data(self): self.log.debug(f"Merging user data for {user}") old_data = self.load_user_data(user) data = self.data.get(user) - new_unique_events = [] + new_unique_events = [] for event in data["activity"]: if event not in old_data["activity"]: new_unique_events.append(event) @@ -512,7 +512,7 @@ def main(): if args.date is None: date = datetime.now(tz=ZoneInfo("UTC")) - timedelta(days=1) else: - date = datetime.strptime(args.date, "%Y-%m-%d").replace(tzinfo=ZoneInfo("UTC")) + date = datetime.strptime(date, "%Y-%m-%d").replace(tzinfo=ZoneInfo("UTC")) scraper = GitHubScraper( args.org_name, diff --git a/scraper/tsconfig.json b/scraper/tsconfig.json index 61d87b0c..19f6dcf5 100644 --- a/scraper/tsconfig.json +++ b/scraper/tsconfig.json @@ -1,36 +1,25 @@ - { "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "module": "ESNext", - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./*"] - } + "target": "es6", // Set the target ECMAScript version + "module": "commonjs", // Specify module code generation + "moduleResolution": "Node", + "strict": true, // Enable all strict type-checking options + "esModuleInterop": true, // Enable interoperability between CommonJS and ES Modules + "forceConsistentCasingInFileNames": true, // Ensure consistent casing in file names + "moduleResolution": "node", // Specify module resolution strategy + "outDir": "./dist", // Specify output directory + "sourceMap": true // Generate source maps for easier debugging }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules", "data-repo", "data"], + "include": [ + "src/**/*.ts" // Include TypeScript files in the 'src' directory + ], + "exclude": [ + "node_modules" // Exclude the 'node_modules' directory + ], "ts-node": { "compilerOptions": { - "module": "CommonJS" + "module": "commonjs", + "moduleResolution": "Node" } } } - diff --git a/tsconfig.json b/tsconfig.json index 0b57e39a..80ddaa01 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,5 +24,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules", "data-repo", "data"], + "exclude": ["node_modules", "data-repo", "data"] } From 0bb6d006bda783d10965670372192be71cc0d0af Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Sat, 8 Jun 2024 18:46:56 +0530 Subject: [PATCH 05/71] Scraper githu.ts divided into different files for better understanding --- scraper/src/github.ts => github.ts | 436 +++--- lib/octokit.ts | 2 +- package.json | 3 +- scraper/package-lock.json | 1420 +++++++++++-------- scraper/package.json | 44 +- scraper/src/github-scraper/config.ts | 11 + scraper/src/github-scraper/fetchEvents.ts | 52 + scraper/src/github-scraper/fetchUserData.ts | 63 + scraper/src/github-scraper/index.ts | 81 ++ scraper/src/github-scraper/parseEvents.ts | 190 +++ scraper/src/github-scraper/saveData.ts | 33 + scraper/src/github-scraper/types.ts | 196 +++ scraper/src/github-scraper/utils.ts | 143 ++ scraper/tsconfig.json | 31 +- 14 files changed, 1824 insertions(+), 881 deletions(-) rename scraper/src/github.ts => github.ts (89%) create mode 100644 scraper/src/github-scraper/config.ts create mode 100644 scraper/src/github-scraper/fetchEvents.ts create mode 100644 scraper/src/github-scraper/fetchUserData.ts create mode 100644 scraper/src/github-scraper/index.ts create mode 100644 scraper/src/github-scraper/parseEvents.ts create mode 100644 scraper/src/github-scraper/saveData.ts create mode 100644 scraper/src/github-scraper/types.ts create mode 100644 scraper/src/github-scraper/utils.ts diff --git a/scraper/src/github.ts b/github.ts similarity index 89% rename from scraper/src/github.ts rename to github.ts index 6ff9ce3d..3be59fd0 100644 --- a/scraper/src/github.ts +++ b/github.ts @@ -1,37 +1,130 @@ -import { formatISO, parseISO, startOfDay, subDays } from "date-fns"; import fs from "fs"; import path from "path"; -import { Activity, ActivityData, ProcessData, Action } from "../../lib/types"; -import { PullRequestEvent, IGitHubEvent } from "../../lib/gh_events"; -import { Octokit } from "octokit"; +import { parseISO, subDays, formatISO, startOfDay } from "date-fns"; +import octokit from "./lib/octokit"; +import { + Action, + Activity, + ProcessData, + UserData, + // IGitHubEvent, + PullRequest, + PullRequestEvent, +} from "./lib/scraper_types"; +import { IGitHubEvent as IG } from "./lib/gh_events"; +const userBlacklist = new Set(["dependabot", "snyk-bot", "codecov-commenter"]); let processedData: ProcessData = {}; -const GITHUB_TOKEN = process.env.GITHUB_TOKEN; -if (!GITHUB_TOKEN) { - console.error("GITHUB_TOKEN not found in environment"); - process.exit(1); +async function addCollaborations(event: PullRequestEvent, eventTime: Date) { + let nameUserCache: { [key: string]: string } = {}; + let emailUserCache: { [key: string]: string } = {}; + const collaborators: Set = new Set(); + + const url: string | undefined = event.payload.pull_request?.commits_url; + + const response = await octokit.request("GET " + url); + const commits = response.data; + for (const commit of commits) { + let authorLogin = commit.author && commit.author.login; + if (!authorLogin) { + authorLogin = commit.commit.author.name; + } + + if (isBlacklisted(authorLogin)) { + continue; + } + + collaborators.add(authorLogin); + + const coAuthors = commit.commit.message.match( + /Co-authored-by: (.+) <(.+)>/, + ); + if (coAuthors) { + for (const [name, email] of coAuthors) { + if (isBlacklisted(name)) { + continue; + } + + if (name in nameUserCache) { + collaborators.add(nameUserCache[name]); + continue; + } + + if (email in emailUserCache) { + collaborators.add(emailUserCache[email]); + continue; + } + + try { + const usersByEmail = await octokit.request("GET /search/users", { + q: email, + }); + + if (usersByEmail.data.total_count > 0) { + const login = usersByEmail.data.items[0].login; + emailUserCache[email] = login; + collaborators.add(login); + continue; + } + const usersByName = await octokit.request("GET /search/users", { + q: name, + }); + + if (usersByName.data.total_count === 1) { + const login = usersByName.data.items[0].login; + nameUserCache[name] = login; + collaborators.add(login); + } + } catch (e) { + console.error( + `Error fetching co-authors for commit ${commit} - ${name} <${email}>: ${e}`, + ); + } + } + } + } + + if (collaborators.size > 1) { + const collaboratorArray = Array.from(collaborators); // Convert Set to Array + for (const user of collaboratorArray) { + const others = new Set(collaborators); + const othersArray = Array.from(others); + + others.delete(user); + appendEvent(user, { + type: "pr_collaborated", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + collaborated_with: [...othersArray], + }); + } + } } -const octokit = new Octokit({ - auth: GITHUB_TOKEN, -}); -const fetchEvents = async (org: string, startDate: Date, endDate: Date) => { +const fetchEvents = async ( + org: string, + startDate: Date, + endDate: Date, + page: number = 1, +) => { const events = await octokit.paginate( "GET /orgs/{org}/events", { org: org, per_page: 1000, }, - (response: { data: IGitHubEvent[] }) => { - return response.data; + (response: { data: IG[] }) => { + return response.data as IG[]; }, ); - + console.log(events.length); let eventsCount: number = 0; let filteredEvents = []; for (const event of events) { - const eventTime: Date = new Date(event.created_at ?? 0); + const eventTime: Date = new Date(event.created_at ?? ""); if (eventTime > endDate) { continue; @@ -49,18 +142,21 @@ const fetchEvents = async (org: string, startDate: Date, endDate: Date) => { "IssuesEvent", "PullRequestEvent", "PullRequestReviewEvent", - ].includes(event.type ?? ""); - console.log(isRequiredEventType); + ].includes(event.type); + if (!isBlacklisted && isRequiredEventType) { - console.log(event.type); + console.log("Hello"); filteredEvents.push(event); + eventsCount++; } - eventsCount++; } console.log("Fetched " + { eventsCount } + " events"); - + console.log(filteredEvents.length); return filteredEvents; }; +const isBlacklisted = (login: string): boolean => { + return login.includes("[bot]") || userBlacklist.has(login); +}; function appendEvent(user: string, event: Activity) { console.log(`Appending event for ${user}`); if (!processedData[user]) { @@ -73,16 +169,11 @@ function appendEvent(user: string, event: Activity) { }; } else { processedData[user]["activity"].push(event); - if (event["time"] > (processedData[user]["last_updated"] ?? 0)) { + if (event["time"] > processedData[user]["last_updated"]) { processedData[user]["last_updated"] = event["time"]; } } } -const userBlacklist = new Set(["dependabot", "snyk-bot", "codecov-commenter"]); - -const isBlacklisted = (login: string): boolean => { - return login.includes("[bot]") || userBlacklist.has(login); -}; function parseISODate(isoDate: Date) { return new Date(isoDate); } @@ -141,9 +232,13 @@ async function calculateTurnaroundTime(event: PullRequestEvent) { issue_number: issueNumber, }, ); + type issueTimelineResponseType = typeof issueTimelineResponse; const issueTimeline = issueTimelineResponse.data; - issueTimeline.forEach((action: Action) => { + + // const issueTimeline = issueTimelineResponse.data; + + issueTimeline.forEach((action: any) => { if (action.event === "assigned" && action.assignee.login === user) { assignedAts.push({ issue: `${org}/${repo}#${issueNumber}`, @@ -167,94 +262,80 @@ async function calculateTurnaroundTime(event: PullRequestEvent) { (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; return turnaroundTime; } -async function addCollaborations(event: PullRequestEvent, eventTime: Date) { - let nameUserCache: { [key: string]: string } = {}; - let emailUserCache: { [key: string]: string } = {}; - const collaborators: Set = new Set(); - const url: string | undefined = event.payload.pull_request?.commits_url; - - const response = await octokit.request("GET " + url); - const commits = response.data; - for (const commit of commits) { - let authorLogin = commit.author && commit.author.login; - if (!authorLogin) { - authorLogin = commit.commit.author.name; - } - - if (isBlacklisted(authorLogin)) { - continue; - } - - collaborators.add(authorLogin); +const parse_event = async (events: any) => { + for (const event of events) { + const eventTime: Date = parseISO(event.created_at); + const user: string = event.actor.login; + if (isBlacklisted(user)) continue; - const coAuthors = commit.commit.message.match( - /Co-authored-by: (.+) <(.+)>/, - ); - if (coAuthors) { - for (const [name, email] of coAuthors) { - if (isBlacklisted(name)) { - continue; - } + console.log("Processing event for user: ", user); + console.log("event_id : ", event.id); - if (name in nameUserCache) { - collaborators.add(nameUserCache[name]); - continue; + switch (event.type) { + case "IssueCommentEvent": + if (event.payload.action === "created") { + appendEvent(user, { + type: "comment_created", + title: `${event.repo.name}#${event.payload.issue.number}`, + time: eventTime.toISOString(), + link: event.payload.comment.html_url, + text: event.payload.comment.body, + }); } - - if (email in emailUserCache) { - collaborators.add(emailUserCache[email]); - continue; + break; + case "IssuesEvent": + if (["opened", "assigned", "closed"].includes(event.payload.action)) { + appendEvent(user, { + type: `issue_${event.payload.action}`, + title: `${event.repo.name}#${event.payload.issue.number}`, + time: eventTime.toISOString(), + link: event.payload.issue.html_url, + text: event.payload.issue.title, + }); } - - try { - const usersByEmail = await octokit.request("GET /search/users", { - q: email, + break; + case "PullRequestEvent": + if (event.payload.action === "opened") { + appendEvent(user, { + type: "pr_opened", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, }); - - if (usersByEmail.data.total_count > 0) { - const login = usersByEmail.data.items[0].login; - emailUserCache[email] = login; - collaborators.add(login); - continue; - } - const usersByName = await octokit.request("GET /search/users", { - q: name, + } else if ( + event.payload.action === "closed" && + event.payload.pull_request.merged + ) { + const turnaroundTime: number = await calculateTurnaroundTime(event); + appendEvent(user, { + type: "pr_merged", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + turnaround_time: turnaroundTime, }); - - if (usersByName.data.total_count === 1) { - const login = usersByName.data.items[0].login; - nameUserCache[name] = login; - collaborators.add(login); - } - } catch (e) { - console.error( - `Error fetching co-authors for commit ${commit} - ${name} <${email}>: ${e}`, - ); + await addCollaborations(event, eventTime); } - } - } - } - - if (collaborators.size > 1) { - const collaboratorArray = Array.from(collaborators); // Convert Set to Array - for (const user of collaboratorArray) { - const others = new Set(collaborators); - const othersArray = Array.from(others); - - others.delete(user); - appendEvent(user, { - type: "pr_collaborated", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - collaborated_with: [...othersArray], - }); + break; + case "PullRequestReviewEvent": + appendEvent(user, { + type: "pr_reviewed", + time: eventTime.toISOString(), + title: `${event.repo.name}#${event.payload.pull_request.number}`, + link: event.payload.review.html_url, + text: event.payload.pull_request.title, + }); + break; + default: + break; } } -} -async function resolve_autonomy_responsibility(event: Action, user: string) { + return processedData; +}; +async function resolve_autonomy_responsibility(event: any, user: string) { if (event.event === "cross-referenced" && event.source.type === "issue") { return event.source.issue.user.login === user; } @@ -310,10 +391,10 @@ const fetchOpenPulls = async (user: string, org: string) => { q: `is:pr is:open org:${org} author:${user}`, }); - type PullsData = (typeof data.items)[0]; - let pulls: PullsData[] = data.items; + // let pulls = data.items; + let pulls = data.items as unknown as PullRequest[]; - pulls.forEach((pr: PullsData) => { + pulls.forEach((pr) => { let today: Date = new Date(); let prLastUpdated: Date = new Date(pr.updated_at); let staleFor: number = Math.floor( @@ -339,77 +420,36 @@ const fetchOpenPulls = async (user: string, org: string) => { console.log(`Fetched ${pulls.length} open pull requests for ${user}`); return processedData; }; -const parse_event = async (events: IGitHubEvent[]) => { - for (const event of events) { - const eventTime: Date = parseISO(event.created_at); - const user: string = event.actor.login; - if (isBlacklisted(user)) continue; +const scrapeGitHub = async ( + org: string, + dataDir: string, + date: string, + numDays: number = 1, +): Promise => { + const endDate: Date = startOfDay(parseISO(date)); + const startDate: Date = startOfDay(subDays(endDate, numDays)); - console.log("Processing event for user: ", user); - console.log("event_id : ", event.id); + console.log( + `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, + ); - switch (event.type) { - case "IssueCommentEvent": - if (event.payload.action === "created") { - appendEvent(user, { - type: "comment_created", - title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), - link: event.payload.comment.html_url, - text: event.payload.comment.body, - }); - } - break; - case "IssuesEvent": - if (["opened", "assigned", "closed"].includes(event.payload.action)) { - appendEvent(user, { - type: `issue_${event.payload.action}`, - title: `${event.repo.name}#${event.payload.issue?.number}`, - time: eventTime.toISOString(), - link: event.payload.issue.html_url, - text: event.payload.issue.title, - }); - } - break; - case "PullRequestEvent": - if (event.payload.action === "opened") { - appendEvent(user, { - type: "pr_opened", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - }); - } else if ( - event.payload.action === "closed" && - event.payload.pull_request?.merged - ) { - const turnaroundTime: number = await calculateTurnaroundTime(event); - appendEvent(user, { - type: "pr_merged", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - turnaround_time: turnaroundTime, - }); - await addCollaborations(event, eventTime); - } - break; - case "PullRequestReviewEvent": - appendEvent(user, { - type: "pr_reviewed", - time: eventTime.toISOString(), - title: `${event.repo.name}#${event.payload.pull_request.number}`, - link: event.payload.review.html_url, - text: event.payload.pull_request.title, - }); - break; - default: - break; + const events: any = await fetchEvents(org, startDate, endDate); + processedData = await parse_event(events); + + for (const user of Object.keys(processedData)) { + try { + await fetch_merge_events(user, org); + } catch (e) { + console.error(`Error fetching merge events for ${user}: ${e}`); + } + try { + await fetchOpenPulls(user, org); + } catch (e) { + console.error(`Error fetching open pulls for ${user}: ${e}`); } } - return processedData; + + console.log("Scraping completed"); }; function loadUserData(user: string, dataDir: string) { const file = path.join(dataDir, `${user}.json`); @@ -417,7 +457,7 @@ function loadUserData(user: string, dataDir: string) { try { const response = fs.readFileSync(file); - const data: ActivityData = JSON.parse(response.toString()); + const data: UserData = JSON.parse(response.toString()); return data; } catch (error: any) { if (error.code === "ENOENT" || error.name === "SyntaxError") { @@ -430,7 +470,7 @@ function loadUserData(user: string, dataDir: string) { } function saveUserData( user: string, - data: ActivityData, + data: UserData, dataDir: string, serializer: any, ) { @@ -459,7 +499,8 @@ const merged_data = async (dataDir: string) => { for (let event of userData.activity) { if ( !oldData.activity.some( - (oldEvent) => JSON.stringify(oldEvent) === JSON.stringify(event), + (oldEvent: Activity) => + JSON.stringify(oldEvent) === JSON.stringify(event), ) ) { newUniqueEvents.push(event); @@ -472,41 +513,6 @@ const merged_data = async (dataDir: string) => { } }; -const scrapeGitHub = async ( - org: string, - date: string, - numDays: number = 1, -): Promise => { - const endDate: Date = startOfDay(parseISO(date)); - const startDate: Date = startOfDay(subDays(endDate, numDays)); - console.log( - `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, - ); - - const events: IGitHubEvent[] = (await fetchEvents( - org, - startDate, - endDate, - )) as IGitHubEvent[]; - processedData = await parse_event(events); - - for (const user of Object.keys(processedData)) { - try { - await fetch_merge_events(user, org); - } catch (e) { - console.error(`Error fetching merge events for ${user}: ${e}`); - } - try { - await fetchOpenPulls(user, org); - } catch (e) { - console.error(`Error fetching open pulls for ${user}: ${e}`); - } - } - - console.log("Scraping completed"); -}; - -// Type Done and check done const main = async () => { // Extract command line arguments (skip the first two default arguments) const args: string[] = process.argv.slice(2); @@ -524,7 +530,7 @@ const main = async () => { process.exit(1); } - await scrapeGitHub(orgName, date, Number(numDays)); + await scrapeGitHub(orgName, dataDir, date, Number(numDays)); await merged_data(dataDir); console.log("Done"); }; diff --git a/lib/octokit.ts b/lib/octokit.ts index 0a472213..7c1e872a 100644 --- a/lib/octokit.ts +++ b/lib/octokit.ts @@ -21,4 +21,4 @@ const octokit = new Octokit({ auth: getGitHubAccessToken(), }); -export default octokit; \ No newline at end of file +export default octokit; diff --git a/package.json b/package.json index f390282e..2f8b086f 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,11 @@ "lint-fix": "eslint . --fix", "format": "prettier --write .", "load-data": "node ./scripts/loadOrgData.js", - "prepare": "husky install" + "prepare": "husky install", }, "dependencies": { "@headlessui/react": "^1.7.18", + "@octokit/core": "^5.2.0", "@t3-oss/env-nextjs": "^0.9.2", "@vercel/kv": "^1.0.1", "clsx": "^1.2.1", diff --git a/scraper/package-lock.json b/scraper/package-lock.json index 840b621b..d78b5ac1 100644 --- a/scraper/package-lock.json +++ b/scraper/package-lock.json @@ -1,626 +1,802 @@ { - "name": "scraper", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "scraper", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@octokit/graphql": "^8.1.1", - "@octokit/types": "^13.5.0", - "date-fns": "^3.6.0", - "octokit": "^4.0.2", - "yargs": "^17.7.2" - }, - "devDependencies": { - "typescript": "^5.4.5" - } - }, - "node_modules/@octokit/app": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.0.1.tgz", - "integrity": "sha512-nwSjC349E6/wruMCo944y1dBC7uKzUYrBMoC4Qx/xfLLBmD+R66oMKB1jXS2HYRF9Hqh/Alq3UNRggVWZxjvUg==", - "dependencies": { - "@octokit/auth-app": "^7.0.0", - "@octokit/auth-unauthenticated": "^6.0.0", - "@octokit/core": "^6.1.2", - "@octokit/oauth-app": "^7.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/types": "^13.0.0", - "@octokit/webhooks": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-app": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.0.tgz", - "integrity": "sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==", - "dependencies": { - "@octokit/auth-oauth-app": "^8.1.0", - "@octokit/auth-oauth-user": "^5.1.0", - "@octokit/request": "^9.1.1", - "@octokit/request-error": "^6.1.1", - "@octokit/types": "^13.4.1", - "lru-cache": "^10.0.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-app": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz", - "integrity": "sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==", - "dependencies": { - "@octokit/auth-oauth-device": "^7.0.0", - "@octokit/auth-oauth-user": "^5.0.1", - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-device": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz", - "integrity": "sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==", - "dependencies": { - "@octokit/oauth-methods": "^5.0.0", - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-user": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz", - "integrity": "sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==", - "dependencies": { - "@octokit/auth-oauth-device": "^7.0.1", - "@octokit/oauth-methods": "^5.0.0", - "@octokit/request": "^9.0.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-token": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", - "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-unauthenticated": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz", - "integrity": "sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==", - "dependencies": { - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", - "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.0.0", - "@octokit/request": "^9.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", - "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", - "dependencies": { - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", - "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", - "dependencies": { - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-app": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.2.tgz", - "integrity": "sha512-4ntCOZIiTozKwuYQroX/ZD722tzMH8Eicv/cgDM/3F3lyrlwENHDv4flTCBpSJbfK546B2SrkKMWB+/HbS84zQ==", - "dependencies": { - "@octokit/auth-oauth-app": "^8.0.0", - "@octokit/auth-oauth-user": "^5.0.1", - "@octokit/auth-unauthenticated": "^6.0.0-beta.1", - "@octokit/core": "^6.0.0", - "@octokit/oauth-authorization-url": "^7.0.0", - "@octokit/oauth-methods": "^5.0.0", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", - "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-methods": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz", - "integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==", - "dependencies": { - "@octokit/oauth-authorization-url": "^7.0.0", - "@octokit/request": "^9.1.0", - "@octokit/request-error": "^6.1.0", - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" - }, - "node_modules/@octokit/openapi-webhooks-types": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.2.1.tgz", - "integrity": "sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==" - }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.2.tgz", - "integrity": "sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.0.tgz", - "integrity": "sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==", - "dependencies": { - "@octokit/types": "^13.5.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.1.tgz", - "integrity": "sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==", - "dependencies": { - "@octokit/types": "^13.5.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-retry": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.1.tgz", - "integrity": "sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==", - "dependencies": { - "@octokit/request-error": "^6.0.0", - "@octokit/types": "^13.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-throttling": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.3.0.tgz", - "integrity": "sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==", - "dependencies": { - "@octokit/types": "^13.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^6.0.0" - } - }, - "node_modules/@octokit/request": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.1.tgz", - "integrity": "sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==", - "dependencies": { - "@octokit/endpoint": "^10.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.1.tgz", - "integrity": "sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==", - "dependencies": { - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", - "dependencies": { - "@octokit/openapi-types": "^22.2.0" - } - }, - "node_modules/@octokit/webhooks": { - "version": "13.2.7", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.2.7.tgz", - "integrity": "sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==", - "dependencies": { - "@octokit/openapi-webhooks-types": "8.2.1", - "@octokit/request-error": "^6.0.1", - "@octokit/webhooks-methods": "^5.0.0", - "aggregate-error": "^5.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/webhooks-methods": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz", - "integrity": "sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@types/aws-lambda": { - "version": "8.10.138", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.138.tgz", - "integrity": "sha512-71EHMl70TPWIAsFuHd85NHq6S6T2OOjiisPTrH7RgcjzpJpPh4RQJv7PvVvIxc6PIp8CLV7F9B+TdjcAES5vcA==" - }, - "node_modules/aggregate-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", - "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" - }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" - }, - "node_modules/clean-stack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/octokit": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-4.0.2.tgz", - "integrity": "sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==", - "dependencies": { - "@octokit/app": "^15.0.0", - "@octokit/core": "^6.0.0", - "@octokit/oauth-app": "^7.0.0", - "@octokit/plugin-paginate-graphql": "^5.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/plugin-rest-endpoint-methods": "^13.0.0", - "@octokit/plugin-retry": "^7.0.0", - "@octokit/plugin-throttling": "^9.0.0", - "@octokit/request-error": "^6.0.0", - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/universal-github-app-jwt": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz", - "integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==" - }, - "node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } + "name": "scraper", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scraper", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@octokit/core": "^6.1.2", + "@octokit/types": "^13.5.0", + "date-fns": "^3.6.0", + "octokit": "^4.0.2", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@octokit/core": "^6.1.2", + "@types/node": "^16.11.18", + "ts-node": "^10.9.2", + "typescript": "^4.9.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@octokit/app": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.0.1.tgz", + "integrity": "sha512-nwSjC349E6/wruMCo944y1dBC7uKzUYrBMoC4Qx/xfLLBmD+R66oMKB1jXS2HYRF9Hqh/Alq3UNRggVWZxjvUg==", + "dependencies": { + "@octokit/auth-app": "^7.0.0", + "@octokit/auth-unauthenticated": "^6.0.0", + "@octokit/core": "^6.1.2", + "@octokit/oauth-app": "^7.0.0", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/types": "^13.0.0", + "@octokit/webhooks": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.0.tgz", + "integrity": "sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==", + "dependencies": { + "@octokit/auth-oauth-app": "^8.1.0", + "@octokit/auth-oauth-user": "^5.1.0", + "@octokit/request": "^9.1.1", + "@octokit/request-error": "^6.1.1", + "@octokit/types": "^13.4.1", + "lru-cache": "^10.0.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz", + "integrity": "sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==", + "dependencies": { + "@octokit/auth-oauth-device": "^7.0.0", + "@octokit/auth-oauth-user": "^5.0.1", + "@octokit/request": "^9.0.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz", + "integrity": "sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==", + "dependencies": { + "@octokit/oauth-methods": "^5.0.0", + "@octokit/request": "^9.0.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz", + "integrity": "sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==", + "dependencies": { + "@octokit/auth-oauth-device": "^7.0.1", + "@octokit/oauth-methods": "^5.0.0", + "@octokit/request": "^9.0.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz", + "integrity": "sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==", + "dependencies": { + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", + "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.0.0", + "@octokit/request": "^9.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", + "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", + "dependencies": { + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", + "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", + "dependencies": { + "@octokit/request": "^9.0.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.2.tgz", + "integrity": "sha512-4ntCOZIiTozKwuYQroX/ZD722tzMH8Eicv/cgDM/3F3lyrlwENHDv4flTCBpSJbfK546B2SrkKMWB+/HbS84zQ==", + "dependencies": { + "@octokit/auth-oauth-app": "^8.0.0", + "@octokit/auth-oauth-user": "^5.0.1", + "@octokit/auth-unauthenticated": "^6.0.0-beta.1", + "@octokit/core": "^6.0.0", + "@octokit/oauth-authorization-url": "^7.0.0", + "@octokit/oauth-methods": "^5.0.0", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", + "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz", + "integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==", + "dependencies": { + "@octokit/oauth-authorization-url": "^7.0.0", + "@octokit/request": "^9.1.0", + "@octokit/request-error": "^6.1.0", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "node_modules/@octokit/openapi-webhooks-types": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.2.1.tgz", + "integrity": "sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.2.tgz", + "integrity": "sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.0.tgz", + "integrity": "sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.1.tgz", + "integrity": "sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.1.tgz", + "integrity": "sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==", + "dependencies": { + "@octokit/request-error": "^6.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.3.0.tgz", + "integrity": "sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==", + "dependencies": { + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^6.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.1.tgz", + "integrity": "sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.1.tgz", + "integrity": "sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==", + "dependencies": { + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "13.2.7", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.2.7.tgz", + "integrity": "sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==", + "dependencies": { + "@octokit/openapi-webhooks-types": "8.2.1", + "@octokit/request-error": "^6.0.1", + "@octokit/webhooks-methods": "^5.0.0", + "aggregate-error": "^5.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz", + "integrity": "sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.138", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.138.tgz", + "integrity": "sha512-71EHMl70TPWIAsFuHd85NHq6S6T2OOjiisPTrH7RgcjzpJpPh4RQJv7PvVvIxc6PIp8CLV7F9B+TdjcAES5vcA==" + }, + "node_modules/@types/node": { + "version": "16.18.98", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.98.tgz", + "integrity": "sha512-fpiC20NvLpTLAzo3oVBKIqBGR6Fx/8oAK/SSf7G+fydnXMY1x4x9RZ6sBXhqKlCU21g2QapUsbLlhv3+a7wS+Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + }, + "node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/octokit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-4.0.2.tgz", + "integrity": "sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==", + "dependencies": { + "@octokit/app": "^15.0.0", + "@octokit/core": "^6.0.0", + "@octokit/oauth-app": "^7.0.0", + "@octokit/plugin-paginate-graphql": "^5.0.0", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-rest-endpoint-methods": "^13.0.0", + "@octokit/plugin-retry": "^7.0.0", + "@octokit/plugin-throttling": "^9.0.0", + "@octokit/request-error": "^6.0.0", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true } + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universal-github-app-jwt": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz", + "integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==" + }, + "node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } + } } diff --git a/scraper/package.json b/scraper/package.json index 4eecf52e..8e91911d 100644 --- a/scraper/package.json +++ b/scraper/package.json @@ -1,22 +1,24 @@ - { - "name": "scraper", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@octokit/graphql": "^8.1.1", - "@octokit/types": "^13.5.0", - "date-fns": "^3.6.0", - "octokit": "^4.0.2", - "yargs": "^17.7.2" - }, - "devDependencies": { - "typescript": "^5.4.5" - } +{ + "name": "scraper", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "scraper": "node --loader ts-node/esm src/github-scraper/index.ts coronasafe ../../data-repo/github" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "date-fns": "^3.6.0", + "octokit": "^4.0.2", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@types/node": "^16.11.18", + "ts-node": "^10.9.2", + "typescript": "^4.9.5" } +} diff --git a/scraper/src/github-scraper/config.ts b/scraper/src/github-scraper/config.ts new file mode 100644 index 00000000..ff3f3567 --- /dev/null +++ b/scraper/src/github-scraper/config.ts @@ -0,0 +1,11 @@ +import { Octokit } from "octokit"; + +export const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +if (!GITHUB_TOKEN) { + console.error("GITHUB_TOKEN not found in environment"); + process.exit(1); +} + +export const octokit = new Octokit({ + auth: GITHUB_TOKEN, +}); diff --git a/scraper/src/github-scraper/fetchEvents.ts b/scraper/src/github-scraper/fetchEvents.ts new file mode 100644 index 00000000..6e49d269 --- /dev/null +++ b/scraper/src/github-scraper/fetchEvents.ts @@ -0,0 +1,52 @@ +import { octokit } from "./config.js"; +import { IGitHubEvent } from "./types.js"; + +export const fetchEvents = async ( + org: string, + startDate: Date, + endDate: Date, +) => { + const events = await octokit.paginate( + "GET /orgs/{org}/events", + { + org: org, + per_page: 1000, + }, + (response: { data: IGitHubEvent[] }) => { + return response.data; + }, + ); + + let eventsCount: number = 0; + let filteredEvents = []; + for (const event of events) { + const eventTime: Date = new Date(event.created_at ?? 0); + + if (eventTime > endDate) { + continue; + } else if (eventTime <= startDate) { + return filteredEvents; + } + const isBlacklisted: boolean = [ + "dependabot", + "snyk-bot", + "codecov-commenter", + "github-actions[bot]", + ].includes(event.actor.login); + const isRequiredEventType: boolean = [ + "IssueCommentEvent", + "IssuesEvent", + "PullRequestEvent", + "PullRequestReviewEvent", + ].includes(event.type ?? ""); + + if (!isBlacklisted && isRequiredEventType) { + console.log(event.type); + filteredEvents.push(event); + } + eventsCount++; + } + console.log("Fetched " + { eventsCount } + " events"); + + return filteredEvents; +}; diff --git a/scraper/src/github-scraper/fetchUserData.ts b/scraper/src/github-scraper/fetchUserData.ts new file mode 100644 index 00000000..98a9e62b --- /dev/null +++ b/scraper/src/github-scraper/fetchUserData.ts @@ -0,0 +1,63 @@ +import { octokit } from "./config.js"; +import { OpenPr } from "./types.js"; +import { resolve_autonomy_responsibility } from "./utils.js"; + +export const fetch_merge_events = async (user: string, org: string) => { + console.log("Merge events for : ", user); + + // Fetching closed issues authored by the user + const { data: issues } = await octokit.request("GET /search/issues", { + q: `is:issue is:closed org:${org} author:${user}`, + }); + + let merged_prs = []; + + for (const issue of issues.items) { + const { data: timeline_events } = await octokit.request( + "GET " + issue.timeline_url, + ); + + for (const event of timeline_events) { + if (await resolve_autonomy_responsibility(event, user)) { + const pull_request = event.source.issue.pull_request; + if (pull_request && pull_request.merged_at) { + merged_prs.push({ + issue_link: issue.html_url, + pr_link: pull_request.html_url, + }); + } + } + } + } + + return merged_prs; +}; + +export const fetchOpenPulls = async (user: string, org: string) => { + console.log(`Fetching open pull requests for ${user}`); + const { data } = await octokit.request("GET /search/issues", { + q: `is:pr is:open org:${org} author:${user}`, + }); + + type PullsData = (typeof data.items)[0]; + let pulls: PullsData[] = data.items; + let open_prs: OpenPr[] = []; + + pulls.forEach((pr: PullsData) => { + let today: Date = new Date(); + let prLastUpdated: Date = new Date(pr.updated_at); + let staleFor: number = Math.floor( + (today.getTime() - prLastUpdated.getTime()) / (1000 * 60 * 60 * 24), + ); + + open_prs.push({ + link: pr.html_url, + title: pr.title, + stale_for: staleFor, + labels: pr.labels.map((label: { name: string }) => label.name), + }); + }); + + console.log(`Fetched ${pulls.length} open pull requests for ${user}`); + return open_prs; +}; diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts new file mode 100644 index 00000000..aeca36c4 --- /dev/null +++ b/scraper/src/github-scraper/index.ts @@ -0,0 +1,81 @@ +import { formatISO, parseISO, startOfDay, subDays } from "date-fns"; +import { IGitHubEvent, ProcessData } from "./types.js"; +import { fetch_merge_events, fetchOpenPulls } from "./fetchUserData.js"; +import { fetchEvents } from "./fetchEvents.js"; +import { parseEvents } from "./parseEvents.js"; +import { merged_data } from "./saveData.js"; + +let processedData: ProcessData = {}; + +const scrapeGitHub = async ( + org: string, + date: string, + numDays: number = 1, +): Promise => { + const endDate: Date = startOfDay(parseISO(date)); + const startDate: Date = startOfDay(subDays(endDate, numDays)); + console.log( + `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, + ); + + const events: IGitHubEvent[] = (await fetchEvents( + org, + startDate, + endDate, + )) as IGitHubEvent[]; + processedData = await parseEvents(events); + + for (const user of Object.keys(processedData)) { + if (!processedData[user]) { + processedData[user] = { + authored_issue_and_pr: [], + last_updated: "", + activity: [], + open_prs: [], + }; + } + try { + const merged_prs = await fetch_merge_events(user, org); + for (const pr of merged_prs) { + processedData[user].authored_issue_and_pr.push(pr); + } + } catch (e) { + console.error(`Error fetching merge events for ${user}: ${e}`); + } + try { + const open_prs = await fetchOpenPulls(user, org); + for (const pr of open_prs) { + processedData[user].open_prs.push(pr); + } + } catch (e) { + console.error(`Error fetching open pulls for ${user}: ${e}`); + } + } + + console.log("Scraping completed"); +}; + +// Type Done and check done +const main = async () => { + // Extract command line arguments (skip the first two default arguments) + const args: string[] = process.argv.slice(2); + + // Destructure arguments with default values + const [ + orgName, + dataDir, + date = formatISO(subDays(new Date(), 1), { representation: "date" }), + numDays = 1, + ] = args; + + if (!orgName || !dataDir) { + console.error("Usage: node script.js [date] [numDays]"); + process.exit(1); + } + + await scrapeGitHub(orgName, date, Number(numDays)); + await merged_data(dataDir, processedData); + console.log("Done"); +}; + +main(); diff --git a/scraper/src/github-scraper/parseEvents.ts b/scraper/src/github-scraper/parseEvents.ts new file mode 100644 index 00000000..690c8453 --- /dev/null +++ b/scraper/src/github-scraper/parseEvents.ts @@ -0,0 +1,190 @@ +import { + Activity, + IGitHubEvent, + ProcessData, + PullRequestEvent, +} from "./types.js"; +import { calculateTurnaroundTime } from "./utils.js"; +import { parseISO } from "date-fns"; +import { isBlacklisted } from "./utils.js"; +import { octokit } from "./config.js"; + +let processedData: ProcessData = {}; + +function appendEvent(user: string, event: Activity) { + console.log(`Appending event for ${user}`); + if (!processedData[user]) { + console.log(`Creating new user data for ${user}`); + processedData[user] = { + last_updated: event.time, + activity: [event], + open_prs: [], + authored_issue_and_pr: [], + }; + } else { + processedData[user]["activity"].push(event); + if (event["time"] > (processedData[user]["last_updated"] ?? 0)) { + processedData[user]["last_updated"] = event["time"]; + } + } +} + +async function addCollaborations(event: PullRequestEvent, eventTime: Date) { + let nameUserCache: { [key: string]: string } = {}; + let emailUserCache: { [key: string]: string } = {}; + const collaborators: Set = new Set(); + + const url: string | undefined = event.payload.pull_request?.commits_url; + + const response = await octokit.request("GET " + url); + const commits = response.data; + for (const commit of commits) { + let authorLogin = commit.author && commit.author.login; + if (!authorLogin) { + authorLogin = commit.commit.author.name; + } + + if (isBlacklisted(authorLogin)) { + continue; + } + + collaborators.add(authorLogin); + + const coAuthors = commit.commit.message.match( + /Co-authored-by: (.+) <(.+)>/, + ); + if (coAuthors) { + for (const [name, email] of coAuthors) { + if (isBlacklisted(name)) { + continue; + } + + if (name in nameUserCache) { + collaborators.add(nameUserCache[name]); + continue; + } + + if (email in emailUserCache) { + collaborators.add(emailUserCache[email]); + continue; + } + + try { + const usersByEmail = await octokit.request("GET /search/users", { + q: email, + }); + + if (usersByEmail.data.total_count > 0) { + const login = usersByEmail.data.items[0].login; + emailUserCache[email] = login; + collaborators.add(login); + continue; + } + const usersByName = await octokit.request("GET /search/users", { + q: name, + }); + + if (usersByName.data.total_count === 1) { + const login = usersByName.data.items[0].login; + nameUserCache[name] = login; + collaborators.add(login); + } + } catch (e) { + console.error( + `Error fetching co-authors for commit ${commit} - ${name} <${email}>: ${e}`, + ); + } + } + } + } + + if (collaborators.size > 1) { + const collaboratorArray = Array.from(collaborators); // Convert Set to Array + for (const user of collaboratorArray) { + const others = new Set(collaborators); + const othersArray = Array.from(others); + + others.delete(user); + appendEvent(user, { + type: "pr_collaborated", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + collaborated_with: [...othersArray], + }); + } + } +} + +export const parseEvents = async (events: IGitHubEvent[]) => { + for (const event of events) { + const eventTime = parseISO(event.created_at); + const user = event.actor.login; + if (isBlacklisted(user)) continue; + + console.log("Processing event for user:", user + " | " + "event_id : ", event.id); + + switch (event.type) { + case "IssueCommentEvent": + if (event.payload.action === "created") { + appendEvent(user, { + type: "comment_created", + title: `${event.repo.name}#${event.payload.issue.number}`, + time: eventTime.toISOString(), + link: event.payload.comment.html_url, + text: event.payload.comment.body, + }); + } + break; + case "IssuesEvent": + if (["opened", "assigned", "closed"].includes(event.payload.action)) { + appendEvent(user, { + type: `issue_${event.payload.action}`, + title: `${event.repo.name}#${event.payload.issue?.number}`, + time: eventTime.toISOString(), + link: event.payload.issue.html_url, + text: event.payload.issue.title, + }); + } + break; + case "PullRequestEvent": + if (event.payload.action === "opened") { + appendEvent(user, { + type: "pr_opened", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + }); + } else if ( + event.payload.action === "closed" && + event.payload.pull_request?.merged + ) { + const turnaroundTime = await calculateTurnaroundTime(event); + appendEvent(user, { + type: "pr_merged", + title: `${event.repo.name}#${event.payload.pull_request.number}`, + time: eventTime.toISOString(), + link: event.payload.pull_request.html_url, + text: event.payload.pull_request.title, + turnaround_time: turnaroundTime, + }); + await addCollaborations(event, eventTime); + } + break; + case "PullRequestReviewEvent": + appendEvent(user, { + type: "pr_reviewed", + time: eventTime.toISOString(), + title: `${event.repo.name}#${event.payload.pull_request.number}`, + link: event.payload.review.html_url, + text: event.payload.pull_request.title, + }); + break; + default: + break; + } + } + return processedData; +}; diff --git a/scraper/src/github-scraper/saveData.ts b/scraper/src/github-scraper/saveData.ts new file mode 100644 index 00000000..143608ec --- /dev/null +++ b/scraper/src/github-scraper/saveData.ts @@ -0,0 +1,33 @@ +import { ProcessData } from "./types.js"; +import { promises as fs } from "fs"; +import { loadUserData, saveUserData } from "./utils.js"; + +export const merged_data = async ( + dataDir: string, + processedData: ProcessData, +) => { + console.log("Updating data"); + await fs.mkdir(dataDir, { recursive: true }); + + for (let user in processedData) { + if (processedData.hasOwnProperty(user)) { + console.log(`Merging user data for ${user}`); + let oldData = await loadUserData(user, dataDir); + let userData = processedData[user]; + let newUniqueEvents = []; + + for (let event of userData.activity) { + if ( + !oldData.activity.some( + (oldEvent) => JSON.stringify(oldEvent) === JSON.stringify(event), + ) + ) { + newUniqueEvents.push(event); + } + } + + userData.activity = newUniqueEvents.concat(oldData.activity); + saveUserData(user, userData, dataDir, null); + } + } +}; diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts new file mode 100644 index 00000000..2f840874 --- /dev/null +++ b/scraper/src/github-scraper/types.ts @@ -0,0 +1,196 @@ +interface Actor { + id: number; + login: string; + gravatar_id: string; + url: string; + avatar_url: string; +} + +interface Reactions { + total_count: number; + "+1": number; + "-1": number; + laugh: number; + hooray: number; + confused: number; + heart: number; + rocket: number; + eyes: number; +} + +interface Comment { + html_url: string; + user: Actor; + body: string; + reactions: Reactions; + created_at: string; + updated_at: string; +} + +export interface PullRequest { + html_url: string; + user: Actor; + title: string; + body: string; + number: number; + labels: string[] | { name: string }[]; + comments: number; + review_comments: number; + commits: number; + additions: number; + deletions: number; + changed_files: number; + created_at: Date; + updated_at: string; + merged_at: Date; + issue_url: string; + merged?: string; + commits_url?: string; +} + +interface Review { + user: Actor; + body: string; + html_url: string; + state: string; +} + +interface Issue { + html_url: string; + user: Actor; + title: string; + body: string; + labels: string[]; + reactions: Reactions; + number: number; + created_at: string; + updated_at: string; +} + +interface Commit { + sha: string; + author: { + name: string; + email: string; + }; + url: string; + message: string; +} + +interface GitHubEvent { + id: string; + actor: Actor; + repo: { + id: number; + name: string; + url: string; + }; + public: boolean; + created_at: string; + org: Actor; +} + +interface PullRequestReviewEvent extends GitHubEvent { + type: "PullRequestReviewEvent"; + payload: { + action: string; + review: Review; + pull_request: PullRequest; + }; +} + +interface IssuesEvent extends GitHubEvent { + type: "IssuesEvent"; + payload: { + action: string; + issue: Issue; + }; +} + +interface IssueCommentEvent extends GitHubEvent { + type: "IssueCommentEvent"; + payload: { + action: string; + issue: Issue; + comment: Comment; + }; +} + +export interface PullRequestEvent extends GitHubEvent { + type: "PullRequestEvent"; + payload: { + action: string; + pull_request: PullRequest; + }; +} + +export type IGitHubEvent = + | PullRequestReviewEvent + | IssuesEvent + | IssueCommentEvent + | PullRequestEvent; + +export interface ActivityData { + last_updated?: string; + activity: Activity[]; + open_prs: OpenPr[]; + pr_stale?: number; + authored_issue_and_pr: AuthoredIssueAndPr[]; +} +export interface ProcessData { + [key: string]: ActivityData; +} + +export const ACTIVITY_TYPES = [ + "comment_created", + "issue_assigned", + "issue_closed", + "pr_reviewed", + "issue_opened", + "eod_update", + "pr_opened", + "pr_merged", + "pr_collaborated", +] as const; + +export interface Action { + event: string; + source: { + type: string; + issue: { + pull_request: boolean; + repository: { + full_name: string; + }; + user: { + login: string; + }; + number: number; + }; + }; + assignee: { + login: string; + }; + created_at: Date; +} +export interface Activity { + type: (typeof ACTIVITY_TYPES)[number] | string; + title: string; + time: string; + link: string; + text: string; + collaborated_with?: string[]; + turnaround_time?: number; +} + +export interface OpenPr { + link: string; + title: string; + stale_for: number; + labels: string[]; +} + +export interface AuthoredIssueAndPr { + issue_link: string; + pr_link: string; +} diff --git a/scraper/src/github-scraper/utils.ts b/scraper/src/github-scraper/utils.ts new file mode 100644 index 00000000..f82a633a --- /dev/null +++ b/scraper/src/github-scraper/utils.ts @@ -0,0 +1,143 @@ +import path from "path"; +import { octokit } from "./config.js"; +import { Action, ActivityData, PullRequestEvent } from "./types.js"; +import { promises as fs } from "fs"; + + +export const parseISODate = (isoDate: Date) => { + return new Date(isoDate); +}; + +const userBlacklist = new Set(["dependabot", "snyk-bot", "codecov-commenter"]); + +export const isBlacklisted = (login: string): boolean => { + return login.includes("[bot]") || userBlacklist.has(login); +}; + +export async function calculateTurnaroundTime(event: PullRequestEvent) { + const user: string = event.payload.pull_request.user.login; + const mergedAt: Date = parseISODate(event.payload.pull_request.merged_at); + const createdAt: Date = parseISODate(event.payload.pull_request.created_at); + + const linkedIssues: [string, string][] = []; + const body = event.payload.pull_request.body || ""; + const regex = + /(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved) ([\w\/.-]*)(#\d+)/gi; + let match: RegExpExecArray | null; + + while ((match = regex.exec(body)) !== null) { + linkedIssues.push([match[2], match[3]]); + } + + const prTimelineResponse = await octokit.request( + `GET ${event.payload?.pull_request?.issue_url}/timeline`, + ); + + const prTimeline = prTimelineResponse.data; + + prTimeline.forEach((action: Action) => { + if ( + action.event === "cross-referenced" && + action.source.type === "issue" && + !action.source.issue.pull_request + ) { + linkedIssues.push([ + action.source.issue.repository.full_name, + `#${action.source.issue.number}`, + ]); + } + + if (action.event === "connected") { + // TODO: currently there is no way to get the issue number from the timeline, handle this case while moving to graphql + } + }); + const uniqueLinkedIssues: [string, string][] = Array.from( + new Set(linkedIssues.map((issue) => JSON.stringify(issue))), + ).map((item) => JSON.parse(item) as [string, string]); + const assignedAts: { issue: string; time: Date }[] = []; + + for (const [org_repo, issue] of uniqueLinkedIssues) { + const org = org_repo.split("/")[0] || event.repo.name.split("/")[0]; + const repo = org_repo.split("/")[-1] || event.repo.name.split("/")[1]; + const issueNumber = parseInt(issue.split("#")[1]); + + const issueTimelineResponse = await octokit.request( + "GET /repos/{owner}/{repo}/issues/{issue_number}/timeline", + { + owner: org, + repo: repo, + issue_number: issueNumber, + }, + ); + + const issueTimeline = issueTimelineResponse.data; + issueTimeline.forEach((action: Action) => { + if (action.event === "assigned" && action.assignee.login === user) { + assignedAts.push({ + issue: `${org}/${repo}#${issueNumber}`, + time: parseISODate(action.created_at), + }); + } + + if (action.event === "unassigned" && action.assignee.login === user) { + assignedAts.pop(); + } + }); + } + + const assignedAt: Date | null = + assignedAts.length === 0 + ? null + : assignedAts.reduce((min, current) => + current.time < min.time ? current : min, + ).time; + const turnaroundTime = + (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; + return turnaroundTime; +} + +export async function resolve_autonomy_responsibility( + event: Action, + user: string, +) { + if (event.event === "cross-referenced" && event.source.type === "issue") { + return event.source.issue.user.login === user; + } + return false; +} + +export async function loadUserData(user: string, dataDir: string) { + const file = path.join(dataDir, `${user}.json`); + console.log(`Loading user data from ${file}`); + + try { + const response = await fs.readFile(file); + const data: ActivityData = JSON.parse(response.toString()); + return data; + } catch (error: any) { + if (error.code === "ENOENT" || error.name === "SyntaxError") { + console.log(`User data not found for ${user}`); + return { activity: [] }; + } else { + throw error; // rethrow unexpected errors + } + } +} + +export async function saveUserData( + user: string, + data: ActivityData, + dataDir: string, + serializer: any, +) { + const file = path.join(dataDir, `${user}.json`); + console.log(`Saving user data to ${file}`); + + try { + const jsonData = JSON.stringify(data, serializer, 2); + await fs.writeFile(file, jsonData); + } catch (error: any) { + console.error(`Failed to save user data for ${user}: ${error.message}`); + throw error; + } +} diff --git a/scraper/tsconfig.json b/scraper/tsconfig.json index 19f6dcf5..0ba8a73f 100644 --- a/scraper/tsconfig.json +++ b/scraper/tsconfig.json @@ -1,25 +1,14 @@ { "compilerOptions": { - "target": "es6", // Set the target ECMAScript version - "module": "commonjs", // Specify module code generation - "moduleResolution": "Node", - "strict": true, // Enable all strict type-checking options - "esModuleInterop": true, // Enable interoperability between CommonJS and ES Modules - "forceConsistentCasingInFileNames": true, // Ensure consistent casing in file names - "moduleResolution": "node", // Specify module resolution strategy - "outDir": "./dist", // Specify output directory - "sourceMap": true // Generate source maps for easier debugging + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true }, - "include": [ - "src/**/*.ts" // Include TypeScript files in the 'src' directory - ], - "exclude": [ - "node_modules" // Exclude the 'node_modules' directory - ], - "ts-node": { - "compilerOptions": { - "module": "commonjs", - "moduleResolution": "Node" - } - } + "include": ["src/github-scraper/**/*"], + "exclude": ["node_modules"] } From 6cc5dac5288b85deaaf516760ba5f194e9352f70 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Fri, 14 Jun 2024 08:09:16 +0530 Subject: [PATCH 06/71] fix scraper setup --- .gitignore | 1 - package.json | 4 +- scraper/.gitignore | 2 +- scraper/package-lock.json | 802 -------------------------------------- scraper/package.json | 11 +- scraper/pnpm-lock.yaml | 502 ++++++++++++++++++++++++ 6 files changed, 511 insertions(+), 811 deletions(-) delete mode 100644 scraper/package-lock.json create mode 100644 scraper/pnpm-lock.yaml diff --git a/.gitignore b/.gitignore index 24103bb9..f6633c5f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ .pnp.js bun.lockb package-lock.json -pnpm-lock.yaml # testing /coverage diff --git a/package.json b/package.json index 2f8b086f..2c2c859c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "lint-fix": "eslint . --fix", "format": "prettier --write .", "load-data": "node ./scripts/loadOrgData.js", - "prepare": "husky install", + "prepare": "husky install" }, "dependencies": { "@headlessui/react": "^1.7.18", @@ -57,4 +57,4 @@ "ts-node": "^10.9.2", "typescript": "^5.4.5" } -} +} \ No newline at end of file diff --git a/scraper/.gitignore b/scraper/.gitignore index b2af6e00..04c01ba7 100644 --- a/scraper/.gitignore +++ b/scraper/.gitignore @@ -1,2 +1,2 @@ node_modules/ -!package-lock.json \ No newline at end of file +dist/ \ No newline at end of file diff --git a/scraper/package-lock.json b/scraper/package-lock.json deleted file mode 100644 index d78b5ac1..00000000 --- a/scraper/package-lock.json +++ /dev/null @@ -1,802 +0,0 @@ -{ - "name": "scraper", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "scraper", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@octokit/core": "^6.1.2", - "@octokit/types": "^13.5.0", - "date-fns": "^3.6.0", - "octokit": "^4.0.2", - "yargs": "^17.7.2" - }, - "devDependencies": { - "@octokit/core": "^6.1.2", - "@types/node": "^16.11.18", - "ts-node": "^10.9.2", - "typescript": "^4.9.5" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@octokit/app": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.0.1.tgz", - "integrity": "sha512-nwSjC349E6/wruMCo944y1dBC7uKzUYrBMoC4Qx/xfLLBmD+R66oMKB1jXS2HYRF9Hqh/Alq3UNRggVWZxjvUg==", - "dependencies": { - "@octokit/auth-app": "^7.0.0", - "@octokit/auth-unauthenticated": "^6.0.0", - "@octokit/core": "^6.1.2", - "@octokit/oauth-app": "^7.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/types": "^13.0.0", - "@octokit/webhooks": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-app": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.0.tgz", - "integrity": "sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==", - "dependencies": { - "@octokit/auth-oauth-app": "^8.1.0", - "@octokit/auth-oauth-user": "^5.1.0", - "@octokit/request": "^9.1.1", - "@octokit/request-error": "^6.1.1", - "@octokit/types": "^13.4.1", - "lru-cache": "^10.0.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-app": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz", - "integrity": "sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==", - "dependencies": { - "@octokit/auth-oauth-device": "^7.0.0", - "@octokit/auth-oauth-user": "^5.0.1", - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-device": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz", - "integrity": "sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==", - "dependencies": { - "@octokit/oauth-methods": "^5.0.0", - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-user": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz", - "integrity": "sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==", - "dependencies": { - "@octokit/auth-oauth-device": "^7.0.1", - "@octokit/oauth-methods": "^5.0.0", - "@octokit/request": "^9.0.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-token": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", - "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-unauthenticated": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz", - "integrity": "sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==", - "dependencies": { - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", - "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.0.0", - "@octokit/request": "^9.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", - "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", - "dependencies": { - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", - "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", - "dependencies": { - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-app": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.2.tgz", - "integrity": "sha512-4ntCOZIiTozKwuYQroX/ZD722tzMH8Eicv/cgDM/3F3lyrlwENHDv4flTCBpSJbfK546B2SrkKMWB+/HbS84zQ==", - "dependencies": { - "@octokit/auth-oauth-app": "^8.0.0", - "@octokit/auth-oauth-user": "^5.0.1", - "@octokit/auth-unauthenticated": "^6.0.0-beta.1", - "@octokit/core": "^6.0.0", - "@octokit/oauth-authorization-url": "^7.0.0", - "@octokit/oauth-methods": "^5.0.0", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", - "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-methods": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz", - "integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==", - "dependencies": { - "@octokit/oauth-authorization-url": "^7.0.0", - "@octokit/request": "^9.1.0", - "@octokit/request-error": "^6.1.0", - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" - }, - "node_modules/@octokit/openapi-webhooks-types": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.2.1.tgz", - "integrity": "sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==" - }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.2.tgz", - "integrity": "sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.0.tgz", - "integrity": "sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==", - "dependencies": { - "@octokit/types": "^13.5.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.1.tgz", - "integrity": "sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==", - "dependencies": { - "@octokit/types": "^13.5.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-retry": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.1.tgz", - "integrity": "sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==", - "dependencies": { - "@octokit/request-error": "^6.0.0", - "@octokit/types": "^13.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-throttling": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.3.0.tgz", - "integrity": "sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==", - "dependencies": { - "@octokit/types": "^13.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^6.0.0" - } - }, - "node_modules/@octokit/request": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.1.tgz", - "integrity": "sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==", - "dependencies": { - "@octokit/endpoint": "^10.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.1.tgz", - "integrity": "sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==", - "dependencies": { - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", - "dependencies": { - "@octokit/openapi-types": "^22.2.0" - } - }, - "node_modules/@octokit/webhooks": { - "version": "13.2.7", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.2.7.tgz", - "integrity": "sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==", - "dependencies": { - "@octokit/openapi-webhooks-types": "8.2.1", - "@octokit/request-error": "^6.0.1", - "@octokit/webhooks-methods": "^5.0.0", - "aggregate-error": "^5.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/webhooks-methods": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz", - "integrity": "sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "node_modules/@types/aws-lambda": { - "version": "8.10.138", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.138.tgz", - "integrity": "sha512-71EHMl70TPWIAsFuHd85NHq6S6T2OOjiisPTrH7RgcjzpJpPh4RQJv7PvVvIxc6PIp8CLV7F9B+TdjcAES5vcA==" - }, - "node_modules/@types/node": { - "version": "16.18.98", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.98.tgz", - "integrity": "sha512-fpiC20NvLpTLAzo3oVBKIqBGR6Fx/8oAK/SSf7G+fydnXMY1x4x9RZ6sBXhqKlCU21g2QapUsbLlhv3+a7wS+Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aggregate-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", - "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" - }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" - }, - "node_modules/clean-stack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/octokit": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-4.0.2.tgz", - "integrity": "sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==", - "dependencies": { - "@octokit/app": "^15.0.0", - "@octokit/core": "^6.0.0", - "@octokit/oauth-app": "^7.0.0", - "@octokit/plugin-paginate-graphql": "^5.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/plugin-rest-endpoint-methods": "^13.0.0", - "@octokit/plugin-retry": "^7.0.0", - "@octokit/plugin-throttling": "^9.0.0", - "@octokit/request-error": "^6.0.0", - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/universal-github-app-jwt": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz", - "integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==" - }, - "node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - } - } -} diff --git a/scraper/package.json b/scraper/package.json index 8e91911d..f7404751 100644 --- a/scraper/package.json +++ b/scraper/package.json @@ -2,23 +2,24 @@ "name": "scraper", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "dist/index.js", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "scraper": "node --loader ts-node/esm src/github-scraper/index.ts coronasafe ../../data-repo/github" + "build": "tsc", + "start": "node dist/index.js", + "dev": "pnpm build && pnpm start" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "date-fns": "^3.6.0", - "octokit": "^4.0.2", - "yargs": "^17.7.2" + "octokit": "^4.0.2" }, "devDependencies": { "@types/node": "^16.11.18", "ts-node": "^10.9.2", "typescript": "^4.9.5" } -} +} \ No newline at end of file diff --git a/scraper/pnpm-lock.yaml b/scraper/pnpm-lock.yaml new file mode 100644 index 00000000..88e21af6 --- /dev/null +++ b/scraper/pnpm-lock.yaml @@ -0,0 +1,502 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + date-fns: + specifier: ^3.6.0 + version: 3.6.0 + octokit: + specifier: ^4.0.2 + version: 4.0.2 + devDependencies: + '@types/node': + specifier: ^16.11.18 + version: 16.18.98 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@16.18.98)(typescript@4.9.5) + typescript: + specifier: ^4.9.5 + version: 4.9.5 + +packages: + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@octokit/app@15.1.0': + resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} + engines: {node: '>= 18'} + + '@octokit/auth-app@7.1.0': + resolution: {integrity: sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-app@8.1.1': + resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-device@7.1.1': + resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-user@5.1.1': + resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==} + engines: {node: '>= 18'} + + '@octokit/auth-token@5.1.1': + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} + + '@octokit/auth-unauthenticated@6.1.0': + resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.2': + resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.1': + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.1.1': + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} + + '@octokit/oauth-app@7.1.2': + resolution: {integrity: sha512-4ntCOZIiTozKwuYQroX/ZD722tzMH8Eicv/cgDM/3F3lyrlwENHDv4flTCBpSJbfK546B2SrkKMWB+/HbS84zQ==} + engines: {node: '>= 18'} + + '@octokit/oauth-authorization-url@7.1.1': + resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} + engines: {node: '>= 18'} + + '@octokit/oauth-methods@5.1.2': + resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@22.2.0': + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + + '@octokit/openapi-webhooks-types@8.2.1': + resolution: {integrity: sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==} + + '@octokit/plugin-paginate-graphql@5.2.2': + resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-paginate-rest@11.3.0': + resolution: {integrity: sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@13.2.1': + resolution: {integrity: sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@7.1.1': + resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-throttling@9.3.0': + resolution: {integrity: sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^6.0.0 + + '@octokit/request-error@6.1.1': + resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==} + engines: {node: '>= 18'} + + '@octokit/request@9.1.1': + resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==} + engines: {node: '>= 18'} + + '@octokit/types@13.5.0': + resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + + '@octokit/webhooks-methods@5.1.0': + resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==} + engines: {node: '>= 18'} + + '@octokit/webhooks@13.2.7': + resolution: {integrity: sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==} + engines: {node: '>= 18'} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/aws-lambda@8.10.138': + resolution: {integrity: sha512-71EHMl70TPWIAsFuHd85NHq6S6T2OOjiisPTrH7RgcjzpJpPh4RQJv7PvVvIxc6PIp8CLV7F9B+TdjcAES5vcA==} + + '@types/node@16.18.98': + resolution: {integrity: sha512-fpiC20NvLpTLAzo3oVBKIqBGR6Fx/8oAK/SSf7G+fydnXMY1x4x9RZ6sBXhqKlCU21g2QapUsbLlhv3+a7wS+Q==} + + acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + aggregate-error@5.0.0: + resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} + engines: {node: '>=18'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + + clean-stack@5.2.0: + resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} + engines: {node: '>=14.16'} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + octokit@4.0.2: + resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==} + engines: {node: '>= 18'} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + universal-github-app-jwt@2.2.0: + resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} + + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + +snapshots: + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@octokit/app@15.1.0': + dependencies: + '@octokit/auth-app': 7.1.0 + '@octokit/auth-unauthenticated': 6.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-app': 7.1.2 + '@octokit/plugin-paginate-rest': 11.3.0(@octokit/core@6.1.2) + '@octokit/types': 13.5.0 + '@octokit/webhooks': 13.2.7 + + '@octokit/auth-app@7.1.0': + dependencies: + '@octokit/auth-oauth-app': 8.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/request': 9.1.1 + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + lru-cache: 10.2.2 + universal-github-app-jwt: 2.2.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-app@8.1.1': + dependencies: + '@octokit/auth-oauth-device': 7.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/request': 9.1.1 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-device@7.1.1': + dependencies: + '@octokit/oauth-methods': 5.1.2 + '@octokit/request': 9.1.1 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-user@5.1.1': + dependencies: + '@octokit/auth-oauth-device': 7.1.1 + '@octokit/oauth-methods': 5.1.2 + '@octokit/request': 9.1.1 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-token@5.1.1': {} + + '@octokit/auth-unauthenticated@6.1.0': + dependencies: + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + + '@octokit/core@6.1.2': + dependencies: + '@octokit/auth-token': 5.1.1 + '@octokit/graphql': 8.1.1 + '@octokit/request': 9.1.1 + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.2 + + '@octokit/endpoint@10.1.1': + dependencies: + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/graphql@8.1.1': + dependencies: + '@octokit/request': 9.1.1 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/oauth-app@7.1.2': + dependencies: + '@octokit/auth-oauth-app': 8.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/auth-unauthenticated': 6.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-authorization-url': 7.1.1 + '@octokit/oauth-methods': 5.1.2 + '@types/aws-lambda': 8.10.138 + universal-user-agent: 7.0.2 + + '@octokit/oauth-authorization-url@7.1.1': {} + + '@octokit/oauth-methods@5.1.2': + dependencies: + '@octokit/oauth-authorization-url': 7.1.1 + '@octokit/request': 9.1.1 + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + + '@octokit/openapi-types@22.2.0': {} + + '@octokit/openapi-webhooks-types@8.2.1': {} + + '@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + + '@octokit/plugin-paginate-rest@11.3.0(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 + + '@octokit/plugin-rest-endpoint-methods@13.2.1(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 + + '@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + bottleneck: 2.19.5 + + '@octokit/plugin-throttling@9.3.0(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 + bottleneck: 2.19.5 + + '@octokit/request-error@6.1.1': + dependencies: + '@octokit/types': 13.5.0 + + '@octokit/request@9.1.1': + dependencies: + '@octokit/endpoint': 10.1.1 + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/types@13.5.0': + dependencies: + '@octokit/openapi-types': 22.2.0 + + '@octokit/webhooks-methods@5.1.0': {} + + '@octokit/webhooks@13.2.7': + dependencies: + '@octokit/openapi-webhooks-types': 8.2.1 + '@octokit/request-error': 6.1.1 + '@octokit/webhooks-methods': 5.1.0 + aggregate-error: 5.0.0 + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/aws-lambda@8.10.138': {} + + '@types/node@16.18.98': {} + + acorn-walk@8.3.2: {} + + acorn@8.11.3: {} + + aggregate-error@5.0.0: + dependencies: + clean-stack: 5.2.0 + indent-string: 5.0.0 + + arg@4.1.3: {} + + before-after-hook@3.0.2: {} + + bottleneck@2.19.5: {} + + clean-stack@5.2.0: + dependencies: + escape-string-regexp: 5.0.0 + + create-require@1.1.1: {} + + date-fns@3.6.0: {} + + diff@4.0.2: {} + + escape-string-regexp@5.0.0: {} + + indent-string@5.0.0: {} + + lru-cache@10.2.2: {} + + make-error@1.3.6: {} + + octokit@4.0.2: + dependencies: + '@octokit/app': 15.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-app': 7.1.2 + '@octokit/plugin-paginate-graphql': 5.2.2(@octokit/core@6.1.2) + '@octokit/plugin-paginate-rest': 11.3.0(@octokit/core@6.1.2) + '@octokit/plugin-rest-endpoint-methods': 13.2.1(@octokit/core@6.1.2) + '@octokit/plugin-retry': 7.1.1(@octokit/core@6.1.2) + '@octokit/plugin-throttling': 9.3.0(@octokit/core@6.1.2) + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + + ts-node@10.9.2(@types/node@16.18.98)(typescript@4.9.5): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 16.18.98 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + typescript@4.9.5: {} + + universal-github-app-jwt@2.2.0: {} + + universal-user-agent@7.0.2: {} + + v8-compile-cache-lib@3.0.1: {} + + yn@3.1.1: {} From a1491b980462a4e63bf12b6a01b31022b9a84159 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Tue, 18 Jun 2024 19:27:01 +0530 Subject: [PATCH 07/71] Integrate Github Discussion in scraper and update scraper-dry-run workflow --- .github/workflows/scraper-dry-run.yaml | 25 +++- scraper/src/github-scraper/discussion.ts | 132 ++++++++++++++++++++++ scraper/src/github-scraper/index.ts | 20 +++- scraper/src/github-scraper/parseEvents.ts | 7 +- scraper/src/github-scraper/types.ts | 44 ++++++++ scraper/src/github-scraper/utils.ts | 5 +- scraper/tsconfig.json | 2 +- tsconfig.json | 2 +- 8 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 scraper/src/github-scraper/discussion.ts diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index f7b2e529..4f1ebe25 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -15,24 +15,39 @@ jobs: steps: - uses: actions/checkout@v4 - - name: setup python - uses: actions/setup-python@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 with: - python-version: "3.10" + node-version: "20" + + - name: Install pnpm + run: npm install -g pnpm - name: Install dependencies - run: pip install -r scraper/requirements.txt + run: pnpm install --frozen-lockfile + working-directory: scraper + + - name: Build the project + run: pnpm build + working-directory: scraper - name: Scrape data from GitHub - run: python scraper/src/github.py ${{ github.repository_owner }} data/github -l DEBUG + run: pnpm start -- ${{ github.repository_owner }} data/github + working-directory: scraper env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Verify scraper output + run: ls -l data/github + - name: Generate markdown files for new contributors run: node scripts/generateNewContributors.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Verify generated markdown files + run: ls -l contributors + - uses: actions/upload-artifact@v4 with: name: output diff --git a/scraper/src/github-scraper/discussion.ts b/scraper/src/github-scraper/discussion.ts new file mode 100644 index 00000000..ddf81357 --- /dev/null +++ b/scraper/src/github-scraper/discussion.ts @@ -0,0 +1,132 @@ +import { octokit } from "./config.js"; +import { Discussion, Edge } from "./types.js"; + +// Query to fetch discussions from GitHub +const query = ` +query($org: String!, $cursor: String) { + organization(login: $org) { + repositories(first: 100, after: $cursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + node { + name + discussions(first: 100) { + edges { + node { + id + title + author { + login + avatarUrl + } + url + category{ + id + name + emoji + } + upvoteCount + reactions { + totalCount + } + comments(first: 10) { + edges { + node { + author { + login + avatarUrl + } + upvoteCount + isAnswer + } + } + } + createdAt + isAnswered + } + } + } + } + } + } + } +} +`; +async function fetchDiscussionsForOrg(org: string, cursor = null) { + const variables = { org, cursor }; + const response = await octokit.graphql(query, variables); + const discussions = response.organization.repositories.edges.map( + (edge: Edge) => edge.node.discussions.edges, + ); + const hasNextPage = response.organization.repositories.pageInfo.hasNextPage; + const nextCursor = response.organization.repositories.pageInfo.endCursor; + + const allDiscussions = discussions.flat(); + + if (hasNextPage) { + const nextDiscussions: Discussion[] = await fetchDiscussionsForOrg( + org, + nextCursor, + ); + return allDiscussions.concat(nextDiscussions); + } + + return allDiscussions; +} + +async function parseDiscussionData(allDiscussions: Discussion[]) { + const authorList = allDiscussions + .map((d: Discussion) => + d.node.comments.edges.map((c) => c.node.author.login), + ) + .flat(); + authorList.push(...allDiscussions.map((d) => d.node.author.login)); + const uniqueAuthors = [...new Set(authorList)]; + const authorDiscussionList = uniqueAuthors.map((author) => { + const discussions = allDiscussions.filter( + (d) => + d.node.author.login === author || + d.node.comments.edges.some((c) => c.node.author.login === author), + ); + const data = discussions.map((d) => { + return { + id: d.node.id, + title: d.node.title, + url: d.node.url, + createdAt: d.node.createdAt, + author: d.node.author, + category: d.node.category, + isAnswered: d.node.isAnswered, + upvoteCount: d.node.upvoteCount, + participants: [ + ...new Map( + d.node.comments.edges.map((c) => [ + c.node.author.login, + { + login: c.node.author.login, + avatarUrl: c.node.author.avatarUrl, + isAnswer: c.node.isAnswer, + upvoteCount: c.node.upvoteCount, + }, + ]), + ).values(), + ], + }; + }); + return { user: author, discussions: data }; + }); + return authorDiscussionList; +} + +export async function fetchAllDiscussionEventsByOrg(organizationName: string) { + try { + const allDiscussions = await fetchDiscussionsForOrg(organizationName); + const parseDiscussion = await parseDiscussionData(allDiscussions); + return parseDiscussion; + } catch (error: any) { + throw new Error(`Error fetching discussions: ${error.message}`); + } +} diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index aeca36c4..31f78a1f 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -4,6 +4,7 @@ import { fetch_merge_events, fetchOpenPulls } from "./fetchUserData.js"; import { fetchEvents } from "./fetchEvents.js"; import { parseEvents } from "./parseEvents.js"; import { merged_data } from "./saveData.js"; +import { fetchAllDiscussionEventsByOrg } from "./discussion.js"; let processedData: ProcessData = {}; @@ -11,6 +12,7 @@ const scrapeGitHub = async ( org: string, date: string, numDays: number = 1, + orgName: string, ): Promise => { const endDate: Date = startOfDay(parseISO(date)); const startDate: Date = startOfDay(subDays(endDate, numDays)); @@ -24,7 +26,6 @@ const scrapeGitHub = async ( endDate, )) as IGitHubEvent[]; processedData = await parseEvents(events); - for (const user of Object.keys(processedData)) { if (!processedData[user]) { processedData[user] = { @@ -32,6 +33,7 @@ const scrapeGitHub = async ( last_updated: "", activity: [], open_prs: [], + discussions: [], }; } try { @@ -51,6 +53,20 @@ const scrapeGitHub = async ( console.error(`Error fetching open pulls for ${user}: ${e}`); } } + const discussions = await fetchAllDiscussionEventsByOrg(orgName); + console.log("Scraping discussions"); + discussions.forEach((d) => { + if (!processedData[d.user]) { + processedData[d.user] = { + authored_issue_and_pr: [], + last_updated: "", + activity: [], + open_prs: [], + discussions: [], + }; + } + processedData[d.user].discussions = d.discussions; + }); console.log("Scraping completed"); }; @@ -73,7 +89,7 @@ const main = async () => { process.exit(1); } - await scrapeGitHub(orgName, date, Number(numDays)); + await scrapeGitHub(orgName, date, Number(numDays), orgName); await merged_data(dataDir, processedData); console.log("Done"); }; diff --git a/scraper/src/github-scraper/parseEvents.ts b/scraper/src/github-scraper/parseEvents.ts index 690c8453..2207b27f 100644 --- a/scraper/src/github-scraper/parseEvents.ts +++ b/scraper/src/github-scraper/parseEvents.ts @@ -20,6 +20,7 @@ function appendEvent(user: string, event: Activity) { activity: [event], open_prs: [], authored_issue_and_pr: [], + discussions: [], }; } else { processedData[user]["activity"].push(event); @@ -123,7 +124,11 @@ export const parseEvents = async (events: IGitHubEvent[]) => { const user = event.actor.login; if (isBlacklisted(user)) continue; - console.log("Processing event for user:", user + " | " + "event_id : ", event.id); + console.log( + "Processing event for user:", + user + " | " + "event_id : ", + event.id, + ); switch (event.type) { case "IssueCommentEvent": diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index 2f840874..cc05f493 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -136,6 +136,7 @@ export interface ActivityData { open_prs: OpenPr[]; pr_stale?: number; authored_issue_and_pr: AuthoredIssueAndPr[]; + discussions?: any; } export interface ProcessData { [key: string]: ActivityData; @@ -194,3 +195,46 @@ export interface AuthoredIssueAndPr { issue_link: string; pr_link: string; } + +export type Discussion = { + node: { + id: string; + title: string; + author: { + login: string; + avatarUrl: string; + }; + url: string; + category: { + id: string; + name: string; + emoji: string; + }; + upvoteCount: number; + reactions: { + totalCount: number; + }; + comments: { + edges: { + node: { + author: { + login: string; + avatarUrl: string; + }; + upvoteCount: number; + isAnswer: boolean; + }; + }[]; + }; + createdAt: string; + isAnswered: boolean; + }; +}; + +export type Edge = { + node: { + discussions: { + edges: Discussion[]; + }; + }; +}; diff --git a/scraper/src/github-scraper/utils.ts b/scraper/src/github-scraper/utils.ts index f82a633a..dee339af 100644 --- a/scraper/src/github-scraper/utils.ts +++ b/scraper/src/github-scraper/utils.ts @@ -3,7 +3,6 @@ import { octokit } from "./config.js"; import { Action, ActivityData, PullRequestEvent } from "./types.js"; import { promises as fs } from "fs"; - export const parseISODate = (isoDate: Date) => { return new Date(isoDate); }; @@ -89,8 +88,8 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { assignedAts.length === 0 ? null : assignedAts.reduce((min, current) => - current.time < min.time ? current : min, - ).time; + current.time < min.time ? current : min, + ).time; const turnaroundTime = (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; return turnaroundTime; diff --git a/scraper/tsconfig.json b/scraper/tsconfig.json index 0ba8a73f..332ad1b0 100644 --- a/scraper/tsconfig.json +++ b/scraper/tsconfig.json @@ -9,6 +9,6 @@ "strict": true, "skipLibCheck": true }, - "include": ["src/github-scraper/**/*"], + "include": ["src/github-scraper/**/*", "dist/discssion.js"], "exclude": ["node_modules"] } diff --git a/tsconfig.json b/tsconfig.json index 80ddaa01..fe9cd07c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,6 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "scraper/dist/discssion.js"], "exclude": ["node_modules", "data-repo", "data"] } From 7583a6217e078d4543f981f259e849778df52ffb Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Tue, 18 Jun 2024 19:54:44 +0530 Subject: [PATCH 08/71] Removing dry-run work flow error (date-fns) --- .github/workflows/scraper-dry-run.yaml | 2 +- scraper/src/github-scraper/index.ts | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 4f1ebe25..331f3497 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -18,7 +18,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: "20" + node-version: "20.14.0" - name: Install pnpm run: npm install -g pnpm diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index 31f78a1f..ac0a3009 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -77,18 +77,25 @@ const main = async () => { const args: string[] = process.argv.slice(2); // Destructure arguments with default values - const [ - orgName, - dataDir, - date = formatISO(subDays(new Date(), 1), { representation: "date" }), - numDays = 1, - ] = args; + const [orgName, dataDir, dateArg = "", numDays = 1] = args; if (!orgName || !dataDir) { console.error("Usage: node script.js [date] [numDays]"); process.exit(1); } + let date: string; + try { + if (dateArg) { + date = new Date(dateArg).toISOString(); + } else { + date = formatISO(subDays(new Date(), 1), { representation: "date" }); + } + } catch (error) { + console.error("Invalid date value:", dateArg); + process.exit(1); + } + await scrapeGitHub(orgName, date, Number(numDays), orgName); await merged_data(dataDir, processedData); console.log("Done"); From 1d51cd180bb3ac826ac4683448465d0ae54036f2 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Mon, 24 Jun 2024 10:16:50 +0530 Subject: [PATCH 09/71] Fixing scraper-dry-run date error by puting null as a default value for date --- scraper/src/github-scraper/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index ac0a3009..f16baa8d 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -77,7 +77,7 @@ const main = async () => { const args: string[] = process.argv.slice(2); // Destructure arguments with default values - const [orgName, dataDir, dateArg = "", numDays = 1] = args; + const [orgName, dataDir, dateArg = null, numDays = 1] = args; if (!orgName || !dataDir) { console.error("Usage: node script.js [date] [numDays]"); @@ -95,7 +95,6 @@ const main = async () => { console.error("Invalid date value:", dateArg); process.exit(1); } - await scrapeGitHub(orgName, date, Number(numDays), orgName); await merged_data(dataDir, processedData); console.log("Done"); From 0e5fde7ab028751ab763e1880bfb747f69e955ce Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Mon, 24 Jun 2024 10:31:36 +0530 Subject: [PATCH 10/71] Fixing scraper-dry-run failing --- .github/workflows/scraper-dry-run.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 331f3497..2c07515a 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -32,7 +32,7 @@ jobs: working-directory: scraper - name: Scrape data from GitHub - run: pnpm start -- ${{ github.repository_owner }} data/github + run: pnpm start ${{ github.repository_owner }} data/github working-directory: scraper env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 8596095c1059a9556eb55e260ffc437410e9d643 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Mon, 24 Jun 2024 10:54:49 +0530 Subject: [PATCH 11/71] Fixing scraper-dry-run failing --- .github/workflows/scraper-dry-run.yaml | 6 +++--- scripts/generateNewContributors.js | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 2c07515a..60e8f24a 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -38,15 +38,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Verify scraper output - run: ls -l data/github + run: ls -l ../data-repo/data/github - name: Generate markdown files for new contributors - run: node scripts/generateNewContributors.js + run: node ../scripts/generateNewContributors.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Verify generated markdown files - run: ls -l contributors + run: ls -l ../data-repo/contributors - uses: actions/upload-artifact@v4 with: diff --git a/scripts/generateNewContributors.js b/scripts/generateNewContributors.js index 2fb71ec1..8b1633d4 100644 --- a/scripts/generateNewContributors.js +++ b/scripts/generateNewContributors.js @@ -18,17 +18,20 @@ Still waiting for this } const basePath = join(process.env.DATA_REPO || process.cwd()); +console.log("Base Path", process.cwd()); function getNewContributors() { let newContributors = new Set(); - fs.readdirSync(join(basePath, "data/github")).forEach((file) => { + fs.readdirSync(join(basePath, "../data-repo/data/github")).forEach((file) => { newContributors.add(file.split(".")[0]); }); - fs.readdirSync(join(basePath, "contributors")).forEach((file) => { - newContributors.delete(file.split(".")[0]); - }); + fs.readdirSync(join(basePath, "../data-repo/contributors")).forEach( + (file) => { + newContributors.delete(file.split(".")[0]); + }, + ); return newContributors; } From 30b092a4488ccff4fc4a7a550f2bdd9400fb9338 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Mon, 24 Jun 2024 11:01:06 +0530 Subject: [PATCH 12/71] Fixing scraper-dry-run failing --- .github/workflows/scraper-dry-run.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 60e8f24a..1fd62f9a 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -37,17 +37,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Verify scraper output - run: ls -l ../data-repo/data/github - - name: Generate markdown files for new contributors run: node ../scripts/generateNewContributors.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Verify generated markdown files - run: ls -l ../data-repo/contributors - + - uses: actions/upload-artifact@v4 with: name: output From 8a3de030d1fcb83c0e594b7e46cb15183495827f Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Mon, 24 Jun 2024 11:25:38 +0530 Subject: [PATCH 13/71] Fixing scraper-dry-run failing (Genrate markdown files) --- .github/workflows/scraper-dry-run.yaml | 2 +- scraper/src/github-scraper/fetchEvents.ts | 1 - scripts/generateNewContributors.js | 12 ++++-------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 1fd62f9a..9d49b48d 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -38,7 +38,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate markdown files for new contributors - run: node ../scripts/generateNewContributors.js + run: node scripts/generateNewContributors.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scraper/src/github-scraper/fetchEvents.ts b/scraper/src/github-scraper/fetchEvents.ts index 6e49d269..b9431ec7 100644 --- a/scraper/src/github-scraper/fetchEvents.ts +++ b/scraper/src/github-scraper/fetchEvents.ts @@ -41,7 +41,6 @@ export const fetchEvents = async ( ].includes(event.type ?? ""); if (!isBlacklisted && isRequiredEventType) { - console.log(event.type); filteredEvents.push(event); } eventsCount++; diff --git a/scripts/generateNewContributors.js b/scripts/generateNewContributors.js index 8b1633d4..2f31597a 100644 --- a/scripts/generateNewContributors.js +++ b/scripts/generateNewContributors.js @@ -18,20 +18,17 @@ Still waiting for this } const basePath = join(process.env.DATA_REPO || process.cwd()); -console.log("Base Path", process.cwd()); function getNewContributors() { let newContributors = new Set(); - fs.readdirSync(join(basePath, "../data-repo/data/github")).forEach((file) => { + fs.readdirSync(join(basePath, "data-repo/data/github")).forEach((file) => { newContributors.add(file.split(".")[0]); }); - fs.readdirSync(join(basePath, "../data-repo/contributors")).forEach( - (file) => { - newContributors.delete(file.split(".")[0]); - }, - ); + fs.readdirSync(join(basePath, "data-repo/contributors")).forEach((file) => { + newContributors.delete(file.split(".")[0]); + }); return newContributors; } @@ -46,7 +43,6 @@ function main() { } let newContributors = getNewContributors(); - console.log(newContributors); newContributors.forEach(async (value) => { // fetch the user data from the github api From 6e31084cc247afa531a30869c65844b867af885c Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Mon, 24 Jun 2024 11:52:03 +0530 Subject: [PATCH 14/71] Fixing scraper-dry-run failing (Genrate markdown files) --- .env | 1 + scripts/generateNewContributors.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.env b/.env index a978ad42..04ee3981 100644 --- a/.env +++ b/.env @@ -19,6 +19,7 @@ NEXT_PUBLIC_CONTRIBUTORS_INFO="" NEXT_PUBLIC_LEADERBOARD_DEFAULT_ROLES="core,intern,contributor" ## -- Data Source -- ## +DATA_REPO = "data-repo" DATA_SOURCE="https://github.com/coronasafe/leaderboard-data.git" ## -- Slack -- ## diff --git a/scripts/generateNewContributors.js b/scripts/generateNewContributors.js index 2f31597a..4a2709be 100644 --- a/scripts/generateNewContributors.js +++ b/scripts/generateNewContributors.js @@ -22,11 +22,11 @@ const basePath = join(process.env.DATA_REPO || process.cwd()); function getNewContributors() { let newContributors = new Set(); - fs.readdirSync(join(basePath, "data-repo/data/github")).forEach((file) => { + fs.readdirSync(join(basePath, "data/github")).forEach((file) => { newContributors.add(file.split(".")[0]); }); - fs.readdirSync(join(basePath, "data-repo/contributors")).forEach((file) => { + fs.readdirSync(join(basePath, "contributors")).forEach((file) => { newContributors.delete(file.split(".")[0]); }); From 8c626748d3ec289a74ad3165856c48f6b69d6b18 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Mon, 24 Jun 2024 11:57:15 +0530 Subject: [PATCH 15/71] Fixing scraper-dry-run failing (Genrate markdown files) --- scripts/generateNewContributors.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generateNewContributors.js b/scripts/generateNewContributors.js index 4a2709be..2f31597a 100644 --- a/scripts/generateNewContributors.js +++ b/scripts/generateNewContributors.js @@ -22,11 +22,11 @@ const basePath = join(process.env.DATA_REPO || process.cwd()); function getNewContributors() { let newContributors = new Set(); - fs.readdirSync(join(basePath, "data/github")).forEach((file) => { + fs.readdirSync(join(basePath, "data-repo/data/github")).forEach((file) => { newContributors.add(file.split(".")[0]); }); - fs.readdirSync(join(basePath, "contributors")).forEach((file) => { + fs.readdirSync(join(basePath, "data-repo/contributors")).forEach((file) => { newContributors.delete(file.split(".")[0]); }); From 0e807d8ba44eb8148ecad7d70add98235054c303 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Mon, 24 Jun 2024 12:05:50 +0530 Subject: [PATCH 16/71] Fixing scraper-dry-run failing (Genrate markdown files) --- .env | 1 - scripts/generateNewContributors.js | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 04ee3981..a978ad42 100644 --- a/.env +++ b/.env @@ -19,7 +19,6 @@ NEXT_PUBLIC_CONTRIBUTORS_INFO="" NEXT_PUBLIC_LEADERBOARD_DEFAULT_ROLES="core,intern,contributor" ## -- Data Source -- ## -DATA_REPO = "data-repo" DATA_SOURCE="https://github.com/coronasafe/leaderboard-data.git" ## -- Slack -- ## diff --git a/scripts/generateNewContributors.js b/scripts/generateNewContributors.js index 2f31597a..2fb71ec1 100644 --- a/scripts/generateNewContributors.js +++ b/scripts/generateNewContributors.js @@ -22,11 +22,11 @@ const basePath = join(process.env.DATA_REPO || process.cwd()); function getNewContributors() { let newContributors = new Set(); - fs.readdirSync(join(basePath, "data-repo/data/github")).forEach((file) => { + fs.readdirSync(join(basePath, "data/github")).forEach((file) => { newContributors.add(file.split(".")[0]); }); - fs.readdirSync(join(basePath, "data-repo/contributors")).forEach((file) => { + fs.readdirSync(join(basePath, "contributors")).forEach((file) => { newContributors.delete(file.split(".")[0]); }); @@ -43,6 +43,7 @@ function main() { } let newContributors = getNewContributors(); + console.log(newContributors); newContributors.forEach(async (value) => { // fetch the user data from the github api From 6c14a72b93457cd4f5d4fa1a9ff23d6e39eedc4c Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 14:10:36 +0530 Subject: [PATCH 17/71] Suggested cahnges done --- .env | 2 +- app/api/leaderboard/functions.ts | 2 +- app/feed/page.tsx | 4 +- components/Select.tsx | 4 +- components/contributors/LeaderboardCard.tsx | 2 +- components/gh_events/GitHubEvents.tsx | 32 +- env.mjs | 2 + github.ts | 538 -------------------- lib/gh_events.ts | 15 +- lib/types.ts | 4 +- package.json | 15 +- public/logo.png | Bin 44232 -> 0 bytes scraper/.env | 1 + scraper/src/github-scraper/config.ts | 3 +- scraper/src/github-scraper/discussion.ts | 21 +- scraper/src/github-scraper/fetchEvents.ts | 39 +- scraper/src/github-scraper/fetchUserData.ts | 22 +- scraper/src/github-scraper/parseEvents.ts | 6 +- scraper/src/github-scraper/saveData.ts | 4 +- scraper/src/github-scraper/types.ts | 9 +- scraper/src/github-scraper/utils.ts | 32 +- tsconfig.json | 2 +- 22 files changed, 102 insertions(+), 657 deletions(-) delete mode 100644 github.ts delete mode 100644 public/logo.png create mode 100644 scraper/.env diff --git a/.env b/.env index a978ad42..a4cbeb3d 100644 --- a/.env +++ b/.env @@ -26,4 +26,4 @@ DATA_SOURCE="https://github.com/coronasafe/leaderboard-data.git" # SLACK_EOD_BOT_SIGNING_SECRET= ## -- Features -- ## -NEXT_PUBLIC_FEATURES=Leaderboard,Contributors,Feed,Releases,Projects \ No newline at end of file +NEXT_PUBLIC_FEATURES=Leaderboard,Contributors,Feed,Releases,Projects diff --git a/app/api/leaderboard/functions.ts b/app/api/leaderboard/functions.ts index 0c1fa2f5..e5066f2b 100644 --- a/app/api/leaderboard/functions.ts +++ b/app/api/leaderboard/functions.ts @@ -28,7 +28,7 @@ export const getLeaderboardData = async ( ) .sort((a, b) => { if (sortBy === "pr_stale") { - return (b.activityData.pr_stale ?? 0) - (a.activityData.pr_stale ?? 0); + return b.activityData.pr_stale - a.activityData.pr_stale; } return b.summary[sortBy] - a.summary[sortBy]; }); diff --git a/app/feed/page.tsx b/app/feed/page.tsx index a13781f2..6b5b458b 100644 --- a/app/feed/page.tsx +++ b/app/feed/page.tsx @@ -1,10 +1,10 @@ import LoadingText from "@/components/LoadingText"; import { IGitHubEvent } from "@/lib/gh_events"; import GitHubEvent from "@/components/gh_events/GitHubEvent"; -import { env } from "../../env.mjs"; +import { env } from "@/env.mjs"; import octokit from "@/lib/octokit"; -const GITHUB_ORG: string = env.NEXT_PUBLIC_GITHUB_ORG ?? ""; +const GITHUB_ORG: string = env.NEXT_PUBLIC_GITHUB_ORG; export const revalidate = 600; diff --git a/components/Select.tsx b/components/Select.tsx index 3c3d5fb7..9e93efce 100644 --- a/components/Select.tsx +++ b/components/Select.tsx @@ -108,7 +108,7 @@ export function Select({ <> {showSelectionsAs ? ( showSelectionsAs == "tags" ? ( -
+
{Array.isArray(value) && value?.length > 0 && value.length !== options.length ? ( @@ -129,7 +129,7 @@ export function Select({ )}
) : ( -
+
{Array.isArray(value) && value.length > 0 && value.length !== options.length ? ( diff --git a/components/contributors/LeaderboardCard.tsx b/components/contributors/LeaderboardCard.tsx index 7fd75e9b..eb9989ad 100644 --- a/components/contributors/LeaderboardCard.tsx +++ b/components/contributors/LeaderboardCard.tsx @@ -53,7 +53,7 @@ export default function LeaderboardCard({ height={56} width={56} /> -
+
{contributor.user.name}
diff --git a/components/gh_events/GitHubEvents.tsx b/components/gh_events/GitHubEvents.tsx index 907002dc..842f3cda 100644 --- a/components/gh_events/GitHubEvents.tsx +++ b/components/gh_events/GitHubEvents.tsx @@ -33,22 +33,20 @@ export default async function GitHubEvents({ minimal }: { minimal?: boolean }) { .slice(0, 5); return ( - <> -
-
    - {events ? ( - events.map((e) => ) - ) : ( - <> - - - - - - - )} -
-
- +
+
    + {events ? ( + events.map((e) => ) + ) : ( + <> + + + + + + + )} +
+
); } diff --git a/env.mjs b/env.mjs index 239d2818..5a2ad9fd 100644 --- a/env.mjs +++ b/env.mjs @@ -24,6 +24,7 @@ export const env = createEnv({ NEXT_PUBLIC_PAGE_TITLE: z.string(), NEXT_PUBLIC_CONTRIBUTORS_INFO: z.string().optional(), NEXT_PUBLIC_LEADERBOARD_DEFAULT_ROLES: z.string().optional(), + BLACKLISTED_USERS: z.string().array(), NEXT_PUBLIC_FEATURES: z.string(), }, @@ -47,5 +48,6 @@ export const env = createEnv({ ? "" : process.env.GITHUB_PAT, NEXT_PUBLIC_FEATURES: process.env.NEXT_PUBLIC_FEATURES, + BLACKLISTED_USERS: process.env.BLACKLISTED_USERS, }, }); diff --git a/github.ts b/github.ts deleted file mode 100644 index 3be59fd0..00000000 --- a/github.ts +++ /dev/null @@ -1,538 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { parseISO, subDays, formatISO, startOfDay } from "date-fns"; -import octokit from "./lib/octokit"; -import { - Action, - Activity, - ProcessData, - UserData, - // IGitHubEvent, - PullRequest, - PullRequestEvent, -} from "./lib/scraper_types"; -import { IGitHubEvent as IG } from "./lib/gh_events"; - -const userBlacklist = new Set(["dependabot", "snyk-bot", "codecov-commenter"]); -let processedData: ProcessData = {}; - -async function addCollaborations(event: PullRequestEvent, eventTime: Date) { - let nameUserCache: { [key: string]: string } = {}; - let emailUserCache: { [key: string]: string } = {}; - const collaborators: Set = new Set(); - - const url: string | undefined = event.payload.pull_request?.commits_url; - - const response = await octokit.request("GET " + url); - const commits = response.data; - for (const commit of commits) { - let authorLogin = commit.author && commit.author.login; - if (!authorLogin) { - authorLogin = commit.commit.author.name; - } - - if (isBlacklisted(authorLogin)) { - continue; - } - - collaborators.add(authorLogin); - - const coAuthors = commit.commit.message.match( - /Co-authored-by: (.+) <(.+)>/, - ); - if (coAuthors) { - for (const [name, email] of coAuthors) { - if (isBlacklisted(name)) { - continue; - } - - if (name in nameUserCache) { - collaborators.add(nameUserCache[name]); - continue; - } - - if (email in emailUserCache) { - collaborators.add(emailUserCache[email]); - continue; - } - - try { - const usersByEmail = await octokit.request("GET /search/users", { - q: email, - }); - - if (usersByEmail.data.total_count > 0) { - const login = usersByEmail.data.items[0].login; - emailUserCache[email] = login; - collaborators.add(login); - continue; - } - const usersByName = await octokit.request("GET /search/users", { - q: name, - }); - - if (usersByName.data.total_count === 1) { - const login = usersByName.data.items[0].login; - nameUserCache[name] = login; - collaborators.add(login); - } - } catch (e) { - console.error( - `Error fetching co-authors for commit ${commit} - ${name} <${email}>: ${e}`, - ); - } - } - } - } - - if (collaborators.size > 1) { - const collaboratorArray = Array.from(collaborators); // Convert Set to Array - for (const user of collaboratorArray) { - const others = new Set(collaborators); - const othersArray = Array.from(others); - - others.delete(user); - appendEvent(user, { - type: "pr_collaborated", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - collaborated_with: [...othersArray], - }); - } - } -} - -const fetchEvents = async ( - org: string, - startDate: Date, - endDate: Date, - page: number = 1, -) => { - const events = await octokit.paginate( - "GET /orgs/{org}/events", - { - org: org, - per_page: 1000, - }, - (response: { data: IG[] }) => { - return response.data as IG[]; - }, - ); - console.log(events.length); - let eventsCount: number = 0; - let filteredEvents = []; - for (const event of events) { - const eventTime: Date = new Date(event.created_at ?? ""); - - if (eventTime > endDate) { - continue; - } else if (eventTime <= startDate) { - return filteredEvents; - } - const isBlacklisted: boolean = [ - "dependabot", - "snyk-bot", - "codecov-commenter", - "github-actions[bot]", - ].includes(event.actor.login); - const isRequiredEventType: boolean = [ - "IssueCommentEvent", - "IssuesEvent", - "PullRequestEvent", - "PullRequestReviewEvent", - ].includes(event.type); - - if (!isBlacklisted && isRequiredEventType) { - console.log("Hello"); - filteredEvents.push(event); - eventsCount++; - } - } - console.log("Fetched " + { eventsCount } + " events"); - console.log(filteredEvents.length); - return filteredEvents; -}; -const isBlacklisted = (login: string): boolean => { - return login.includes("[bot]") || userBlacklist.has(login); -}; -function appendEvent(user: string, event: Activity) { - console.log(`Appending event for ${user}`); - if (!processedData[user]) { - console.log(`Creating new user data for ${user}`); - processedData[user] = { - last_updated: event.time, - activity: [event], - open_prs: [], - authored_issue_and_pr: [], - }; - } else { - processedData[user]["activity"].push(event); - if (event["time"] > processedData[user]["last_updated"]) { - processedData[user]["last_updated"] = event["time"]; - } - } -} -function parseISODate(isoDate: Date) { - return new Date(isoDate); -} -async function calculateTurnaroundTime(event: PullRequestEvent) { - const user: string = event.payload.pull_request.user.login; - const mergedAt: Date = parseISODate(event.payload.pull_request.merged_at); - const createdAt: Date = parseISODate(event.payload.pull_request.created_at); - - const linkedIssues: [string, string][] = []; - const body = event.payload.pull_request.body || ""; - const regex = - /(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved) ([\w\/.-]*)(#\d+)/gi; - let match: RegExpExecArray | null; - - while ((match = regex.exec(body)) !== null) { - linkedIssues.push([match[2], match[3]]); - } - - const prTimelineResponse = await octokit.request( - `GET ${event.payload?.pull_request?.issue_url}/timeline`, - ); - - const prTimeline = prTimelineResponse.data; - - prTimeline.forEach((action: Action) => { - if ( - action.event === "cross-referenced" && - action.source.type === "issue" && - !action.source.issue.pull_request - ) { - linkedIssues.push([ - action.source.issue.repository.full_name, - `#${action.source.issue.number}`, - ]); - } - - if (action.event === "connected") { - // TODO: currently there is no way to get the issue number from the timeline, handle this case while moving to graphql - } - }); - const uniqueLinkedIssues: [string, string][] = Array.from( - new Set(linkedIssues.map((issue) => JSON.stringify(issue))), - ).map((item) => JSON.parse(item) as [string, string]); - const assignedAts: { issue: string; time: Date }[] = []; - - for (const [org_repo, issue] of uniqueLinkedIssues) { - const org = org_repo.split("/")[0] || event.repo.name.split("/")[0]; - const repo = org_repo.split("/")[-1] || event.repo.name.split("/")[1]; - const issueNumber = parseInt(issue.split("#")[1]); - - const issueTimelineResponse = await octokit.request( - "GET /repos/{owner}/{repo}/issues/{issue_number}/timeline", - { - owner: org, - repo: repo, - issue_number: issueNumber, - }, - ); - type issueTimelineResponseType = typeof issueTimelineResponse; - - const issueTimeline = issueTimelineResponse.data; - - // const issueTimeline = issueTimelineResponse.data; - - issueTimeline.forEach((action: any) => { - if (action.event === "assigned" && action.assignee.login === user) { - assignedAts.push({ - issue: `${org}/${repo}#${issueNumber}`, - time: parseISODate(action.created_at), - }); - } - - if (action.event === "unassigned" && action.assignee.login === user) { - assignedAts.pop(); - } - }); - } - - const assignedAt: Date | null = - assignedAts.length === 0 - ? null - : assignedAts.reduce((min, current) => - current.time < min.time ? current : min, - ).time; - const turnaroundTime = - (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; - return turnaroundTime; -} - -const parse_event = async (events: any) => { - for (const event of events) { - const eventTime: Date = parseISO(event.created_at); - const user: string = event.actor.login; - if (isBlacklisted(user)) continue; - - console.log("Processing event for user: ", user); - console.log("event_id : ", event.id); - - switch (event.type) { - case "IssueCommentEvent": - if (event.payload.action === "created") { - appendEvent(user, { - type: "comment_created", - title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), - link: event.payload.comment.html_url, - text: event.payload.comment.body, - }); - } - break; - case "IssuesEvent": - if (["opened", "assigned", "closed"].includes(event.payload.action)) { - appendEvent(user, { - type: `issue_${event.payload.action}`, - title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), - link: event.payload.issue.html_url, - text: event.payload.issue.title, - }); - } - break; - case "PullRequestEvent": - if (event.payload.action === "opened") { - appendEvent(user, { - type: "pr_opened", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - }); - } else if ( - event.payload.action === "closed" && - event.payload.pull_request.merged - ) { - const turnaroundTime: number = await calculateTurnaroundTime(event); - appendEvent(user, { - type: "pr_merged", - title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), - link: event.payload.pull_request.html_url, - text: event.payload.pull_request.title, - turnaround_time: turnaroundTime, - }); - await addCollaborations(event, eventTime); - } - break; - case "PullRequestReviewEvent": - appendEvent(user, { - type: "pr_reviewed", - time: eventTime.toISOString(), - title: `${event.repo.name}#${event.payload.pull_request.number}`, - link: event.payload.review.html_url, - text: event.payload.pull_request.title, - }); - break; - default: - break; - } - } - return processedData; -}; -async function resolve_autonomy_responsibility(event: any, user: string) { - if (event.event === "cross-referenced" && event.source.type === "issue") { - return event.source.issue.user.login === user; - } - return false; -} -const fetch_merge_events = async (user: string, org: string) => { - console.log("Merge events for : ", user); - - // Fetching closed issues authored by the user - const { data: issues } = await octokit.request("GET /search/issues", { - q: `is:issue is:closed org:${org} author:${user}`, - }); - - let merged_prs = []; - - for (const issue of issues.items) { - const { data: timeline_events } = await octokit.request( - "GET " + issue.timeline_url, - ); - - for (const event of timeline_events) { - if (await resolve_autonomy_responsibility(event, user)) { - const pull_request = event.source.issue.pull_request; - if (pull_request && pull_request.merged_at) { - merged_prs.push({ - issue_link: issue.html_url, - pr_link: pull_request.html_url, - }); - } - } - } - } - - if (!processedData[user]) { - processedData[user] = { - authored_issue_and_pr: [], - last_updated: "", - activity: [], - open_prs: [], - }; - } - - for (const pr of merged_prs) { - processedData[user].authored_issue_and_pr.push(pr); - } - - return processedData; -}; - -const fetchOpenPulls = async (user: string, org: string) => { - console.log(`Fetching open pull requests for ${user}`); - const { data } = await octokit.request("GET /search/issues", { - q: `is:pr is:open org:${org} author:${user}`, - }); - - // let pulls = data.items; - let pulls = data.items as unknown as PullRequest[]; - - pulls.forEach((pr) => { - let today: Date = new Date(); - let prLastUpdated: Date = new Date(pr.updated_at); - let staleFor: number = Math.floor( - (today.getTime() - prLastUpdated.getTime()) / (1000 * 60 * 60 * 24), - ); - - if (!processedData[user]) { - processedData[user] = { - last_updated: "", - activity: [], - authored_issue_and_pr: [], - open_prs: [], - }; - } - processedData[user].open_prs.push({ - link: pr.html_url, - title: pr.title, - stale_for: staleFor, - labels: pr.labels.map((label: { name: string }) => label.name), - }); - }); - - console.log(`Fetched ${pulls.length} open pull requests for ${user}`); - return processedData; -}; -const scrapeGitHub = async ( - org: string, - dataDir: string, - date: string, - numDays: number = 1, -): Promise => { - const endDate: Date = startOfDay(parseISO(date)); - const startDate: Date = startOfDay(subDays(endDate, numDays)); - - console.log( - `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, - ); - - const events: any = await fetchEvents(org, startDate, endDate); - processedData = await parse_event(events); - - for (const user of Object.keys(processedData)) { - try { - await fetch_merge_events(user, org); - } catch (e) { - console.error(`Error fetching merge events for ${user}: ${e}`); - } - try { - await fetchOpenPulls(user, org); - } catch (e) { - console.error(`Error fetching open pulls for ${user}: ${e}`); - } - } - - console.log("Scraping completed"); -}; -function loadUserData(user: string, dataDir: string) { - const file = path.join(dataDir, `${user}.json`); - console.log(`Loading user data from ${file}`); - - try { - const response = fs.readFileSync(file); - const data: UserData = JSON.parse(response.toString()); - return data; - } catch (error: any) { - if (error.code === "ENOENT" || error.name === "SyntaxError") { - console.log(`User data not found for ${user}`); - return { activity: [] }; - } else { - throw error; // rethrow unexpected errors - } - } -} -function saveUserData( - user: string, - data: UserData, - dataDir: string, - serializer: any, -) { - const file = path.join(dataDir, `${user}.json`); - console.log(`Saving user data to ${file}`); - - try { - const jsonData = JSON.stringify(data, serializer, 2); - fs.writeFileSync(file, jsonData); - } catch (error: any) { - console.error(`Failed to save user data for ${user}: ${error.message}`); - throw error; - } -} -const merged_data = async (dataDir: string) => { - console.log("Updating data"); - fs.mkdirSync(dataDir, { recursive: true }); - - for (let user in processedData) { - if (processedData.hasOwnProperty(user)) { - console.log(`Merging user data for ${user}`); - let oldData = await loadUserData(user, dataDir); - let userData = processedData[user]; - let newUniqueEvents = []; - - for (let event of userData.activity) { - if ( - !oldData.activity.some( - (oldEvent: Activity) => - JSON.stringify(oldEvent) === JSON.stringify(event), - ) - ) { - newUniqueEvents.push(event); - } - } - - userData.activity = newUniqueEvents.concat(oldData.activity); - saveUserData(user, userData, dataDir, null); - } - } -}; - -const main = async () => { - // Extract command line arguments (skip the first two default arguments) - const args: string[] = process.argv.slice(2); - - // Destructure arguments with default values - const [ - orgName, - dataDir, - date = formatISO(subDays(new Date(), 1), { representation: "date" }), - numDays = 1, - ] = args; - - if (!orgName || !dataDir) { - console.error("Usage: node script.js [date] [numDays]"); - process.exit(1); - } - - await scrapeGitHub(orgName, dataDir, date, Number(numDays)); - await merged_data(dataDir); - console.log("Done"); -}; - -main(); diff --git a/lib/gh_events.ts b/lib/gh_events.ts index 845d1f3f..19113689 100644 --- a/lib/gh_events.ts +++ b/lib/gh_events.ts @@ -37,25 +37,21 @@ interface Comment { updated_at: string; } -export interface PullRequest { +interface PullRequest { html_url: string; user: Actor; title: string; body: string; number: number; - labels: string[] | { name: string }[]; + labels: string[]; comments: number; review_comments: number; commits: number; additions: number; deletions: number; changed_files: number; - created_at: Date; + created_at: string; updated_at: string; - merged_at: Date; - issue_url: string; - merged?: string; - commits_url?: string; } interface Review { @@ -86,7 +82,6 @@ interface Commit { url: string; message: string; } - interface GitHubEvent { id: string; actor: Actor; @@ -108,7 +103,6 @@ interface PullRequestReviewCommentEvent extends GitHubEvent { pull_request: PullRequest; }; } - interface PullRequestReviewEvent extends GitHubEvent { type: "PullRequestReviewEvent"; payload: { @@ -117,7 +111,6 @@ interface PullRequestReviewEvent extends GitHubEvent { pull_request: PullRequest; }; } - interface MemberEvent extends GitHubEvent { type: "MemberEvent"; payload: { @@ -143,7 +136,7 @@ interface IssueCommentEvent extends GitHubEvent { }; } -export interface PullRequestEvent extends GitHubEvent { +interface PullRequestEvent extends GitHubEvent { type: "PullRequestEvent"; payload: { action: string; diff --git a/lib/types.ts b/lib/types.ts index 143a830d..1413892a 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -28,7 +28,7 @@ export interface ActivityData { last_updated?: string; activity: Activity[]; open_prs: OpenPr[]; - pr_stale?: number; + pr_stale: number; authored_issue_and_pr: AuthoredIssueAndPr[]; } export interface ProcessData { @@ -91,7 +91,7 @@ export interface Action { created_at: Date; } export interface Activity { - type: (typeof ACTIVITY_TYPES)[number] | string; + type: (typeof ACTIVITY_TYPES)[number]; title: string; time: string; link: string; diff --git a/package.json b/package.json index 2c2c859c..6d55e546 100644 --- a/package.json +++ b/package.json @@ -10,16 +10,15 @@ "lint-fix": "eslint . --fix", "format": "prettier --write .", "load-data": "node ./scripts/loadOrgData.js", - "prepare": "husky install" + "prepare": "husky install", + "test": "mocha tests" }, "dependencies": { "@headlessui/react": "^1.7.18", - "@octokit/core": "^5.2.0", "@t3-oss/env-nextjs": "^0.9.2", "@vercel/kv": "^1.0.1", "clsx": "^1.2.1", "date-fns": "^2.30.0", - "fs": "^0.0.1-security", "gray-matter": "^4.0.3", "next": "^14.1.3", "next-themes": "^0.2.1", @@ -42,6 +41,8 @@ "@types/node": "^20.11.21", "@types/react": "^18.2.60", "autoprefixer": "^10.4.17", + "chai": "^5.1.1", + "chai-json-schema": "^1.5.1", "dotenv": "^16.4.5", "eslint": "8.16.0", "eslint-config-next": "^14.1.0", @@ -49,12 +50,14 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-tailwindcss": "^3.14.3", "husky": "^8.0.3", + "mocha": "^10.5.1", "octokit": "^3.1.2", "postcss": "^8.4.35", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", + "strip-json-comments": "^5.0.1", "tailwindcss": "^3.4.1", - "ts-node": "^10.9.2", - "typescript": "^5.4.5" + "typescript": "^5.3.3", + "yaml": "^2.4.5" } -} \ No newline at end of file +} diff --git a/public/logo.png b/public/logo.png deleted file mode 100644 index 0050b13ba30c4c5527af5ae8ca3da4e7d30ede49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44232 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%0D0+(wyvDN-bo!{gez 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 edge.node.discussions.edges, ); - const hasNextPage = response.organization.repositories.pageInfo.hasNextPage; - const nextCursor = response.organization.repositories.pageInfo.endCursor; - - const allDiscussions = discussions.flat(); - - if (hasNextPage) { - const nextDiscussions: Discussion[] = await fetchDiscussionsForOrg( - org, - nextCursor, - ); - return allDiscussions.concat(nextDiscussions); - } - return allDiscussions; + return discussions.flat(); } async function parseDiscussionData(allDiscussions: Discussion[]) { diff --git a/scraper/src/github-scraper/fetchEvents.ts b/scraper/src/github-scraper/fetchEvents.ts index b9431ec7..6a3f2461 100644 --- a/scraper/src/github-scraper/fetchEvents.ts +++ b/scraper/src/github-scraper/fetchEvents.ts @@ -1,5 +1,21 @@ import { octokit } from "./config.js"; import { IGitHubEvent } from "./types.js"; +import dotenv from "dotenv"; +dotenv.config(); + +const blacklistedUsers = [ + "dependabot", + "snyk-bot", + "codecov-commenter", + "github-actions[bot]", +].concat(process.env.BLACKLISTED_USERS?.split(",") ?? []); + +const requiredEventType = [ + "IssueCommentEvent", + "IssuesEvent", + "PullRequestEvent", + "PullRequestReviewEvent", +]; export const fetchEvents = async ( org: string, @@ -17,8 +33,7 @@ export const fetchEvents = async ( }, ); - let eventsCount: number = 0; - let filteredEvents = []; + const filteredEvents = []; for (const event of events) { const eventTime: Date = new Date(event.created_at ?? 0); @@ -27,25 +42,15 @@ export const fetchEvents = async ( } else if (eventTime <= startDate) { return filteredEvents; } - const isBlacklisted: boolean = [ - "dependabot", - "snyk-bot", - "codecov-commenter", - "github-actions[bot]", - ].includes(event.actor.login); - const isRequiredEventType: boolean = [ - "IssueCommentEvent", - "IssuesEvent", - "PullRequestEvent", - "PullRequestReviewEvent", - ].includes(event.type ?? ""); - if (!isBlacklisted && isRequiredEventType) { + if ( + !blacklistedUsers.includes(event.actor.login) && + requiredEventType.includes(event.type) + ) { filteredEvents.push(event); } - eventsCount++; } - console.log("Fetched " + { eventsCount } + " events"); + console.log("Fetched " + filteredEvents.length + " events"); return filteredEvents; }; diff --git a/scraper/src/github-scraper/fetchUserData.ts b/scraper/src/github-scraper/fetchUserData.ts index 98a9e62b..a37c7533 100644 --- a/scraper/src/github-scraper/fetchUserData.ts +++ b/scraper/src/github-scraper/fetchUserData.ts @@ -1,6 +1,5 @@ import { octokit } from "./config.js"; -import { OpenPr } from "./types.js"; -import { resolve_autonomy_responsibility } from "./utils.js"; +import { resolveAutonomyResponsibility } from "./utils.js"; export const fetch_merge_events = async (user: string, org: string) => { console.log("Merge events for : ", user); @@ -10,7 +9,7 @@ export const fetch_merge_events = async (user: string, org: string) => { q: `is:issue is:closed org:${org} author:${user}`, }); - let merged_prs = []; + const merged_prs = []; for (const issue of issues.items) { const { data: timeline_events } = await octokit.request( @@ -18,7 +17,7 @@ export const fetch_merge_events = async (user: string, org: string) => { ); for (const event of timeline_events) { - if (await resolve_autonomy_responsibility(event, user)) { + if (await resolveAutonomyResponsibility(event, user)) { const pull_request = event.source.issue.pull_request; if (pull_request && pull_request.merged_at) { merged_prs.push({ @@ -40,22 +39,21 @@ export const fetchOpenPulls = async (user: string, org: string) => { }); type PullsData = (typeof data.items)[0]; - let pulls: PullsData[] = data.items; - let open_prs: OpenPr[] = []; + const pulls: PullsData[] = data.items; - pulls.forEach((pr: PullsData) => { - let today: Date = new Date(); - let prLastUpdated: Date = new Date(pr.updated_at); - let staleFor: number = Math.floor( + const open_prs = pulls.map((pr: PullsData) => { + const today: Date = new Date(); + const prLastUpdated: Date = new Date(pr.updated_at); + const staleFor: number = Math.floor( (today.getTime() - prLastUpdated.getTime()) / (1000 * 60 * 60 * 24), ); - open_prs.push({ + return { link: pr.html_url, title: pr.title, stale_for: staleFor, labels: pr.labels.map((label: { name: string }) => label.name), - }); + }; }); console.log(`Fetched ${pulls.length} open pull requests for ${user}`); diff --git a/scraper/src/github-scraper/parseEvents.ts b/scraper/src/github-scraper/parseEvents.ts index 2207b27f..cf019100 100644 --- a/scraper/src/github-scraper/parseEvents.ts +++ b/scraper/src/github-scraper/parseEvents.ts @@ -9,7 +9,7 @@ import { parseISO } from "date-fns"; import { isBlacklisted } from "./utils.js"; import { octokit } from "./config.js"; -let processedData: ProcessData = {}; +const processedData: ProcessData = {}; function appendEvent(user: string, event: Activity) { console.log(`Appending event for ${user}`); @@ -30,9 +30,9 @@ function appendEvent(user: string, event: Activity) { } } +const nameUserCache: { [key: string]: string } = {}; +const emailUserCache: { [key: string]: string } = {}; async function addCollaborations(event: PullRequestEvent, eventTime: Date) { - let nameUserCache: { [key: string]: string } = {}; - let emailUserCache: { [key: string]: string } = {}; const collaborators: Set = new Set(); const url: string | undefined = event.payload.pull_request?.commits_url; diff --git a/scraper/src/github-scraper/saveData.ts b/scraper/src/github-scraper/saveData.ts index 143608ec..14674665 100644 --- a/scraper/src/github-scraper/saveData.ts +++ b/scraper/src/github-scraper/saveData.ts @@ -1,5 +1,5 @@ import { ProcessData } from "./types.js"; -import { promises as fs } from "fs"; +import { mkdir } from "fs/promises"; import { loadUserData, saveUserData } from "./utils.js"; export const merged_data = async ( @@ -7,7 +7,7 @@ export const merged_data = async ( processedData: ProcessData, ) => { console.log("Updating data"); - await fs.mkdir(dataDir, { recursive: true }); + await mkdir(dataDir, { recursive: true }); for (let user in processedData) { if (processedData.hasOwnProperty(user)) { diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index cc05f493..ea8def1f 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -157,6 +157,7 @@ export const ACTIVITY_TYPES = [ export interface Action { event: string; source: { + [x: string]: any; type: string; issue: { pull_request: boolean; @@ -230,11 +231,3 @@ export type Discussion = { isAnswered: boolean; }; }; - -export type Edge = { - node: { - discussions: { - edges: Discussion[]; - }; - }; -}; diff --git a/scraper/src/github-scraper/utils.ts b/scraper/src/github-scraper/utils.ts index dee339af..7770c8a3 100644 --- a/scraper/src/github-scraper/utils.ts +++ b/scraper/src/github-scraper/utils.ts @@ -1,7 +1,7 @@ import path from "path"; import { octokit } from "./config.js"; import { Action, ActivityData, PullRequestEvent } from "./types.js"; -import { promises as fs } from "fs"; +import { readFile, writeFile } from "fs/promises"; export const parseISODate = (isoDate: Date) => { return new Date(isoDate); @@ -19,22 +19,17 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { const createdAt: Date = parseISODate(event.payload.pull_request.created_at); const linkedIssues: [string, string][] = []; - const body = event.payload.pull_request.body || ""; - const regex = - /(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved) ([\w\/.-]*)(#\d+)/gi; - let match: RegExpExecArray | null; - - while ((match = regex.exec(body)) !== null) { - linkedIssues.push([match[2], match[3]]); - } + const linkedIssuesResponse = await octokit.request( + `GET ${event.payload?.pull_request?.issue_url}`, + ); + // Fetch url all linked issues url from the response + linkedIssues.push([event.repo.name, `#${linkedIssuesResponse.data.number}`]); const prTimelineResponse = await octokit.request( `GET ${event.payload?.pull_request?.issue_url}/timeline`, ); - const prTimeline = prTimelineResponse.data; - - prTimeline.forEach((action: Action) => { + prTimelineResponse.data.forEach((action: Action) => { if ( action.event === "cross-referenced" && action.source.type === "issue" && @@ -47,12 +42,15 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { } if (action.event === "connected") { - // TODO: currently there is no way to get the issue number from the timeline, handle this case while moving to graphql + // Fetch the issue number from the url + linkedIssues.push([action.source.repository.full_name, `#${0}`]); } }); - const uniqueLinkedIssues: [string, string][] = Array.from( + + const uniqueLinkedIssues = Array.from( new Set(linkedIssues.map((issue) => JSON.stringify(issue))), ).map((item) => JSON.parse(item) as [string, string]); + const assignedAts: { issue: string; time: Date }[] = []; for (const [org_repo, issue] of uniqueLinkedIssues) { @@ -95,7 +93,7 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { return turnaroundTime; } -export async function resolve_autonomy_responsibility( +export async function resolveAutonomyResponsibility( event: Action, user: string, ) { @@ -110,7 +108,7 @@ export async function loadUserData(user: string, dataDir: string) { console.log(`Loading user data from ${file}`); try { - const response = await fs.readFile(file); + const response = await readFile(file); const data: ActivityData = JSON.parse(response.toString()); return data; } catch (error: any) { @@ -134,7 +132,7 @@ export async function saveUserData( try { const jsonData = JSON.stringify(data, serializer, 2); - await fs.writeFile(file, jsonData); + await writeFile(file, jsonData); } catch (error: any) { console.error(`Failed to save user data for ${user}: ${error.message}`); throw error; diff --git a/tsconfig.json b/tsconfig.json index fe9cd07c..80ddaa01 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,6 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "scraper/dist/discssion.js"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules", "data-repo", "data"] } From 4fd59d424f0e71762880f3f6fc750e2f9eb67ae2 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 15:17:33 +0530 Subject: [PATCH 18/71] Update test schema for discussion --- schemas/github-data.yaml | 47 +++++++++++++++++++++++++++++++ scraper/package.json | 3 +- scraper/pnpm-lock.yaml | 1 + tests/github-data-schema.test.mjs | 4 ++- 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/schemas/github-data.yaml b/schemas/github-data.yaml index a82d6aae..8400c46a 100644 --- a/schemas/github-data.yaml +++ b/schemas/github-data.yaml @@ -7,6 +7,8 @@ required: - activity - open_prs # - authored_issue_and_pr + # - discussions + properties: last_updated: @@ -46,3 +48,48 @@ properties: properties: issue_link: string pr_link: string + + discussions: + type: array + items: + type: object + required: ["id", "title", "author", "url", "category", "createdAt", "isAnswered"] + properties: + id: string + title: string + author: + type: object + required: ["login", "avatarUrl"] + properties: + login: string + avatarUrl: string + url: string + category: + type: object + required: ["id", "name", "emoji"] + properties: + id: string + name: string + emoji: string + upvoteCount: integer + reactions: + type: object + required: ["totalCount"] + properties: + totalCount: integer + comments: + type: array + items: + type: object + required: ["author", "upvoteCount", "isAnswer"] + properties: + author: + type: object + required: ["login", "avatarUrl"] + properties: + login: string + avatarUrl: string + upvoteCount: integer + isAnswer: boolean + createdAt: string + isAnswered: boolean diff --git a/scraper/package.json b/scraper/package.json index f7404751..4922d5d6 100644 --- a/scraper/package.json +++ b/scraper/package.json @@ -15,6 +15,7 @@ "license": "ISC", "dependencies": { "date-fns": "^3.6.0", + "dotenv": "^16.4.5", "octokit": "^4.0.2" }, "devDependencies": { @@ -22,4 +23,4 @@ "ts-node": "^10.9.2", "typescript": "^4.9.5" } -} \ No newline at end of file +} diff --git a/scraper/pnpm-lock.yaml b/scraper/pnpm-lock.yaml index 88e21af6..5a18ba33 100644 --- a/scraper/pnpm-lock.yaml +++ b/scraper/pnpm-lock.yaml @@ -500,3 +500,4 @@ snapshots: v8-compile-cache-lib@3.0.1: {} yn@3.1.1: {} + \ No newline at end of file diff --git a/tests/github-data-schema.test.mjs b/tests/github-data-schema.test.mjs index 270f8e42..1a31f2b0 100644 --- a/tests/github-data-schema.test.mjs +++ b/tests/github-data-schema.test.mjs @@ -1,6 +1,7 @@ import { expect, use } from "chai"; import chaiJsonSchema from "chai-json-schema"; import fs from "fs"; +import { describe, it } from "node:test"; import path, { join } from "path"; import stripJsonComments from "strip-json-comments"; import yaml from "yaml"; @@ -10,13 +11,14 @@ const cwd = process.cwd(); const SCHEMA_FILE = join(cwd, "schemas/github-data.yaml"); const GH_DATA = join(cwd, process.env.DATA_REPO ?? "data-repo", "data/github"); -const schema = yaml.parse(fs.readFileSync(SCHEMA_FILE).toString()); +const schema = await yaml.parse(fs.readFileSync(SCHEMA_FILE).toString()); use(chaiJsonSchema); const filesInDir = fs .readdirSync(GH_DATA) .filter((file) => path.extname(file) === ".json"); +console.log(filesInDir.length); filesInDir.forEach((file) => { const content = fs.readFileSync(join(GH_DATA, file)).toString(); From 89800425aaad3bac4d329ed0bdf4284363c11d84 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 15:35:34 +0530 Subject: [PATCH 19/71] resolve-dry-run error with pnpm --- scraper/pnpm-lock.yaml | 565 +++++++++++++++++++---------------------- 1 file changed, 257 insertions(+), 308 deletions(-) diff --git a/scraper/pnpm-lock.yaml b/scraper/pnpm-lock.yaml index 5a18ba33..89a1702d 100644 --- a/scraper/pnpm-lock.yaml +++ b/scraper/pnpm-lock.yaml @@ -1,284 +1,72 @@ -lockfileVersion: '9.0' +lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -importers: - - .: - dependencies: - date-fns: - specifier: ^3.6.0 - version: 3.6.0 - octokit: - specifier: ^4.0.2 - version: 4.0.2 - devDependencies: - '@types/node': - specifier: ^16.11.18 - version: 16.18.98 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@16.18.98)(typescript@4.9.5) - typescript: - specifier: ^4.9.5 - version: 4.9.5 +dependencies: + date-fns: + specifier: ^3.6.0 + version: 3.6.0 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + octokit: + specifier: ^4.0.2 + version: 4.0.2 + +devDependencies: + '@types/node': + specifier: ^16.11.18 + version: 16.18.101 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@16.18.101)(typescript@4.9.5) + typescript: + specifier: ^4.9.5 + version: 4.9.5 packages: - '@cspotcode/source-map-support@0.8.1': + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true - '@jridgewell/resolve-uri@3.1.2': + /@jridgewell/resolve-uri@3.1.2: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} + dev: true - '@jridgewell/sourcemap-codec@1.4.15': + /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true - '@jridgewell/trace-mapping@0.3.9': + /@jridgewell/trace-mapping@0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - - '@octokit/app@15.1.0': - resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} - engines: {node: '>= 18'} - - '@octokit/auth-app@7.1.0': - resolution: {integrity: sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==} - engines: {node: '>= 18'} - - '@octokit/auth-oauth-app@8.1.1': - resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} - engines: {node: '>= 18'} - - '@octokit/auth-oauth-device@7.1.1': - resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} - engines: {node: '>= 18'} - - '@octokit/auth-oauth-user@5.1.1': - resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==} - engines: {node: '>= 18'} - - '@octokit/auth-token@5.1.1': - resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} - engines: {node: '>= 18'} - - '@octokit/auth-unauthenticated@6.1.0': - resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==} - engines: {node: '>= 18'} - - '@octokit/core@6.1.2': - resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} - engines: {node: '>= 18'} - - '@octokit/endpoint@10.1.1': - resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} - engines: {node: '>= 18'} - - '@octokit/graphql@8.1.1': - resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} - engines: {node: '>= 18'} - - '@octokit/oauth-app@7.1.2': - resolution: {integrity: sha512-4ntCOZIiTozKwuYQroX/ZD722tzMH8Eicv/cgDM/3F3lyrlwENHDv4flTCBpSJbfK546B2SrkKMWB+/HbS84zQ==} - engines: {node: '>= 18'} - - '@octokit/oauth-authorization-url@7.1.1': - resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} - engines: {node: '>= 18'} - - '@octokit/oauth-methods@5.1.2': - resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==} - engines: {node: '>= 18'} - - '@octokit/openapi-types@22.2.0': - resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} - - '@octokit/openapi-webhooks-types@8.2.1': - resolution: {integrity: sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==} - - '@octokit/plugin-paginate-graphql@5.2.2': - resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/plugin-paginate-rest@11.3.0': - resolution: {integrity: sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/plugin-rest-endpoint-methods@13.2.1': - resolution: {integrity: sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/plugin-retry@7.1.1': - resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/plugin-throttling@9.3.0': - resolution: {integrity: sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': ^6.0.0 - - '@octokit/request-error@6.1.1': - resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==} - engines: {node: '>= 18'} - - '@octokit/request@9.1.1': - resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==} - engines: {node: '>= 18'} - - '@octokit/types@13.5.0': - resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} - - '@octokit/webhooks-methods@5.1.0': - resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==} - engines: {node: '>= 18'} - - '@octokit/webhooks@13.2.7': - resolution: {integrity: sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==} - engines: {node: '>= 18'} - - '@tsconfig/node10@1.0.11': - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - - '@types/aws-lambda@8.10.138': - resolution: {integrity: sha512-71EHMl70TPWIAsFuHd85NHq6S6T2OOjiisPTrH7RgcjzpJpPh4RQJv7PvVvIxc6PIp8CLV7F9B+TdjcAES5vcA==} - - '@types/node@16.18.98': - resolution: {integrity: sha512-fpiC20NvLpTLAzo3oVBKIqBGR6Fx/8oAK/SSf7G+fydnXMY1x4x9RZ6sBXhqKlCU21g2QapUsbLlhv3+a7wS+Q==} - - acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - - aggregate-error@5.0.0: - resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} - engines: {node: '>=18'} - - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - - before-after-hook@3.0.2: - resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} - - bottleneck@2.19.5: - resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} - - clean-stack@5.2.0: - resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} - engines: {node: '>=14.16'} - - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - date-fns@3.6.0: - resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} - - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - - lru-cache@10.2.2: - resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} - engines: {node: 14 || >=16.14} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - octokit@4.0.2: - resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==} - engines: {node: '>= 18'} - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - - universal-github-app-jwt@2.2.0: - resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} - - universal-user-agent@7.0.2: - resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} - - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - -snapshots: - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.4.15': {} - - '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + dev: true - '@octokit/app@15.1.0': + /@octokit/app@15.1.0: + resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} + engines: {node: '>= 18'} dependencies: '@octokit/auth-app': 7.1.0 '@octokit/auth-unauthenticated': 6.1.0 '@octokit/core': 6.1.2 - '@octokit/oauth-app': 7.1.2 + '@octokit/oauth-app': 7.1.3 '@octokit/plugin-paginate-rest': 11.3.0(@octokit/core@6.1.2) '@octokit/types': 13.5.0 '@octokit/webhooks': 13.2.7 + dev: false - '@octokit/auth-app@7.1.0': + /@octokit/auth-app@7.1.0: + resolution: {integrity: sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==} + engines: {node: '>= 18'} dependencies: '@octokit/auth-oauth-app': 8.1.1 '@octokit/auth-oauth-user': 5.1.1 @@ -288,38 +76,56 @@ snapshots: lru-cache: 10.2.2 universal-github-app-jwt: 2.2.0 universal-user-agent: 7.0.2 + dev: false - '@octokit/auth-oauth-app@8.1.1': + /@octokit/auth-oauth-app@8.1.1: + resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} + engines: {node: '>= 18'} dependencies: '@octokit/auth-oauth-device': 7.1.1 '@octokit/auth-oauth-user': 5.1.1 '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 + dev: false - '@octokit/auth-oauth-device@7.1.1': + /@octokit/auth-oauth-device@7.1.1: + resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} + engines: {node: '>= 18'} dependencies: '@octokit/oauth-methods': 5.1.2 '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 + dev: false - '@octokit/auth-oauth-user@5.1.1': + /@octokit/auth-oauth-user@5.1.1: + resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==} + engines: {node: '>= 18'} dependencies: '@octokit/auth-oauth-device': 7.1.1 '@octokit/oauth-methods': 5.1.2 '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 + dev: false - '@octokit/auth-token@5.1.1': {} + /@octokit/auth-token@5.1.1: + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} + dev: false - '@octokit/auth-unauthenticated@6.1.0': + /@octokit/auth-unauthenticated@6.1.0: + resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==} + engines: {node: '>= 18'} dependencies: '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 + dev: false - '@octokit/core@6.1.2': + /@octokit/core@6.1.2: + resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + engines: {node: '>= 18'} dependencies: '@octokit/auth-token': 5.1.1 '@octokit/graphql': 8.1.1 @@ -328,19 +134,28 @@ snapshots: '@octokit/types': 13.5.0 before-after-hook: 3.0.2 universal-user-agent: 7.0.2 + dev: false - '@octokit/endpoint@10.1.1': + /@octokit/endpoint@10.1.1: + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + engines: {node: '>= 18'} dependencies: '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 + dev: false - '@octokit/graphql@8.1.1': + /@octokit/graphql@8.1.1: + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} dependencies: '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 + dev: false - '@octokit/oauth-app@7.1.2': + /@octokit/oauth-app@7.1.3: + resolution: {integrity: sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg==} + engines: {node: '>= 18'} dependencies: '@octokit/auth-oauth-app': 8.1.1 '@octokit/auth-oauth-user': 5.1.1 @@ -348,123 +163,231 @@ snapshots: '@octokit/core': 6.1.2 '@octokit/oauth-authorization-url': 7.1.1 '@octokit/oauth-methods': 5.1.2 - '@types/aws-lambda': 8.10.138 + '@types/aws-lambda': 8.10.140 universal-user-agent: 7.0.2 + dev: false - '@octokit/oauth-authorization-url@7.1.1': {} + /@octokit/oauth-authorization-url@7.1.1: + resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} + engines: {node: '>= 18'} + dev: false - '@octokit/oauth-methods@5.1.2': + /@octokit/oauth-methods@5.1.2: + resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==} + engines: {node: '>= 18'} dependencies: '@octokit/oauth-authorization-url': 7.1.1 '@octokit/request': 9.1.1 '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 + dev: false - '@octokit/openapi-types@22.2.0': {} + /@octokit/openapi-types@22.2.0: + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + dev: false - '@octokit/openapi-webhooks-types@8.2.1': {} + /@octokit/openapi-webhooks-types@8.2.1: + resolution: {integrity: sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==} + dev: false - '@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2)': + /@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2): + resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' dependencies: '@octokit/core': 6.1.2 + dev: false - '@octokit/plugin-paginate-rest@11.3.0(@octokit/core@6.1.2)': + /@octokit/plugin-paginate-rest@11.3.0(@octokit/core@6.1.2): + resolution: {integrity: sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' dependencies: '@octokit/core': 6.1.2 '@octokit/types': 13.5.0 + dev: false - '@octokit/plugin-rest-endpoint-methods@13.2.1(@octokit/core@6.1.2)': + /@octokit/plugin-rest-endpoint-methods@13.2.1(@octokit/core@6.1.2): + resolution: {integrity: sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' dependencies: '@octokit/core': 6.1.2 '@octokit/types': 13.5.0 + dev: false - '@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2)': + /@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2): + resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' dependencies: '@octokit/core': 6.1.2 '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 bottleneck: 2.19.5 + dev: false - '@octokit/plugin-throttling@9.3.0(@octokit/core@6.1.2)': + /@octokit/plugin-throttling@9.3.0(@octokit/core@6.1.2): + resolution: {integrity: sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^6.0.0 dependencies: '@octokit/core': 6.1.2 '@octokit/types': 13.5.0 bottleneck: 2.19.5 + dev: false - '@octokit/request-error@6.1.1': + /@octokit/request-error@6.1.1: + resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==} + engines: {node: '>= 18'} dependencies: '@octokit/types': 13.5.0 + dev: false - '@octokit/request@9.1.1': + /@octokit/request@9.1.1: + resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==} + engines: {node: '>= 18'} dependencies: '@octokit/endpoint': 10.1.1 '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 + dev: false - '@octokit/types@13.5.0': + /@octokit/types@13.5.0: + resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} dependencies: '@octokit/openapi-types': 22.2.0 + dev: false - '@octokit/webhooks-methods@5.1.0': {} + /@octokit/webhooks-methods@5.1.0: + resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==} + engines: {node: '>= 18'} + dev: false - '@octokit/webhooks@13.2.7': + /@octokit/webhooks@13.2.7: + resolution: {integrity: sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==} + engines: {node: '>= 18'} dependencies: '@octokit/openapi-webhooks-types': 8.2.1 '@octokit/request-error': 6.1.1 '@octokit/webhooks-methods': 5.1.0 aggregate-error: 5.0.0 + dev: false - '@tsconfig/node10@1.0.11': {} + /@tsconfig/node10@1.0.11: + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + dev: true - '@tsconfig/node12@1.0.11': {} + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true - '@tsconfig/node14@1.0.3': {} + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true - '@tsconfig/node16@1.0.4': {} + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true - '@types/aws-lambda@8.10.138': {} + /@types/aws-lambda@8.10.140: + resolution: {integrity: sha512-4Dh3dk2TUcbdfHrX0Al90mNGJDvA9NBiTQPzbrjGi/dLxzKCGOYgT8YQ47jUKNFALkAJAadifq0pzyjIUlhVhg==} + dev: false - '@types/node@16.18.98': {} + /@types/node@16.18.101: + resolution: {integrity: sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA==} + dev: true - acorn-walk@8.3.2: {} + /acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} + engines: {node: '>=0.4.0'} + dependencies: + acorn: 8.12.0 + dev: true - acorn@8.11.3: {} + /acorn@8.12.0: + resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true - aggregate-error@5.0.0: + /aggregate-error@5.0.0: + resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} + engines: {node: '>=18'} dependencies: clean-stack: 5.2.0 indent-string: 5.0.0 + dev: false - arg@4.1.3: {} + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true - before-after-hook@3.0.2: {} + /before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + dev: false - bottleneck@2.19.5: {} + /bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + dev: false - clean-stack@5.2.0: + /clean-stack@5.2.0: + resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} + engines: {node: '>=14.16'} dependencies: escape-string-regexp: 5.0.0 + dev: false + + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true - create-require@1.1.1: {} + /date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + dev: false - date-fns@3.6.0: {} + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true - diff@4.0.2: {} + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: false - escape-string-regexp@5.0.0: {} + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: false - indent-string@5.0.0: {} + /indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + dev: false - lru-cache@10.2.2: {} + /lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + dev: false - make-error@1.3.6: {} + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true - octokit@4.0.2: + /octokit@4.0.2: + resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==} + engines: {node: '>= 18'} dependencies: '@octokit/app': 15.1.0 '@octokit/core': 6.1.2 - '@octokit/oauth-app': 7.1.2 + '@octokit/oauth-app': 7.1.3 '@octokit/plugin-paginate-graphql': 5.2.2(@octokit/core@6.1.2) '@octokit/plugin-paginate-rest': 11.3.0(@octokit/core@6.1.2) '@octokit/plugin-rest-endpoint-methods': 13.2.1(@octokit/core@6.1.2) @@ -472,17 +395,30 @@ snapshots: '@octokit/plugin-throttling': 9.3.0(@octokit/core@6.1.2) '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 + dev: false - ts-node@10.9.2(@types/node@16.18.98)(typescript@4.9.5): + /ts-node@10.9.2(@types/node@16.18.101)(typescript@4.9.5): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 16.18.98 - acorn: 8.11.3 - acorn-walk: 8.3.2 + '@types/node': 16.18.101 + acorn: 8.12.0 + acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 @@ -490,14 +426,27 @@ snapshots: typescript: 4.9.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + dev: true - typescript@4.9.5: {} + /typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true - universal-github-app-jwt@2.2.0: {} + /universal-github-app-jwt@2.2.0: + resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} + dev: false - universal-user-agent@7.0.2: {} + /universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + dev: false - v8-compile-cache-lib@3.0.1: {} + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true - yn@3.1.1: {} - \ No newline at end of file + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true From 68f8fd42d981bcfcaf8317440b431d41fe9f48ef Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 16:04:46 +0530 Subject: [PATCH 20/71] Revert accidental cahnges in scraper0dry-run.yaml --- .github/workflows/scraper-dry-run.yaml | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index c4721192..7035a2f6 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -20,25 +20,16 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v3 + - name: setup python + uses: actions/setup-python@v3 with: - node-version: "20.14.0" - - - name: Install pnpm - run: npm install -g pnpm + python-version: "3.10" - name: Install dependencies - run: pnpm install --frozen-lockfile - working-directory: scraper - - - name: Build the project - run: pnpm build - working-directory: scraper + run: pip install -r scraper/requirements.txt - name: Scrape data from GitHub - run: pnpm start ${{ github.repository_owner }} data/github - working-directory: scraper + run: python scraper/src/github.py ${{ github.repository_owner }} data/github -l DEBUG env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -48,7 +39,7 @@ jobs: run: node scripts/generateNewContributors.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + - uses: actions/upload-artifact@v4 with: name: output @@ -86,4 +77,4 @@ jobs: run: pnpm install --frozen-lockfile - name: Run Tests - run: pnpm test + run: pnpm test \ No newline at end of file From 8675c01f104476ec75af0ee7218a0ca1c47f7a9e Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 16:12:00 +0530 Subject: [PATCH 21/71] Revert accidental cahnges in scraperdry-run.yaml --- .github/workflows/scraper-dry-run.yaml | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 7035a2f6..c5a0e0c2 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -20,16 +20,25 @@ jobs: steps: - uses: actions/checkout@v4 - - name: setup python - uses: actions/setup-python@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 with: - python-version: "3.10" + node-version: "20.14.0" + + - name: Install pnpm + run: npm install -g pnpm - name: Install dependencies - run: pip install -r scraper/requirements.txt + run: pnpm install --frozen-lockfile + working-directory: scraper + + - name: Build the project + run: pnpm build + working-directory: scraper - name: Scrape data from GitHub - run: python scraper/src/github.py ${{ github.repository_owner }} data/github -l DEBUG + run: pnpm start ${{ github.repository_owner }} data/github + working-directory: scraper env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -39,7 +48,7 @@ jobs: run: node scripts/generateNewContributors.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + - uses: actions/upload-artifact@v4 with: name: output @@ -47,7 +56,6 @@ jobs: path: | data contributors - - name: Setup pnpm uses: pnpm/action-setup@v4 with: @@ -64,7 +72,6 @@ jobs: shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - uses: actions/cache@v4 name: Setup pnpm cache with: @@ -72,9 +79,8 @@ jobs: key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - - name: Install dependencies run: pnpm install --frozen-lockfile - name: Run Tests - run: pnpm test \ No newline at end of file + run: pnpm test From 638f272cb2d0a81b7ddfa7b869dabe9a26bf1664 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 16:19:29 +0530 Subject: [PATCH 22/71] dotenv used in generateNewContrbutors.js --- scripts/generateNewContributors.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/generateNewContributors.js b/scripts/generateNewContributors.js index 36cc5f84..ea7e25f0 100644 --- a/scripts/generateNewContributors.js +++ b/scripts/generateNewContributors.js @@ -1,5 +1,7 @@ const fs = require("fs"); const { join } = require("path"); +const dotenv = require("dotenv"); +dotenv.config(); function generateContent(name, github) { return `--- From 386448dd876181512ddfc995bf84499ba7b1192e Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 16:28:15 +0530 Subject: [PATCH 23/71] remove: dotenv used in generateNewContrbutors.js --- .github/workflows/scraper-dry-run.yaml | 1 + scripts/generateNewContributors.js | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index c5a0e0c2..2215aa58 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -56,6 +56,7 @@ jobs: path: | data contributors + - name: Setup pnpm uses: pnpm/action-setup@v4 with: diff --git a/scripts/generateNewContributors.js b/scripts/generateNewContributors.js index ea7e25f0..36cc5f84 100644 --- a/scripts/generateNewContributors.js +++ b/scripts/generateNewContributors.js @@ -1,7 +1,5 @@ const fs = require("fs"); const { join } = require("path"); -const dotenv = require("dotenv"); -dotenv.config(); function generateContent(name, github) { return `--- From eaadb2a8b171d671b4174f06fd64aa7c04d85baa Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 17:31:07 +0530 Subject: [PATCH 24/71] Fix path for data repository to solve dry-run error --- .github/workflows/scraper-dry-run.yaml | 4 ++-- env.mjs | 2 -- public/logo.png | Bin 0 -> 44232 bytes scraper/src/github-scraper/discussion.ts | 4 ++-- scraper/tsconfig.json | 1 + 5 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 public/logo.png diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 2215aa58..07ddf2d6 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -37,7 +37,7 @@ jobs: working-directory: scraper - name: Scrape data from GitHub - run: pnpm start ${{ github.repository_owner }} data/github + run: pnpm start ${{ github.repository_owner }} ../data/github working-directory: scraper env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -56,7 +56,7 @@ jobs: path: | data contributors - + - name: Setup pnpm uses: pnpm/action-setup@v4 with: diff --git a/env.mjs b/env.mjs index 5a2ad9fd..239d2818 100644 --- a/env.mjs +++ b/env.mjs @@ -24,7 +24,6 @@ export const env = createEnv({ NEXT_PUBLIC_PAGE_TITLE: z.string(), NEXT_PUBLIC_CONTRIBUTORS_INFO: z.string().optional(), NEXT_PUBLIC_LEADERBOARD_DEFAULT_ROLES: z.string().optional(), - BLACKLISTED_USERS: z.string().array(), NEXT_PUBLIC_FEATURES: z.string(), }, @@ -48,6 +47,5 @@ export const env = createEnv({ ? "" : process.env.GITHUB_PAT, NEXT_PUBLIC_FEATURES: process.env.NEXT_PUBLIC_FEATURES, - BLACKLISTED_USERS: process.env.BLACKLISTED_USERS, }, }); diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0050b13ba30c4c5527af5ae8ca3da4e7d30ede49 GIT binary patch literal 44232 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%0D0+(wyvDN-bo!{gez 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 d.node.author.login)); - const uniqueAuthors = [...new Set(authorList)]; + const uniqueAuthors = Array.from(new Set(authorList)); const authorDiscussionList = uniqueAuthors.map((author) => { const discussions = allDiscussions.filter( (d) => @@ -93,7 +93,7 @@ async function parseDiscussionData(allDiscussions: Discussion[]) { isAnswered: d.node.isAnswered, upvoteCount: d.node.upvoteCount, participants: [ - ...new Map( + new Map( d.node.comments.edges.map((c) => [ c.node.author.login, { diff --git a/scraper/tsconfig.json b/scraper/tsconfig.json index 332ad1b0..6855b5dc 100644 --- a/scraper/tsconfig.json +++ b/scraper/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "node", "outDir": "./dist", "esModuleInterop": true, + "downlevelIteration": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true From a5f2371466e618053a41de375b96c459c5ba5506 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 17:53:44 +0530 Subject: [PATCH 25/71] update pnpm-lock.yaml --- pnpm-lock.yaml | 112 +++++++++++++++---------------------------------- 1 file changed, 34 insertions(+), 78 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32836840..768c96de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@headlessui/react': specifier: ^1.7.18 version: 1.7.18(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@octokit/core': - specifier: ^5.2.0 - version: 5.2.0 '@t3-oss/env-nextjs': specifier: ^0.9.2 version: 0.9.2(typescript@5.3.3)(zod@3.22.4) @@ -26,9 +23,6 @@ importers: date-fns: specifier: ^2.30.0 version: 2.30.0 - fs: - specifier: ^0.0.1-security - version: 0.0.1-security gray-matter: specifier: ^4.0.3 version: 4.0.3 @@ -299,16 +293,16 @@ packages: resolution: {integrity: sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==} engines: {node: '>= 18'} - '@octokit/core@5.2.0': - resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} + '@octokit/core@5.1.0': + resolution: {integrity: sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==} engines: {node: '>= 18'} '@octokit/endpoint@9.0.4': resolution: {integrity: sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==} engines: {node: '>= 18'} - '@octokit/graphql@7.1.0': - resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} + '@octokit/graphql@7.0.2': + resolution: {integrity: sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==} engines: {node: '>= 18'} '@octokit/oauth-app@6.1.0': @@ -326,9 +320,6 @@ packages: '@octokit/openapi-types@20.0.0': resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} - '@octokit/openapi-types@22.2.0': - resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} - '@octokit/plugin-paginate-graphql@4.0.0': resolution: {integrity: sha512-7HcYW5tP7/Z6AETAPU14gp5H5KmCPT3hmJrS/5tO7HIgbwenYmgw4OY9Ma54FDySuxMwD+wsJlxtuGWwuZuItA==} engines: {node: '>= 18'} @@ -363,24 +354,13 @@ packages: resolution: {integrity: sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==} engines: {node: '>= 18'} - '@octokit/request-error@5.1.0': - resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} - engines: {node: '>= 18'} - '@octokit/request@8.2.0': resolution: {integrity: sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==} engines: {node: '>= 18'} - '@octokit/request@8.4.0': - resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} - engines: {node: '>= 18'} - '@octokit/types@12.6.0': resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} - '@octokit/types@13.5.0': - resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} - '@octokit/webhooks-methods@4.1.0': resolution: {integrity: sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==} engines: {node: '>= 18'} @@ -1137,9 +1117,6 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fs@0.0.1-security: - resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2598,9 +2575,9 @@ snapshots: dependencies: '@octokit/auth-app': 6.0.4 '@octokit/auth-unauthenticated': 5.0.1 - '@octokit/core': 5.2.0 + '@octokit/core': 5.1.0 '@octokit/oauth-app': 6.1.0 - '@octokit/plugin-paginate-rest': 9.2.0(@octokit/core@5.2.0) + '@octokit/plugin-paginate-rest': 9.2.0(@octokit/core@5.1.0) '@octokit/types': 12.6.0 '@octokit/webhooks': 12.1.2 @@ -2649,13 +2626,13 @@ snapshots: '@octokit/request-error': 5.0.1 '@octokit/types': 12.6.0 - '@octokit/core@5.2.0': + '@octokit/core@5.1.0': dependencies: '@octokit/auth-token': 4.0.0 - '@octokit/graphql': 7.1.0 - '@octokit/request': 8.4.0 - '@octokit/request-error': 5.1.0 - '@octokit/types': 13.5.0 + '@octokit/graphql': 7.0.2 + '@octokit/request': 8.2.0 + '@octokit/request-error': 5.0.1 + '@octokit/types': 12.6.0 before-after-hook: 2.2.3 universal-user-agent: 6.0.1 @@ -2664,10 +2641,10 @@ snapshots: '@octokit/types': 12.6.0 universal-user-agent: 6.0.1 - '@octokit/graphql@7.1.0': + '@octokit/graphql@7.0.2': dependencies: - '@octokit/request': 8.4.0 - '@octokit/types': 13.5.0 + '@octokit/request': 8.2.0 + '@octokit/types': 12.6.0 universal-user-agent: 6.0.1 '@octokit/oauth-app@6.1.0': @@ -2675,7 +2652,7 @@ snapshots: '@octokit/auth-oauth-app': 7.0.1 '@octokit/auth-oauth-user': 4.0.1 '@octokit/auth-unauthenticated': 5.0.1 - '@octokit/core': 5.2.0 + '@octokit/core': 5.1.0 '@octokit/oauth-authorization-url': 6.0.2 '@octokit/oauth-methods': 4.0.1 '@types/aws-lambda': 8.10.134 @@ -2693,32 +2670,30 @@ snapshots: '@octokit/openapi-types@20.0.0': {} - '@octokit/openapi-types@22.2.0': {} - - '@octokit/plugin-paginate-graphql@4.0.0(@octokit/core@5.2.0)': + '@octokit/plugin-paginate-graphql@4.0.0(@octokit/core@5.1.0)': dependencies: - '@octokit/core': 5.2.0 + '@octokit/core': 5.1.0 - '@octokit/plugin-paginate-rest@9.2.0(@octokit/core@5.2.0)': + '@octokit/plugin-paginate-rest@9.2.0(@octokit/core@5.1.0)': dependencies: - '@octokit/core': 5.2.0 + '@octokit/core': 5.1.0 '@octokit/types': 12.6.0 - '@octokit/plugin-rest-endpoint-methods@10.4.0(@octokit/core@5.2.0)': + '@octokit/plugin-rest-endpoint-methods@10.4.0(@octokit/core@5.1.0)': dependencies: - '@octokit/core': 5.2.0 + '@octokit/core': 5.1.0 '@octokit/types': 12.6.0 - '@octokit/plugin-retry@6.0.1(@octokit/core@5.2.0)': + '@octokit/plugin-retry@6.0.1(@octokit/core@5.1.0)': dependencies: - '@octokit/core': 5.2.0 + '@octokit/core': 5.1.0 '@octokit/request-error': 5.0.1 '@octokit/types': 12.6.0 bottleneck: 2.19.5 - '@octokit/plugin-throttling@8.2.0(@octokit/core@5.2.0)': + '@octokit/plugin-throttling@8.2.0(@octokit/core@5.1.0)': dependencies: - '@octokit/core': 5.2.0 + '@octokit/core': 5.1.0 '@octokit/types': 12.6.0 bottleneck: 2.19.5 @@ -2728,12 +2703,6 @@ snapshots: deprecation: 2.3.1 once: 1.4.0 - '@octokit/request-error@5.1.0': - dependencies: - '@octokit/types': 13.5.0 - deprecation: 2.3.1 - once: 1.4.0 - '@octokit/request@8.2.0': dependencies: '@octokit/endpoint': 9.0.4 @@ -2741,21 +2710,10 @@ snapshots: '@octokit/types': 12.6.0 universal-user-agent: 6.0.1 - '@octokit/request@8.4.0': - dependencies: - '@octokit/endpoint': 9.0.4 - '@octokit/request-error': 5.1.0 - '@octokit/types': 13.5.0 - universal-user-agent: 6.0.1 - '@octokit/types@12.6.0': dependencies: '@octokit/openapi-types': 20.0.0 - '@octokit/types@13.5.0': - dependencies: - '@octokit/openapi-types': 22.2.0 - '@octokit/webhooks-methods@4.1.0': {} '@octokit/webhooks-types@7.3.2': {} @@ -3358,7 +3316,7 @@ snapshots: eslint: 8.16.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.16.0) eslint-plugin-react: 7.33.2(eslint@8.16.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.16.0) @@ -3386,7 +3344,7 @@ snapshots: enhanced-resolve: 5.15.1 eslint: 8.16.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0) fast-glob: 3.3.2 get-tsconfig: 4.7.2 is-core-module: 2.13.1 @@ -3408,7 +3366,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.16.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0): dependencies: array-includes: 3.1.7 array.prototype.findlastindex: 1.2.4 @@ -3630,8 +3588,6 @@ snapshots: fs.realpath@1.0.0: {} - fs@0.0.1-security: {} - fsevents@2.3.3: optional: true @@ -4534,13 +4490,13 @@ snapshots: octokit@3.1.2: dependencies: '@octokit/app': 14.0.2 - '@octokit/core': 5.2.0 + '@octokit/core': 5.1.0 '@octokit/oauth-app': 6.1.0 - '@octokit/plugin-paginate-graphql': 4.0.0(@octokit/core@5.2.0) - '@octokit/plugin-paginate-rest': 9.2.0(@octokit/core@5.2.0) - '@octokit/plugin-rest-endpoint-methods': 10.4.0(@octokit/core@5.2.0) - '@octokit/plugin-retry': 6.0.1(@octokit/core@5.2.0) - '@octokit/plugin-throttling': 8.2.0(@octokit/core@5.2.0) + '@octokit/plugin-paginate-graphql': 4.0.0(@octokit/core@5.1.0) + '@octokit/plugin-paginate-rest': 9.2.0(@octokit/core@5.1.0) + '@octokit/plugin-rest-endpoint-methods': 10.4.0(@octokit/core@5.1.0) + '@octokit/plugin-retry': 6.0.1(@octokit/core@5.1.0) + '@octokit/plugin-throttling': 8.2.0(@octokit/core@5.1.0) '@octokit/request-error': 5.0.1 '@octokit/types': 12.6.0 From d66fae95c85f88bf43a9b3e36b4bac722d1ad2dc Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Thu, 27 Jun 2024 22:38:33 +0530 Subject: [PATCH 26/71] Store seprately all discussion in discussion folder --- schemas/discussion-data.yaml | 59 ++++++++++++++ scraper/src/github-scraper/discussion.ts | 96 ++++++++++++----------- scraper/src/github-scraper/index.ts | 17 +--- scraper/src/github-scraper/parseEvents.ts | 1 - scraper/src/github-scraper/types.ts | 1 - scraper/src/github-scraper/utils.ts | 22 +++++- tests/github-discussion-schema.test.mjs | 37 +++++++++ 7 files changed, 167 insertions(+), 66 deletions(-) create mode 100644 schemas/discussion-data.yaml create mode 100644 tests/github-discussion-schema.test.mjs diff --git a/schemas/discussion-data.yaml b/schemas/discussion-data.yaml new file mode 100644 index 00000000..c4b8538d --- /dev/null +++ b/schemas/discussion-data.yaml @@ -0,0 +1,59 @@ +$schema: http://json-schema.org/draft-07/schema# +title: GitHub Discussions Schema +type: array + +required: + - id + - title + - author + - url + - category + - createdAt + - isAnswered + +properties: + discussions: + type: array + items: + type: object + required: + ["id", "title", "author", "url", "category", "createdAt", "isAnswered"] + properties: + id: string + title: string + author: + type: object + required: ["login", "avatarUrl"] + properties: + login: string + avatarUrl: string + url: string + category: + type: object + required: ["id", "name", "emoji"] + properties: + id: string + name: string + emoji: string + upvoteCount: integer + reactions: + type: object + required: ["totalCount"] + properties: + totalCount: integer + comments: + type: array + items: + type: object + required: ["author", "upvoteCount", "isAnswer"] + properties: + author: + type: object + required: ["login", "avatarUrl"] + properties: + login: string + avatarUrl: string + upvoteCount: integer + isAnswer: boolean + createdAt: string + isAnswered: boolean diff --git a/scraper/src/github-scraper/discussion.ts b/scraper/src/github-scraper/discussion.ts index 31b590fb..5fc01aa4 100644 --- a/scraper/src/github-scraper/discussion.ts +++ b/scraper/src/github-scraper/discussion.ts @@ -1,5 +1,5 @@ import { octokit } from "./config.js"; -import { Discussion } from "./types.js"; +import { saveDiscussionData } from "./utils.js"; // Query to fetch discussions from GitHub const query = ` @@ -68,55 +68,57 @@ async function fetchDiscussionsForOrg(org: string, cursor = null) { return discussions.flat(); } -async function parseDiscussionData(allDiscussions: Discussion[]) { - const authorList = allDiscussions - .map((d: Discussion) => - d.node.comments.edges.map((c) => c.node.author.login), - ) - .flat(); - authorList.push(...allDiscussions.map((d) => d.node.author.login)); - const uniqueAuthors = Array.from(new Set(authorList)); - const authorDiscussionList = uniqueAuthors.map((author) => { - const discussions = allDiscussions.filter( - (d) => - d.node.author.login === author || - d.node.comments.edges.some((c) => c.node.author.login === author), - ); - const data = discussions.map((d) => { - return { - id: d.node.id, - title: d.node.title, - url: d.node.url, - createdAt: d.node.createdAt, - author: d.node.author, - category: d.node.category, - isAnswered: d.node.isAnswered, - upvoteCount: d.node.upvoteCount, - participants: [ - new Map( - d.node.comments.edges.map((c) => [ - c.node.author.login, - { - login: c.node.author.login, - avatarUrl: c.node.author.avatarUrl, - isAnswer: c.node.isAnswer, - upvoteCount: c.node.upvoteCount, - }, - ]), - ).values(), - ], - }; - }); - return { user: author, discussions: data }; - }); - return authorDiscussionList; -} +// async function parseDiscussionData(allDiscussions: Discussion[]) { +// const authorList = allDiscussions +// .map((d: Discussion) => +// d.node.comments.edges.map((c) => c.node.author.login), +// ) +// .flat(); +// authorList.push(...allDiscussions.map((d) => d.node.author.login)); +// const uniqueAuthors = Array.from(new Set(authorList)); +// const authorDiscussionList = uniqueAuthors.map((author) => { +// const discussions = allDiscussions.filter( +// (d) => +// d.node.author.login === author || +// d.node.comments.edges.some((c) => c.node.author.login === author), +// ); +// const data = discussions.map((d) => { +// return { +// id: d.node.id, +// title: d.node.title, +// url: d.node.url, +// createdAt: d.node.createdAt, +// author: d.node.author, +// category: d.node.category, +// isAnswered: d.node.isAnswered, +// upvoteCount: d.node.upvoteCount, +// participants: [ +// new Map( +// d.node.comments.edges.map((c) => [ +// c.node.author.login, +// { +// login: c.node.author.login, +// avatarUrl: c.node.author.avatarUrl, +// isAnswer: c.node.isAnswer, +// upvoteCount: c.node.upvoteCount, +// }, +// ]), +// ).values(), +// ], +// }; +// }); +// return { user: author, discussions: data }; +// }); +// return authorDiscussionList; +// } -export async function fetchAllDiscussionEventsByOrg(organizationName: string) { +export async function fetchAllDiscussionEventsByOrg( + organizationName: string, + dataDir: string, +) { try { const allDiscussions = await fetchDiscussionsForOrg(organizationName); - const parseDiscussion = await parseDiscussionData(allDiscussions); - return parseDiscussion; + await saveDiscussionData(allDiscussions, dataDir); } catch (error: any) { throw new Error(`Error fetching discussions: ${error.message}`); } diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index f16baa8d..ea7d737b 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -33,7 +33,6 @@ const scrapeGitHub = async ( last_updated: "", activity: [], open_prs: [], - discussions: [], }; } try { @@ -53,20 +52,6 @@ const scrapeGitHub = async ( console.error(`Error fetching open pulls for ${user}: ${e}`); } } - const discussions = await fetchAllDiscussionEventsByOrg(orgName); - console.log("Scraping discussions"); - discussions.forEach((d) => { - if (!processedData[d.user]) { - processedData[d.user] = { - authored_issue_and_pr: [], - last_updated: "", - activity: [], - open_prs: [], - discussions: [], - }; - } - processedData[d.user].discussions = d.discussions; - }); console.log("Scraping completed"); }; @@ -97,6 +82,8 @@ const main = async () => { } await scrapeGitHub(orgName, date, Number(numDays), orgName); await merged_data(dataDir, processedData); + await fetchAllDiscussionEventsByOrg(orgName, dataDir); + console.log("Done"); }; diff --git a/scraper/src/github-scraper/parseEvents.ts b/scraper/src/github-scraper/parseEvents.ts index cf019100..c0a50c2d 100644 --- a/scraper/src/github-scraper/parseEvents.ts +++ b/scraper/src/github-scraper/parseEvents.ts @@ -20,7 +20,6 @@ function appendEvent(user: string, event: Activity) { activity: [event], open_prs: [], authored_issue_and_pr: [], - discussions: [], }; } else { processedData[user]["activity"].push(event); diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index ea8def1f..8d616668 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -136,7 +136,6 @@ export interface ActivityData { open_prs: OpenPr[]; pr_stale?: number; authored_issue_and_pr: AuthoredIssueAndPr[]; - discussions?: any; } export interface ProcessData { [key: string]: ActivityData; diff --git a/scraper/src/github-scraper/utils.ts b/scraper/src/github-scraper/utils.ts index 7770c8a3..c5c7742f 100644 --- a/scraper/src/github-scraper/utils.ts +++ b/scraper/src/github-scraper/utils.ts @@ -1,7 +1,7 @@ import path from "path"; import { octokit } from "./config.js"; -import { Action, ActivityData, PullRequestEvent } from "./types.js"; -import { readFile, writeFile } from "fs/promises"; +import { Action, ActivityData, Discussion, PullRequestEvent } from "./types.js"; +import { mkdir, readFile, writeFile } from "fs/promises"; export const parseISODate = (isoDate: Date) => { return new Date(isoDate); @@ -138,3 +138,21 @@ export async function saveUserData( throw error; } } + +export async function saveDiscussionData( + discussions: Discussion, + dataDir: string, +) { + const discussionDir = path.join(dataDir, "discussions"); + await mkdir(discussionDir, { recursive: true }); + const file = path.join(discussionDir, "discussions.json"); + console.log(`Saving discussion data to ${file}`); + + try { + const jsonData = JSON.stringify(discussions, null, 2); + await writeFile(file, jsonData); + } catch (error: any) { + console.error(`Failed to save discussion data: ${error.message}`); + throw error; + } +} diff --git a/tests/github-discussion-schema.test.mjs b/tests/github-discussion-schema.test.mjs new file mode 100644 index 00000000..07c6e9a1 --- /dev/null +++ b/tests/github-discussion-schema.test.mjs @@ -0,0 +1,37 @@ +import { expect, use } from "chai"; +import chaiJsonSchema from "chai-json-schema"; +import fs from "fs"; +import { describe, it } from "node:test"; +import path, { join } from "path"; +import stripJsonComments from "strip-json-comments"; +import yaml from "yaml"; + +const cwd = process.cwd(); + +const SCHEMA_FILE = join(cwd, "schemas/discussion-data.yaml"); +const GH_DATA = join( + cwd, + process.env.DATA_REPO ?? "data-repo", + "data/github/discussions", +); + +const schema = await yaml.parse(fs.readFileSync(SCHEMA_FILE).toString()); + +use(chaiJsonSchema); + +// There will be more files in the folder becuase futrue plan is sharded data +const filesInDir = fs + .readdirSync(GH_DATA) + .filter((file) => path.extname(file) === ".json"); +console.log(filesInDir.length); + +filesInDir.forEach((file) => { + const content = fs.readFileSync(join(GH_DATA, file)).toString(); + const data = JSON.parse(stripJsonComments(content)); + + describe(`Validate '${file}'`, function () { + it("should be properly validated by the json schema", () => { + expect(data).to.be.jsonSchema(schema); + }); + }); +}); From cac83cf9bf8eb69e4a2f0b3e03fe3a7e7b1bf2ff Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 28 Jun 2024 10:54:31 +0530 Subject: [PATCH 27/71] Update discussion schema --- data/github/discussions/discussions.json | 1091 +++++++++++++++++++++ schemas/discussion-data.yaml | 69 +- scraper/src/github-scraper/discussion.ts | 77 +- scraper/src/github-scraper/index.ts | 4 +- scraper/src/github-scraper/parseEvents.ts | 1 + scraper/src/github-scraper/types.ts | 24 +- scraper/src/github-scraper/utils.ts | 18 +- tests/github-discussion-schema.test.mjs | 1 - 8 files changed, 1164 insertions(+), 121 deletions(-) create mode 100644 data/github/discussions/discussions.json diff --git a/data/github/discussions/discussions.json b/data/github/discussions/discussions.json new file mode 100644 index 00000000..69c85363 --- /dev/null +++ b/data/github/discussions/discussions.json @@ -0,0 +1,1091 @@ +[ + { + "source": "github", + "title": "v24.25.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2277", + "time": "2024-06-18T17:31:12Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.24.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2259", + "time": "2024-06-10T17:08:04Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.23.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2227", + "time": "2024-06-03T16:53:59Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.22.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2196", + "time": "2024-05-27T17:58:16Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.21.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2180", + "time": "2024-05-20T16:52:44Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.20.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2149", + "time": "2024-05-13T17:52:57Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.19.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2130", + "time": "2024-05-06T17:23:35Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.17.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2109", + "time": "2024-04-22T09:15:23Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.15.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2067", + "time": "2024-04-09T08:53:28Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.14.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/2043", + "time": "2024-04-01T17:57:49Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.13.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/2027", + "time": "2024-03-28T05:57:40Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.13.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/2026", + "time": "2024-03-28T05:51:38Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.13.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/2023", + "time": "2024-03-27T15:25:16Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.13.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/2022", + "time": "2024-03-27T15:24:58Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.13.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/2021", + "time": "2024-03-27T15:24:39Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.12.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/1990", + "time": "2024-03-19T05:53:38Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.11.0", + "author": "sainak", + "url": "https://github.com/coronasafe/care/discussions/1963", + "time": "2024-03-11T17:37:35Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.09.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1943", + "time": "2024-03-05T02:59:50Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.08.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1923", + "time": "2024-02-27T08:13:01Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.07.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1905", + "time": "2024-02-22T05:41:25Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.06.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1875", + "time": "2024-02-06T06:25:57Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.04.1", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1860", + "time": "2024-01-27T09:17:47Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.04.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1859", + "time": "2024-01-27T09:15:42Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.03.0", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1832", + "time": "2024-01-17T14:20:28Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "January Week 1 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1804", + "time": "2024-01-04T03:14:05Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "December Week 4 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1787", + "time": "2023-12-27T06:13:17Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "December Week 2 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1772", + "time": "2023-12-12T05:26:25Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "December Week 1 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1746", + "time": "2023-12-05T08:12:45Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "November Week 4 Release", + "author": "gigincg", + "url": "https://github.com/coronasafe/care/discussions/1730", + "time": "2023-11-28T04:45:41Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "November Week 3 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1720", + "time": "2023-11-20T12:46:41Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "October Week 4 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1685", + "time": "2023-10-23T05:13:26Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "October Week 3 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1675", + "time": "2023-10-16T08:01:24Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "October Week 2 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1664", + "time": "2023-10-09T06:06:05Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "October Week 1 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1652", + "time": "2023-10-03T02:48:54Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "September Week 4 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1634", + "time": "2023-09-25T05:14:52Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "September Week 3 Release", + "author": "khavinshankar", + "url": "https://github.com/coronasafe/care/discussions/1617", + "time": "2023-09-18T05:02:50Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "Welcome!", + "author": "vigneshhari", + "url": "https://github.com/coronasafe/care/discussions/803", + "time": "2022-05-29T11:03:57Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.26.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/8079", + "time": "2024-06-24T16:38:12Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.25.1", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/8065", + "time": "2024-06-19T18:00:01Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.25.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/8044", + "time": "2024-06-18T17:31:36Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.24.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/8015", + "time": "2024-06-10T17:07:45Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.23.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7968", + "time": "2024-06-03T16:53:01Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.22.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7918", + "time": "2024-05-27T17:57:14Z", + "category": { + "name": "Announcements", + "emoji": ":mega:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.21.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7864", + "time": "2024-05-20T16:50:31Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.20.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7802", + "time": "2024-05-13T17:54:17Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.19.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7766", + "time": "2024-05-06T17:24:20Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.17.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7681", + "time": "2024-04-22T09:14:31Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.15.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7579", + "time": "2024-04-09T08:54:58Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.14.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7520", + "time": "2024-04-01T18:13:40Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.13.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/7483", + "time": "2024-03-27T15:22:27Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.12.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7437", + "time": "2024-03-19T05:55:09Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "Setup Frontend in local machine", + "author": "AbdulBasit2733", + "url": "https://github.com/orgs/coronasafe/discussions/7413", + "time": "2024-03-14T10:28:09Z", + "category": { + "name": "Q&A", + "emoji": ":pray:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.11.0", + "author": "sainak", + "url": "https://github.com/orgs/coronasafe/discussions/7388", + "time": "2024-03-11T17:40:11Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.09.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/7338", + "time": "2024-03-05T02:57:04Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "Problem in committing the changes", + "author": "saloni0419", + "url": "https://github.com/orgs/coronasafe/discussions/7299", + "time": "2024-02-27T18:04:01Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [ + "aeswibon", + "saloni0419" + ] + }, + { + "source": "github", + "title": "v24.08.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/7283", + "time": "2024-02-27T08:11:34Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.07.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/7247", + "time": "2024-02-22T05:39:11Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.06.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/7175", + "time": "2024-02-06T06:22:57Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.05.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/7122", + "time": "2024-01-30T14:16:48Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.04.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/7110", + "time": "2024-01-27T09:13:00Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.03.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/7050", + "time": "2024-01-17T14:17:57Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.02.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6997", + "time": "2024-01-09T04:31:02Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "v24.01.0", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6970", + "time": "2024-01-04T03:14:09Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "December Week 4 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6927", + "time": "2023-12-27T06:11:36Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "December Week 2 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6850", + "time": "2023-12-12T05:25:16Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "December Week 1 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6797", + "time": "2023-12-05T08:11:56Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "November Week 4 Release", + "author": "gigincg", + "url": "https://github.com/orgs/coronasafe/discussions/6740", + "time": "2023-11-28T04:45:08Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "November Week 3 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6672", + "time": "2023-11-20T12:40:31Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "October Week 5 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6520", + "time": "2023-10-30T05:24:49Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "October Week 4 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6481", + "time": "2023-10-23T05:13:16Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "October Week 3 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6454", + "time": "2023-10-16T08:02:10Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [ + "gigincg" + ] + }, + { + "source": "github", + "title": "October Week 2 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6419", + "time": "2023-10-09T06:05:27Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "October Week 1 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6385", + "time": "2023-10-03T02:47:50Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "September Week 4 Release", + "author": "khavinshankar", + "url": "https://github.com/orgs/coronasafe/discussions/6336", + "time": "2023-09-25T05:13:30Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "Live Camera Feed Enhancement", + "author": "JahnabDutta", + "url": "https://github.com/orgs/coronasafe/discussions/6219", + "time": "2023-09-04T13:42:14Z", + "category": { + "name": "Show and tell", + "emoji": ":raised_hands:" + }, + "participants": [] + }, + { + "source": "github", + "title": "[C4GT] Redesign Doctor Notes", + "author": "Bhavik-ag", + "url": "https://github.com/orgs/coronasafe/discussions/6180", + "time": "2023-08-30T10:01:36Z", + "category": { + "name": "Show and tell", + "emoji": ":raised_hands:" + }, + "participants": [] + }, + { + "source": "github", + "title": "August Week 2 Release", + "author": "gigincg", + "url": "https://github.com/orgs/coronasafe/discussions/6072", + "time": "2023-08-14T05:16:36Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "August Week 1 Release", + "author": "gigincg", + "url": "https://github.com/orgs/coronasafe/discussions/6014", + "time": "2023-08-07T08:25:10Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "We are thinking of changing our login page description, and you can contribute!", + "author": "skks1212", + "url": "https://github.com/orgs/coronasafe/discussions/2658", + "time": "2022-06-08T14:45:52Z", + "category": { + "name": "Ideas", + "emoji": ":bulb:" + }, + "participants": [ + "rithviknishad" + ] + }, + { + "source": "github", + "title": "Welcome to care_fe Discussions!", + "author": "tomahawk-pilot", + "url": "https://github.com/orgs/coronasafe/discussions/957", + "time": "2021-04-23T09:04:52Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [ + "mario0508", + "v1nshul" + ] + }, + { + "source": "github", + "title": "Verification / Reverification with Freshdesk", + "author": "jitendraag", + "url": "https://github.com/coronasafe/life/discussions/202", + "time": "2021-04-29T17:54:01Z", + "category": { + "name": "Ideas", + "emoji": ":bulb:" + }, + "participants": [ + "karmakoder", + "vasanthirajkumar" + ] + }, + { + "source": "github", + "title": "Post API to submit a lead (verified or otherwise)", + "author": "rakshitdaga", + "url": "https://github.com/coronasafe/life/discussions/214", + "time": "2021-04-30T04:14:58Z", + "category": { + "name": "Ideas", + "emoji": ":bulb:" + }, + "participants": [] + }, + { + "source": "github", + "title": "A method for missing districts data to come faster in knowledge.", + "author": "Fayiz-A", + "url": "https://github.com/coronasafe/life/discussions/190", + "time": "2021-04-29T12:23:57Z", + "category": { + "name": "Ideas", + "emoji": ":bulb:" + }, + "participants": [] + }, + { + "source": "github", + "title": "Improving the schema", + "author": "bodhish", + "url": "https://github.com/coronasafe/life/discussions/184", + "time": "2021-04-28T20:10:38Z", + "category": { + "name": "Ideas", + "emoji": ":bulb:" + }, + "participants": [ + "vandanabhandari" + ] + }, + { + "source": "github", + "title": "Chatbots", + "author": "vjFaLk", + "url": "https://github.com/coronasafe/life/discussions/146", + "time": "2021-04-27T07:55:46Z", + "category": { + "name": "Show and tell", + "emoji": ":raised_hands:" + }, + "participants": [ + "vjFaLk" + ] + }, + { + "source": "github", + "title": "Use of python", + "author": "avinas-dwivedi", + "url": "https://github.com/coronasafe/life/discussions/154", + "time": "2021-04-27T12:29:56Z", + "category": { + "name": "Q&A", + "emoji": ":pray:" + }, + "participants": [ + "vigneshhari" + ] + }, + { + "source": "github", + "title": "Initial Features for WhatsApp Chatbot", + "author": "aswinmohanme", + "url": "https://github.com/coronasafe/whatsapp-bot/discussions/2", + "time": "2021-04-26T13:11:31Z", + "category": { + "name": "Ideas", + "emoji": ":bulb:" + }, + "participants": [ + "Kishoraditya" + ] + }, + { + "source": "github", + "title": "Welcome to whatsapp-bot Discussions!", + "author": "aswinmohanme", + "url": "https://github.com/coronasafe/whatsapp-bot/discussions/1", + "time": "2021-04-26T13:10:34Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + }, + { + "source": "github", + "title": "Design Methodology", + "author": "agneym", + "url": "https://github.com/coronasafe/02-journal/discussions/19", + "time": "2021-05-15T10:59:32Z", + "category": { + "name": "General", + "emoji": ":speech_balloon:" + }, + "participants": [] + } +] \ No newline at end of file diff --git a/schemas/discussion-data.yaml b/schemas/discussion-data.yaml index c4b8538d..8bfd1e2c 100644 --- a/schemas/discussion-data.yaml +++ b/schemas/discussion-data.yaml @@ -3,57 +3,36 @@ title: GitHub Discussions Schema type: array required: - - id + -source - title - author - url - category - - createdAt - - isAnswered + - time properties: - discussions: - type: array - items: + source: + type: string + title: + type: string + author: + type: string + url: + type: string + time: + type: string + category: type: object required: - ["id", "title", "author", "url", "category", "createdAt", "isAnswered"] + - name + - emoji properties: - id: string - title: string - author: - type: object - required: ["login", "avatarUrl"] - properties: - login: string - avatarUrl: string - url: string - category: - type: object - required: ["id", "name", "emoji"] - properties: - id: string - name: string - emoji: string - upvoteCount: integer - reactions: - type: object - required: ["totalCount"] - properties: - totalCount: integer - comments: - type: array - items: - type: object - required: ["author", "upvoteCount", "isAnswer"] - properties: - author: - type: object - required: ["login", "avatarUrl"] - properties: - login: string - avatarUrl: string - upvoteCount: integer - isAnswer: boolean - createdAt: string - isAnswered: boolean + name: + type: string + emoji: + type: string + participants: + type: array + items: + type: string + diff --git a/scraper/src/github-scraper/discussion.ts b/scraper/src/github-scraper/discussion.ts index 5fc01aa4..f915c281 100644 --- a/scraper/src/github-scraper/discussion.ts +++ b/scraper/src/github-scraper/discussion.ts @@ -1,4 +1,5 @@ import { octokit } from "./config.js"; +import { Discussion, ParsedDiscussion } from "./types.js"; import { saveDiscussionData } from "./utils.js"; // Query to fetch discussions from GitHub @@ -16,36 +17,25 @@ query($org: String!, $cursor: String) { discussions(first: 100) { edges { node { - id title author { login - avatarUrl } url category{ - id name emoji } - upvoteCount - reactions { - totalCount - } comments(first: 10) { edges { node { author { login - avatarUrl } - upvoteCount - isAnswer } } } createdAt - isAnswered } } } @@ -61,56 +51,30 @@ async function fetchDiscussionsForOrg(org: string, cursor = null) { const response = await octokit.graphql.paginate(query, variables); type Edge = typeof response.organization.repositories.edges; - const discussions = response.organization.repositories.edges.map( + const discussions: Edge[] = response.organization.repositories.edges.map( (edge: Edge) => edge.node.discussions.edges, ); return discussions.flat(); } -// async function parseDiscussionData(allDiscussions: Discussion[]) { -// const authorList = allDiscussions -// .map((d: Discussion) => -// d.node.comments.edges.map((c) => c.node.author.login), -// ) -// .flat(); -// authorList.push(...allDiscussions.map((d) => d.node.author.login)); -// const uniqueAuthors = Array.from(new Set(authorList)); -// const authorDiscussionList = uniqueAuthors.map((author) => { -// const discussions = allDiscussions.filter( -// (d) => -// d.node.author.login === author || -// d.node.comments.edges.some((c) => c.node.author.login === author), -// ); -// const data = discussions.map((d) => { -// return { -// id: d.node.id, -// title: d.node.title, -// url: d.node.url, -// createdAt: d.node.createdAt, -// author: d.node.author, -// category: d.node.category, -// isAnswered: d.node.isAnswered, -// upvoteCount: d.node.upvoteCount, -// participants: [ -// new Map( -// d.node.comments.edges.map((c) => [ -// c.node.author.login, -// { -// login: c.node.author.login, -// avatarUrl: c.node.author.avatarUrl, -// isAnswer: c.node.isAnswer, -// upvoteCount: c.node.upvoteCount, -// }, -// ]), -// ).values(), -// ], -// }; -// }); -// return { user: author, discussions: data }; -// }); -// return authorDiscussionList; -// } +async function parseDiscussionData(allDiscussions: Discussion[]) { + const parsedDiscussions: ParsedDiscussion[] = allDiscussions.map((d) => { + const participants = Array.from( + new Set(d.node.comments.edges.map((c) => c.node.author.login)), + ); + return { + source: "github", + title: d.node.title, + author: d.node.author.login, + url: d.node.url, + time: d.node.createdAt, + category: d.node.category, + participants, + }; + }); + return parsedDiscussions; +} export async function fetchAllDiscussionEventsByOrg( organizationName: string, @@ -118,7 +82,8 @@ export async function fetchAllDiscussionEventsByOrg( ) { try { const allDiscussions = await fetchDiscussionsForOrg(organizationName); - await saveDiscussionData(allDiscussions, dataDir); + const parsedDiscussions = await parseDiscussionData(allDiscussions); + await saveDiscussionData(parsedDiscussions, dataDir); } catch (error: any) { throw new Error(`Error fetching discussions: ${error.message}`); } diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index ea7d737b..c8482743 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -80,8 +80,8 @@ const main = async () => { console.error("Invalid date value:", dateArg); process.exit(1); } - await scrapeGitHub(orgName, date, Number(numDays), orgName); - await merged_data(dataDir, processedData); + // await scrapeGitHub(orgName, date, Number(numDays), orgName); + // await merged_data(dataDir, processedData); await fetchAllDiscussionEventsByOrg(orgName, dataDir); console.log("Done"); diff --git a/scraper/src/github-scraper/parseEvents.ts b/scraper/src/github-scraper/parseEvents.ts index c0a50c2d..40574509 100644 --- a/scraper/src/github-scraper/parseEvents.ts +++ b/scraper/src/github-scraper/parseEvents.ts @@ -31,6 +31,7 @@ function appendEvent(user: string, event: Activity) { const nameUserCache: { [key: string]: string } = {}; const emailUserCache: { [key: string]: string } = {}; + async function addCollaborations(event: PullRequestEvent, eventTime: Date) { const collaborators: Set = new Set(); diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index 8d616668..8bcf2a85 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -198,35 +198,37 @@ export interface AuthoredIssueAndPr { export type Discussion = { node: { - id: string; title: string; author: { login: string; - avatarUrl: string; }; url: string; category: { - id: string; name: string; emoji: string; }; - upvoteCount: number; - reactions: { - totalCount: number; - }; comments: { edges: { node: { author: { login: string; - avatarUrl: string; }; - upvoteCount: number; - isAnswer: boolean; }; }[]; }; createdAt: string; - isAnswered: boolean; }; }; + +export type ParsedDiscussion = { + source: string; + title: string; + author: string; + url: string; + time: string; + category: { + name: string; + emoji: string; + }; + participants: string[]; +}; diff --git a/scraper/src/github-scraper/utils.ts b/scraper/src/github-scraper/utils.ts index c5c7742f..d96f1330 100644 --- a/scraper/src/github-scraper/utils.ts +++ b/scraper/src/github-scraper/utils.ts @@ -1,6 +1,11 @@ import path from "path"; import { octokit } from "./config.js"; -import { Action, ActivityData, Discussion, PullRequestEvent } from "./types.js"; +import { + Action, + ActivityData, + ParsedDiscussion, + PullRequestEvent, +} from "./types.js"; import { mkdir, readFile, writeFile } from "fs/promises"; export const parseISODate = (isoDate: Date) => { @@ -97,10 +102,11 @@ export async function resolveAutonomyResponsibility( event: Action, user: string, ) { - if (event.event === "cross-referenced" && event.source.type === "issue") { - return event.source.issue.user.login === user; - } - return false; + return ( + event.event === "cross-referenced" && + event.source.type === "issue" && + event.source.issue.user.login === user + ); } export async function loadUserData(user: string, dataDir: string) { @@ -140,7 +146,7 @@ export async function saveUserData( } export async function saveDiscussionData( - discussions: Discussion, + discussions: ParsedDiscussion[], dataDir: string, ) { const discussionDir = path.join(dataDir, "discussions"); diff --git a/tests/github-discussion-schema.test.mjs b/tests/github-discussion-schema.test.mjs index 07c6e9a1..8b58a93a 100644 --- a/tests/github-discussion-schema.test.mjs +++ b/tests/github-discussion-schema.test.mjs @@ -23,7 +23,6 @@ use(chaiJsonSchema); const filesInDir = fs .readdirSync(GH_DATA) .filter((file) => path.extname(file) === ".json"); -console.log(filesInDir.length); filesInDir.forEach((file) => { const content = fs.readFileSync(join(GH_DATA, file)).toString(); From af061d554ea2a39e28ad2c5cc19f2bf1ae46814a Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 28 Jun 2024 10:55:53 +0530 Subject: [PATCH 28/71] Update discussion schema --- scraper/src/github-scraper/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index c8482743..ea7d737b 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -80,8 +80,8 @@ const main = async () => { console.error("Invalid date value:", dateArg); process.exit(1); } - // await scrapeGitHub(orgName, date, Number(numDays), orgName); - // await merged_data(dataDir, processedData); + await scrapeGitHub(orgName, date, Number(numDays), orgName); + await merged_data(dataDir, processedData); await fetchAllDiscussionEventsByOrg(orgName, dataDir); console.log("Done"); From 122d6629de42b2c5450a72d27bb65047f0996364 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 28 Jun 2024 11:39:52 +0530 Subject: [PATCH 29/71] Update scraper-dry-run.yaml --- .github/workflows/scraper-dry-run.yaml | 40 +- data/github/discussions/discussions.json | 1091 ---------------------- public/logo.png | Bin 44232 -> 0 bytes schemas/discussion-data.yaml | 2 +- schemas/github-data.yaml | 46 - 5 files changed, 12 insertions(+), 1167 deletions(-) delete mode 100644 data/github/discussions/discussions.json delete mode 100644 public/logo.png diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 07ddf2d6..9151ede4 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -14,19 +14,19 @@ jobs: runs-on: ubuntu-latest permissions: issues: read - pull-requests: read + pull_requests: read env: DATA_REPO: ./ steps: - uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v3 + - name: Setup Node.js and PNPM + uses: actions/setup-node@v4 with: node-version: "20.14.0" - - - name: Install pnpm - run: npm install -g pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 - name: Install dependencies run: pnpm install --frozen-lockfile @@ -48,7 +48,7 @@ jobs: run: node scripts/generateNewContributors.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + - uses: actions/upload-artifact@v4 with: name: output @@ -57,31 +57,13 @@ jobs: data contributors - - name: Setup pnpm - uses: pnpm/action-setup@v4 + - name: Setup PNPM cache + uses: actions/cache@v4 with: - version: 9 - run_install: false - - - name: Setup Node.js environment - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - uses: actions/cache@v4 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + path: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + key: ${{ runner.os }}-pnpm-store- restore-keys: | ${{ runner.os }}-pnpm-store- - - name: Install dependencies - run: pnpm install --frozen-lockfile - name: Run Tests run: pnpm test diff --git a/data/github/discussions/discussions.json b/data/github/discussions/discussions.json deleted file mode 100644 index 69c85363..00000000 --- a/data/github/discussions/discussions.json +++ /dev/null @@ -1,1091 +0,0 @@ -[ - { - "source": "github", - "title": "v24.25.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2277", - "time": "2024-06-18T17:31:12Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.24.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2259", - "time": "2024-06-10T17:08:04Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.23.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2227", - "time": "2024-06-03T16:53:59Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.22.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2196", - "time": "2024-05-27T17:58:16Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.21.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2180", - "time": "2024-05-20T16:52:44Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.20.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2149", - "time": "2024-05-13T17:52:57Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.19.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2130", - "time": "2024-05-06T17:23:35Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.17.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2109", - "time": "2024-04-22T09:15:23Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.15.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2067", - "time": "2024-04-09T08:53:28Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.14.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/2043", - "time": "2024-04-01T17:57:49Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.13.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/2027", - "time": "2024-03-28T05:57:40Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.13.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/2026", - "time": "2024-03-28T05:51:38Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.13.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/2023", - "time": "2024-03-27T15:25:16Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.13.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/2022", - "time": "2024-03-27T15:24:58Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.13.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/2021", - "time": "2024-03-27T15:24:39Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.12.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/1990", - "time": "2024-03-19T05:53:38Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.11.0", - "author": "sainak", - "url": "https://github.com/coronasafe/care/discussions/1963", - "time": "2024-03-11T17:37:35Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.09.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1943", - "time": "2024-03-05T02:59:50Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.08.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1923", - "time": "2024-02-27T08:13:01Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.07.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1905", - "time": "2024-02-22T05:41:25Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.06.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1875", - "time": "2024-02-06T06:25:57Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.04.1", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1860", - "time": "2024-01-27T09:17:47Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.04.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1859", - "time": "2024-01-27T09:15:42Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.03.0", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1832", - "time": "2024-01-17T14:20:28Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "January Week 1 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1804", - "time": "2024-01-04T03:14:05Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "December Week 4 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1787", - "time": "2023-12-27T06:13:17Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "December Week 2 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1772", - "time": "2023-12-12T05:26:25Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "December Week 1 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1746", - "time": "2023-12-05T08:12:45Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "November Week 4 Release", - "author": "gigincg", - "url": "https://github.com/coronasafe/care/discussions/1730", - "time": "2023-11-28T04:45:41Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "November Week 3 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1720", - "time": "2023-11-20T12:46:41Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "October Week 4 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1685", - "time": "2023-10-23T05:13:26Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "October Week 3 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1675", - "time": "2023-10-16T08:01:24Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "October Week 2 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1664", - "time": "2023-10-09T06:06:05Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "October Week 1 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1652", - "time": "2023-10-03T02:48:54Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "September Week 4 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1634", - "time": "2023-09-25T05:14:52Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "September Week 3 Release", - "author": "khavinshankar", - "url": "https://github.com/coronasafe/care/discussions/1617", - "time": "2023-09-18T05:02:50Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "Welcome!", - "author": "vigneshhari", - "url": "https://github.com/coronasafe/care/discussions/803", - "time": "2022-05-29T11:03:57Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.26.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/8079", - "time": "2024-06-24T16:38:12Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.25.1", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/8065", - "time": "2024-06-19T18:00:01Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.25.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/8044", - "time": "2024-06-18T17:31:36Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.24.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/8015", - "time": "2024-06-10T17:07:45Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.23.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7968", - "time": "2024-06-03T16:53:01Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.22.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7918", - "time": "2024-05-27T17:57:14Z", - "category": { - "name": "Announcements", - "emoji": ":mega:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.21.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7864", - "time": "2024-05-20T16:50:31Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.20.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7802", - "time": "2024-05-13T17:54:17Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.19.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7766", - "time": "2024-05-06T17:24:20Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.17.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7681", - "time": "2024-04-22T09:14:31Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.15.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7579", - "time": "2024-04-09T08:54:58Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.14.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7520", - "time": "2024-04-01T18:13:40Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.13.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/7483", - "time": "2024-03-27T15:22:27Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.12.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7437", - "time": "2024-03-19T05:55:09Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "Setup Frontend in local machine", - "author": "AbdulBasit2733", - "url": "https://github.com/orgs/coronasafe/discussions/7413", - "time": "2024-03-14T10:28:09Z", - "category": { - "name": "Q&A", - "emoji": ":pray:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.11.0", - "author": "sainak", - "url": "https://github.com/orgs/coronasafe/discussions/7388", - "time": "2024-03-11T17:40:11Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.09.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/7338", - "time": "2024-03-05T02:57:04Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "Problem in committing the changes", - "author": "saloni0419", - "url": "https://github.com/orgs/coronasafe/discussions/7299", - "time": "2024-02-27T18:04:01Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [ - "aeswibon", - "saloni0419" - ] - }, - { - "source": "github", - "title": "v24.08.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/7283", - "time": "2024-02-27T08:11:34Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.07.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/7247", - "time": "2024-02-22T05:39:11Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.06.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/7175", - "time": "2024-02-06T06:22:57Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.05.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/7122", - "time": "2024-01-30T14:16:48Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.04.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/7110", - "time": "2024-01-27T09:13:00Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.03.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/7050", - "time": "2024-01-17T14:17:57Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.02.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6997", - "time": "2024-01-09T04:31:02Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "v24.01.0", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6970", - "time": "2024-01-04T03:14:09Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "December Week 4 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6927", - "time": "2023-12-27T06:11:36Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "December Week 2 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6850", - "time": "2023-12-12T05:25:16Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "December Week 1 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6797", - "time": "2023-12-05T08:11:56Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "November Week 4 Release", - "author": "gigincg", - "url": "https://github.com/orgs/coronasafe/discussions/6740", - "time": "2023-11-28T04:45:08Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "November Week 3 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6672", - "time": "2023-11-20T12:40:31Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "October Week 5 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6520", - "time": "2023-10-30T05:24:49Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "October Week 4 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6481", - "time": "2023-10-23T05:13:16Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "October Week 3 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6454", - "time": "2023-10-16T08:02:10Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [ - "gigincg" - ] - }, - { - "source": "github", - "title": "October Week 2 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6419", - "time": "2023-10-09T06:05:27Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "October Week 1 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6385", - "time": "2023-10-03T02:47:50Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "September Week 4 Release", - "author": "khavinshankar", - "url": "https://github.com/orgs/coronasafe/discussions/6336", - "time": "2023-09-25T05:13:30Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "Live Camera Feed Enhancement", - "author": "JahnabDutta", - "url": "https://github.com/orgs/coronasafe/discussions/6219", - "time": "2023-09-04T13:42:14Z", - "category": { - "name": "Show and tell", - "emoji": ":raised_hands:" - }, - "participants": [] - }, - { - "source": "github", - "title": "[C4GT] Redesign Doctor Notes", - "author": "Bhavik-ag", - "url": "https://github.com/orgs/coronasafe/discussions/6180", - "time": "2023-08-30T10:01:36Z", - "category": { - "name": "Show and tell", - "emoji": ":raised_hands:" - }, - "participants": [] - }, - { - "source": "github", - "title": "August Week 2 Release", - "author": "gigincg", - "url": "https://github.com/orgs/coronasafe/discussions/6072", - "time": "2023-08-14T05:16:36Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "August Week 1 Release", - "author": "gigincg", - "url": "https://github.com/orgs/coronasafe/discussions/6014", - "time": "2023-08-07T08:25:10Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "We are thinking of changing our login page description, and you can contribute!", - "author": "skks1212", - "url": "https://github.com/orgs/coronasafe/discussions/2658", - "time": "2022-06-08T14:45:52Z", - "category": { - "name": "Ideas", - "emoji": ":bulb:" - }, - "participants": [ - "rithviknishad" - ] - }, - { - "source": "github", - "title": "Welcome to care_fe Discussions!", - "author": "tomahawk-pilot", - "url": "https://github.com/orgs/coronasafe/discussions/957", - "time": "2021-04-23T09:04:52Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [ - "mario0508", - "v1nshul" - ] - }, - { - "source": "github", - "title": "Verification / Reverification with Freshdesk", - "author": "jitendraag", - "url": "https://github.com/coronasafe/life/discussions/202", - "time": "2021-04-29T17:54:01Z", - "category": { - "name": "Ideas", - "emoji": ":bulb:" - }, - "participants": [ - "karmakoder", - "vasanthirajkumar" - ] - }, - { - "source": "github", - "title": "Post API to submit a lead (verified or otherwise)", - "author": "rakshitdaga", - "url": "https://github.com/coronasafe/life/discussions/214", - "time": "2021-04-30T04:14:58Z", - "category": { - "name": "Ideas", - "emoji": ":bulb:" - }, - "participants": [] - }, - { - "source": "github", - "title": "A method for missing districts data to come faster in knowledge.", - "author": "Fayiz-A", - "url": "https://github.com/coronasafe/life/discussions/190", - "time": "2021-04-29T12:23:57Z", - "category": { - "name": "Ideas", - "emoji": ":bulb:" - }, - "participants": [] - }, - { - "source": "github", - "title": "Improving the schema", - "author": "bodhish", - "url": "https://github.com/coronasafe/life/discussions/184", - "time": "2021-04-28T20:10:38Z", - "category": { - "name": "Ideas", - "emoji": ":bulb:" - }, - "participants": [ - "vandanabhandari" - ] - }, - { - "source": "github", - "title": "Chatbots", - "author": "vjFaLk", - "url": "https://github.com/coronasafe/life/discussions/146", - "time": "2021-04-27T07:55:46Z", - "category": { - "name": "Show and tell", - "emoji": ":raised_hands:" - }, - "participants": [ - "vjFaLk" - ] - }, - { - "source": "github", - "title": "Use of python", - "author": "avinas-dwivedi", - "url": "https://github.com/coronasafe/life/discussions/154", - "time": "2021-04-27T12:29:56Z", - "category": { - "name": "Q&A", - "emoji": ":pray:" - }, - "participants": [ - "vigneshhari" - ] - }, - { - "source": "github", - "title": "Initial Features for WhatsApp Chatbot", - "author": "aswinmohanme", - "url": "https://github.com/coronasafe/whatsapp-bot/discussions/2", - "time": "2021-04-26T13:11:31Z", - "category": { - "name": "Ideas", - "emoji": ":bulb:" - }, - "participants": [ - "Kishoraditya" - ] - }, - { - "source": "github", - "title": "Welcome to whatsapp-bot Discussions!", - "author": "aswinmohanme", - "url": "https://github.com/coronasafe/whatsapp-bot/discussions/1", - "time": "2021-04-26T13:10:34Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - }, - { - "source": "github", - "title": "Design Methodology", - "author": "agneym", - "url": "https://github.com/coronasafe/02-journal/discussions/19", - "time": "2021-05-15T10:59:32Z", - "category": { - "name": "General", - "emoji": ":speech_balloon:" - }, - "participants": [] - } -] \ No newline at end of file diff --git a/public/logo.png b/public/logo.png deleted file mode 100644 index 0050b13ba30c4c5527af5ae8ca3da4e7d30ede49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44232 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%0D0+(wyvDN-bo!{gez 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 Date: Fri, 28 Jun 2024 12:53:02 +0530 Subject: [PATCH 30/71] Update scraper-dry-run.yaml and fix some typos --- .github/workflows/scraper-dry-run.yaml | 26 ++++++++++++--------- scraper/src/github-scraper/fetchUserData.ts | 2 +- scraper/src/github-scraper/index.ts | 8 +++---- scraper/src/github-scraper/saveData.ts | 2 +- scraper/src/github-scraper/types.ts | 24 ------------------- scraper/src/github-scraper/utils.ts | 26 +++++++++++++++++++++ 6 files changed, 47 insertions(+), 41 deletions(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index 9151ede4..bf1c894f 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -14,19 +14,19 @@ jobs: runs-on: ubuntu-latest permissions: issues: read - pull_requests: read + pull-requests: read env: DATA_REPO: ./ steps: - uses: actions/checkout@v4 - - name: Setup Node.js and PNPM - uses: actions/setup-node@v4 + - name: Setup Node.js + uses: actions/setup-node@v3 with: node-version: "20.14.0" - uses: pnpm/action-setup@v4 - with: - version: 9 + + - name: Install pnpm + run: npm install -g pnpm - name: Install dependencies run: pnpm install --frozen-lockfile @@ -48,7 +48,7 @@ jobs: run: node scripts/generateNewContributors.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + - uses: actions/upload-artifact@v4 with: name: output @@ -57,11 +57,15 @@ jobs: data contributors - - name: Setup PNPM cache - uses: actions/cache@v4 + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + - uses: actions/cache@v4 + name: Setup pnpm cache with: - path: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - key: ${{ runner.os }}-pnpm-store- + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- diff --git a/scraper/src/github-scraper/fetchUserData.ts b/scraper/src/github-scraper/fetchUserData.ts index a37c7533..95c90344 100644 --- a/scraper/src/github-scraper/fetchUserData.ts +++ b/scraper/src/github-scraper/fetchUserData.ts @@ -1,7 +1,7 @@ import { octokit } from "./config.js"; import { resolveAutonomyResponsibility } from "./utils.js"; -export const fetch_merge_events = async (user: string, org: string) => { +export const fetchMergeEvents = async (user: string, org: string) => { console.log("Merge events for : ", user); // Fetching closed issues authored by the user diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index ea7d737b..cc482c99 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -1,9 +1,9 @@ import { formatISO, parseISO, startOfDay, subDays } from "date-fns"; import { IGitHubEvent, ProcessData } from "./types.js"; -import { fetch_merge_events, fetchOpenPulls } from "./fetchUserData.js"; +import { fetchMergeEvents, fetchOpenPulls } from "./fetchUserData.js"; import { fetchEvents } from "./fetchEvents.js"; import { parseEvents } from "./parseEvents.js"; -import { merged_data } from "./saveData.js"; +import { mergedData } from "./saveData.js"; import { fetchAllDiscussionEventsByOrg } from "./discussion.js"; let processedData: ProcessData = {}; @@ -36,7 +36,7 @@ const scrapeGitHub = async ( }; } try { - const merged_prs = await fetch_merge_events(user, org); + const merged_prs = await fetchMergeEvents(user, org); for (const pr of merged_prs) { processedData[user].authored_issue_and_pr.push(pr); } @@ -81,7 +81,7 @@ const main = async () => { process.exit(1); } await scrapeGitHub(orgName, date, Number(numDays), orgName); - await merged_data(dataDir, processedData); + await mergedData(dataDir, processedData); await fetchAllDiscussionEventsByOrg(orgName, dataDir); console.log("Done"); diff --git a/scraper/src/github-scraper/saveData.ts b/scraper/src/github-scraper/saveData.ts index 14674665..e75e204e 100644 --- a/scraper/src/github-scraper/saveData.ts +++ b/scraper/src/github-scraper/saveData.ts @@ -2,7 +2,7 @@ import { ProcessData } from "./types.js"; import { mkdir } from "fs/promises"; import { loadUserData, saveUserData } from "./utils.js"; -export const merged_data = async ( +export const mergedData = async ( dataDir: string, processedData: ProcessData, ) => { diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index 8bcf2a85..332324f4 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -6,23 +6,10 @@ interface Actor { avatar_url: string; } -interface Reactions { - total_count: number; - "+1": number; - "-1": number; - laugh: number; - hooray: number; - confused: number; - heart: number; - rocket: number; - eyes: number; -} - interface Comment { html_url: string; user: Actor; body: string; - reactions: Reactions; created_at: string; updated_at: string; } @@ -61,22 +48,11 @@ interface Issue { title: string; body: string; labels: string[]; - reactions: Reactions; number: number; created_at: string; updated_at: string; } -interface Commit { - sha: string; - author: { - name: string; - email: string; - }; - url: string; - message: string; -} - interface GitHubEvent { id: string; actor: Actor; diff --git a/scraper/src/github-scraper/utils.ts b/scraper/src/github-scraper/utils.ts index d96f1330..31d81cf7 100644 --- a/scraper/src/github-scraper/utils.ts +++ b/scraper/src/github-scraper/utils.ts @@ -28,8 +28,34 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { `GET ${event.payload?.pull_request?.issue_url}`, ); // Fetch url all linked issues url from the response + // What if the issue was cross-referenced from another repository than the repository of the PR made also add this feature linkedIssues.push([event.repo.name, `#${linkedIssuesResponse.data.number}`]); + // Fetch issue events to find cross-referenced issues + const issueEventsUrl = linkedIssuesResponse.data.events_url; + const issueEventsResponse = await octokit.request(`GET ${issueEventsUrl}`); + + type issueEvent = typeof issueEventsResponse.data; + // Filter for cross-referenced events and add them if they are from a different repo + issueEventsResponse.data.forEach((event: issueEvent) => { + if (event.event === "cross-referenced" && event.source?.issue) { + const crossReferencedRepoFullName = + event.source.issue.repository.full_name; + const crossReferencedIssueNumber = `#${event.source.issue.number}`; + + // Check if the cross-referenced issue is from a different repository + if ( + crossReferencedRepoFullName !== + linkedIssuesResponse.data.repository.full_name + ) { + linkedIssues.push([ + crossReferencedRepoFullName, + crossReferencedIssueNumber, + ]); + } + } + }); + const prTimelineResponse = await octokit.request( `GET ${event.payload?.pull_request?.issue_url}/timeline`, ); From 05a8b400fc2c9e0a4aac415e3ed934cce6bedce9 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 28 Jun 2024 13:43:02 +0530 Subject: [PATCH 31/71] Update scraper-dry-run.yaml --- .github/workflows/scraper-dry-run.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/scraper-dry-run.yaml b/.github/workflows/scraper-dry-run.yaml index bf1c894f..2ff353f2 100644 --- a/.github/workflows/scraper-dry-run.yaml +++ b/.github/workflows/scraper-dry-run.yaml @@ -56,7 +56,7 @@ jobs: path: | data contributors - + - name: Get pnpm store directory shell: bash run: | @@ -68,6 +68,8 @@ jobs: key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Run Tests run: pnpm test From e678e68321509e382a38012d401a9bd9b7ae5f1a Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 28 Jun 2024 16:58:58 +0530 Subject: [PATCH 32/71] Remove casting in fetchEvents.ts --- lib/types.ts | 24 +- scraper/pnpm-lock.yaml | 557 ++++++++++++---------- scraper/src/github-scraper/discussion.ts | 2 +- scraper/src/github-scraper/fetchEvents.ts | 15 +- scraper/src/github-scraper/index.ts | 8 +- 5 files changed, 317 insertions(+), 289 deletions(-) diff --git a/lib/types.ts b/lib/types.ts index 1413892a..a9959c99 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -31,9 +31,6 @@ export interface ActivityData { pr_stale: number; authored_issue_and_pr: AuthoredIssueAndPr[]; } -export interface ProcessData { - [key: string]: ActivityData; -} export interface Highlights { points: number; @@ -70,26 +67,7 @@ export const ACTIVITY_TYPES = [ "pr_merged", "pr_collaborated", ] as const; -export interface Action { - event: string; - source: { - type: string; - issue: { - pull_request: boolean; - repository: { - full_name: string; - }; - user: { - login: string; - }; - number: number; - }; - }; - assignee: { - login: string; - }; - created_at: Date; -} + export interface Activity { type: (typeof ACTIVITY_TYPES)[number]; title: string; diff --git a/scraper/pnpm-lock.yaml b/scraper/pnpm-lock.yaml index 89a1702d..72b8f679 100644 --- a/scraper/pnpm-lock.yaml +++ b/scraper/pnpm-lock.yaml @@ -1,59 +1,281 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - date-fns: - specifier: ^3.6.0 - version: 3.6.0 - dotenv: - specifier: ^16.4.5 - version: 16.4.5 - octokit: - specifier: ^4.0.2 - version: 4.0.2 - -devDependencies: - '@types/node': - specifier: ^16.11.18 - version: 16.18.101 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@16.18.101)(typescript@4.9.5) - typescript: - specifier: ^4.9.5 - version: 4.9.5 +importers: + + .: + dependencies: + date-fns: + specifier: ^3.6.0 + version: 3.6.0 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + octokit: + specifier: ^4.0.2 + version: 4.0.2 + devDependencies: + '@types/node': + specifier: ^16.11.18 + version: 16.18.101 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@16.18.101)(typescript@4.9.5) + typescript: + specifier: ^4.9.5 + version: 4.9.5 packages: - /@cspotcode/source-map-support@0.8.1: + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - dev: true - /@jridgewell/resolve-uri@3.1.2: + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/sourcemap-codec@1.4.15: + '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true - /@jridgewell/trace-mapping@0.3.9: + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@octokit/app@15.1.0': + resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} + engines: {node: '>= 18'} + + '@octokit/auth-app@7.1.0': + resolution: {integrity: sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-app@8.1.1': + resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-device@7.1.1': + resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-user@5.1.1': + resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==} + engines: {node: '>= 18'} + + '@octokit/auth-token@5.1.1': + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} + + '@octokit/auth-unauthenticated@6.1.0': + resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.2': + resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.1': + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.1.1': + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} + + '@octokit/oauth-app@7.1.3': + resolution: {integrity: sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg==} + engines: {node: '>= 18'} + + '@octokit/oauth-authorization-url@7.1.1': + resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} + engines: {node: '>= 18'} + + '@octokit/oauth-methods@5.1.2': + resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@22.2.0': + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + + '@octokit/openapi-webhooks-types@8.2.1': + resolution: {integrity: sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==} + + '@octokit/plugin-paginate-graphql@5.2.2': + resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-paginate-rest@11.3.0': + resolution: {integrity: sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@13.2.1': + resolution: {integrity: sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@7.1.1': + resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-throttling@9.3.0': + resolution: {integrity: sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^6.0.0 + + '@octokit/request-error@6.1.1': + resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==} + engines: {node: '>= 18'} + + '@octokit/request@9.1.1': + resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==} + engines: {node: '>= 18'} + + '@octokit/types@13.5.0': + resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + + '@octokit/webhooks-methods@5.1.0': + resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==} + engines: {node: '>= 18'} + + '@octokit/webhooks@13.2.7': + resolution: {integrity: sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==} + engines: {node: '>= 18'} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/aws-lambda@8.10.140': + resolution: {integrity: sha512-4Dh3dk2TUcbdfHrX0Al90mNGJDvA9NBiTQPzbrjGi/dLxzKCGOYgT8YQ47jUKNFALkAJAadifq0pzyjIUlhVhg==} + + '@types/node@16.18.101': + resolution: {integrity: sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA==} + + acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} + engines: {node: '>=0.4.0'} + + acorn@8.12.0: + resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} + engines: {node: '>=0.4.0'} + hasBin: true + + aggregate-error@5.0.0: + resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} + engines: {node: '>=18'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + + clean-stack@5.2.0: + resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} + engines: {node: '>=14.16'} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + octokit@4.0.2: + resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==} + engines: {node: '>= 18'} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + universal-github-app-jwt@2.2.0: + resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} + + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + +snapshots: + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /@octokit/app@15.1.0: - resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} - engines: {node: '>= 18'} + '@octokit/app@15.1.0': dependencies: '@octokit/auth-app': 7.1.0 '@octokit/auth-unauthenticated': 6.1.0 @@ -62,11 +284,8 @@ packages: '@octokit/plugin-paginate-rest': 11.3.0(@octokit/core@6.1.2) '@octokit/types': 13.5.0 '@octokit/webhooks': 13.2.7 - dev: false - /@octokit/auth-app@7.1.0: - resolution: {integrity: sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==} - engines: {node: '>= 18'} + '@octokit/auth-app@7.1.0': dependencies: '@octokit/auth-oauth-app': 8.1.1 '@octokit/auth-oauth-user': 5.1.1 @@ -76,56 +295,38 @@ packages: lru-cache: 10.2.2 universal-github-app-jwt: 2.2.0 universal-user-agent: 7.0.2 - dev: false - /@octokit/auth-oauth-app@8.1.1: - resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} - engines: {node: '>= 18'} + '@octokit/auth-oauth-app@8.1.1': dependencies: '@octokit/auth-oauth-device': 7.1.1 '@octokit/auth-oauth-user': 5.1.1 '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 - dev: false - /@octokit/auth-oauth-device@7.1.1: - resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} - engines: {node: '>= 18'} + '@octokit/auth-oauth-device@7.1.1': dependencies: '@octokit/oauth-methods': 5.1.2 '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 - dev: false - /@octokit/auth-oauth-user@5.1.1: - resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==} - engines: {node: '>= 18'} + '@octokit/auth-oauth-user@5.1.1': dependencies: '@octokit/auth-oauth-device': 7.1.1 '@octokit/oauth-methods': 5.1.2 '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 - dev: false - /@octokit/auth-token@5.1.1: - resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} - engines: {node: '>= 18'} - dev: false + '@octokit/auth-token@5.1.1': {} - /@octokit/auth-unauthenticated@6.1.0: - resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==} - engines: {node: '>= 18'} + '@octokit/auth-unauthenticated@6.1.0': dependencies: '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 - dev: false - /@octokit/core@6.1.2: - resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} - engines: {node: '>= 18'} + '@octokit/core@6.1.2': dependencies: '@octokit/auth-token': 5.1.1 '@octokit/graphql': 8.1.1 @@ -134,28 +335,19 @@ packages: '@octokit/types': 13.5.0 before-after-hook: 3.0.2 universal-user-agent: 7.0.2 - dev: false - /@octokit/endpoint@10.1.1: - resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} - engines: {node: '>= 18'} + '@octokit/endpoint@10.1.1': dependencies: '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 - dev: false - /@octokit/graphql@8.1.1: - resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} - engines: {node: '>= 18'} + '@octokit/graphql@8.1.1': dependencies: '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 - dev: false - /@octokit/oauth-app@7.1.3: - resolution: {integrity: sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg==} - engines: {node: '>= 18'} + '@octokit/oauth-app@7.1.3': dependencies: '@octokit/auth-oauth-app': 8.1.1 '@octokit/auth-oauth-user': 5.1.1 @@ -165,225 +357,121 @@ packages: '@octokit/oauth-methods': 5.1.2 '@types/aws-lambda': 8.10.140 universal-user-agent: 7.0.2 - dev: false - /@octokit/oauth-authorization-url@7.1.1: - resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} - engines: {node: '>= 18'} - dev: false + '@octokit/oauth-authorization-url@7.1.1': {} - /@octokit/oauth-methods@5.1.2: - resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==} - engines: {node: '>= 18'} + '@octokit/oauth-methods@5.1.2': dependencies: '@octokit/oauth-authorization-url': 7.1.1 '@octokit/request': 9.1.1 '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 - dev: false - /@octokit/openapi-types@22.2.0: - resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} - dev: false + '@octokit/openapi-types@22.2.0': {} - /@octokit/openapi-webhooks-types@8.2.1: - resolution: {integrity: sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==} - dev: false + '@octokit/openapi-webhooks-types@8.2.1': {} - /@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2): - resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' + '@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 - dev: false - /@octokit/plugin-paginate-rest@11.3.0(@octokit/core@6.1.2): - resolution: {integrity: sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' + '@octokit/plugin-paginate-rest@11.3.0(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 '@octokit/types': 13.5.0 - dev: false - /@octokit/plugin-rest-endpoint-methods@13.2.1(@octokit/core@6.1.2): - resolution: {integrity: sha512-YMWBw6Exh1ZBs5cCE0AnzYxSQDIJS00VlBqISTgNYmu5MBdeM07K/MAJjy/VkNaH5jpJmD/5HFUvIZ+LDB5jSQ==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' + '@octokit/plugin-rest-endpoint-methods@13.2.1(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 '@octokit/types': 13.5.0 - dev: false - /@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2): - resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' + '@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 bottleneck: 2.19.5 - dev: false - /@octokit/plugin-throttling@9.3.0(@octokit/core@6.1.2): - resolution: {integrity: sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': ^6.0.0 + '@octokit/plugin-throttling@9.3.0(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 '@octokit/types': 13.5.0 bottleneck: 2.19.5 - dev: false - /@octokit/request-error@6.1.1: - resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==} - engines: {node: '>= 18'} + '@octokit/request-error@6.1.1': dependencies: '@octokit/types': 13.5.0 - dev: false - /@octokit/request@9.1.1: - resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==} - engines: {node: '>= 18'} + '@octokit/request@9.1.1': dependencies: '@octokit/endpoint': 10.1.1 '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 - dev: false - /@octokit/types@13.5.0: - resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + '@octokit/types@13.5.0': dependencies: '@octokit/openapi-types': 22.2.0 - dev: false - /@octokit/webhooks-methods@5.1.0: - resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==} - engines: {node: '>= 18'} - dev: false + '@octokit/webhooks-methods@5.1.0': {} - /@octokit/webhooks@13.2.7: - resolution: {integrity: sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==} - engines: {node: '>= 18'} + '@octokit/webhooks@13.2.7': dependencies: '@octokit/openapi-webhooks-types': 8.2.1 '@octokit/request-error': 6.1.1 '@octokit/webhooks-methods': 5.1.0 aggregate-error: 5.0.0 - dev: false - /@tsconfig/node10@1.0.11: - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - dev: true + '@tsconfig/node10@1.0.11': {} - /@tsconfig/node12@1.0.11: - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true + '@tsconfig/node12@1.0.11': {} - /@tsconfig/node14@1.0.3: - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true + '@tsconfig/node14@1.0.3': {} - /@tsconfig/node16@1.0.4: - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true + '@tsconfig/node16@1.0.4': {} - /@types/aws-lambda@8.10.140: - resolution: {integrity: sha512-4Dh3dk2TUcbdfHrX0Al90mNGJDvA9NBiTQPzbrjGi/dLxzKCGOYgT8YQ47jUKNFALkAJAadifq0pzyjIUlhVhg==} - dev: false + '@types/aws-lambda@8.10.140': {} - /@types/node@16.18.101: - resolution: {integrity: sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA==} - dev: true + '@types/node@16.18.101': {} - /acorn-walk@8.3.3: - resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} - engines: {node: '>=0.4.0'} + acorn-walk@8.3.3: dependencies: acorn: 8.12.0 - dev: true - /acorn@8.12.0: - resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true + acorn@8.12.0: {} - /aggregate-error@5.0.0: - resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} - engines: {node: '>=18'} + aggregate-error@5.0.0: dependencies: clean-stack: 5.2.0 indent-string: 5.0.0 - dev: false - /arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true + arg@4.1.3: {} - /before-after-hook@3.0.2: - resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} - dev: false + before-after-hook@3.0.2: {} - /bottleneck@2.19.5: - resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} - dev: false + bottleneck@2.19.5: {} - /clean-stack@5.2.0: - resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} - engines: {node: '>=14.16'} + clean-stack@5.2.0: dependencies: escape-string-regexp: 5.0.0 - dev: false - /create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true + create-require@1.1.1: {} - /date-fns@3.6.0: - resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} - dev: false + date-fns@3.6.0: {} - /diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dev: true + diff@4.0.2: {} - /dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - dev: false + dotenv@16.4.5: {} - /escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - dev: false + escape-string-regexp@5.0.0: {} - /indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - dev: false + indent-string@5.0.0: {} - /lru-cache@10.2.2: - resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} - engines: {node: 14 || >=16.14} - dev: false + lru-cache@10.2.2: {} - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true + make-error@1.3.6: {} - /octokit@4.0.2: - resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==} - engines: {node: '>= 18'} + octokit@4.0.2: dependencies: '@octokit/app': 15.1.0 '@octokit/core': 6.1.2 @@ -395,21 +483,8 @@ packages: '@octokit/plugin-throttling': 9.3.0(@octokit/core@6.1.2) '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 - dev: false - /ts-node@10.9.2(@types/node@16.18.101)(typescript@4.9.5): - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true + ts-node@10.9.2(@types/node@16.18.101)(typescript@4.9.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -426,27 +501,13 @@ packages: typescript: 4.9.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: true - /typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true + typescript@4.9.5: {} - /universal-github-app-jwt@2.2.0: - resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} - dev: false + universal-github-app-jwt@2.2.0: {} - /universal-user-agent@7.0.2: - resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} - dev: false + universal-user-agent@7.0.2: {} - /v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true + v8-compile-cache-lib@3.0.1: {} - /yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true + yn@3.1.1: {} diff --git a/scraper/src/github-scraper/discussion.ts b/scraper/src/github-scraper/discussion.ts index f915c281..3f7cbe44 100644 --- a/scraper/src/github-scraper/discussion.ts +++ b/scraper/src/github-scraper/discussion.ts @@ -51,7 +51,7 @@ async function fetchDiscussionsForOrg(org: string, cursor = null) { const response = await octokit.graphql.paginate(query, variables); type Edge = typeof response.organization.repositories.edges; - const discussions: Edge[] = response.organization.repositories.edges.map( + const discussions = response.organization.repositories.edges.map( (edge: Edge) => edge.node.discussions.edges, ); diff --git a/scraper/src/github-scraper/fetchEvents.ts b/scraper/src/github-scraper/fetchEvents.ts index 6a3f2461..3b62b99f 100644 --- a/scraper/src/github-scraper/fetchEvents.ts +++ b/scraper/src/github-scraper/fetchEvents.ts @@ -1,5 +1,4 @@ import { octokit } from "./config.js"; -import { IGitHubEvent } from "./types.js"; import dotenv from "dotenv"; dotenv.config(); @@ -22,16 +21,10 @@ export const fetchEvents = async ( startDate: Date, endDate: Date, ) => { - const events = await octokit.paginate( - "GET /orgs/{org}/events", - { - org: org, - per_page: 1000, - }, - (response: { data: IGitHubEvent[] }) => { - return response.data; - }, - ); + const events = await octokit.paginate("GET /orgs/{org}/events", { + org: org, + per_page: 1000, + }); const filteredEvents = []; for (const event of events) { diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index cc482c99..2c66845f 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -1,5 +1,5 @@ import { formatISO, parseISO, startOfDay, subDays } from "date-fns"; -import { IGitHubEvent, ProcessData } from "./types.js"; +import { ProcessData } from "./types.js"; import { fetchMergeEvents, fetchOpenPulls } from "./fetchUserData.js"; import { fetchEvents } from "./fetchEvents.js"; import { parseEvents } from "./parseEvents.js"; @@ -20,11 +20,7 @@ const scrapeGitHub = async ( `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, ); - const events: IGitHubEvent[] = (await fetchEvents( - org, - startDate, - endDate, - )) as IGitHubEvent[]; + const events = await fetchEvents(org, startDate, endDate); processedData = await parseEvents(events); for (const user of Object.keys(processedData)) { if (!processedData[user]) { From 2dc5b94eeaf0dd8f8057ee41d23b7010d60aa306 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Fri, 28 Jun 2024 17:25:56 +0530 Subject: [PATCH 33/71] fix type error --- scraper/src/github-scraper/fetchEvents.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scraper/src/github-scraper/fetchEvents.ts b/scraper/src/github-scraper/fetchEvents.ts index 3b62b99f..50dce7cd 100644 --- a/scraper/src/github-scraper/fetchEvents.ts +++ b/scraper/src/github-scraper/fetchEvents.ts @@ -38,6 +38,7 @@ export const fetchEvents = async ( if ( !blacklistedUsers.includes(event.actor.login) && + event.type && requiredEventType.includes(event.type) ) { filteredEvents.push(event); From 7ea2a3f711fe6ea61619448e151b285844a17b43 Mon Sep 17 00:00:00 2001 From: dgparmar1406 Date: Mon, 1 Jul 2024 11:50:48 +0530 Subject: [PATCH 34/71] Fix type errors --- public/logo.png | Bin 0 -> 44232 bytes scraper/src/github-scraper/fetchUserData.ts | 2 +- scraper/src/github-scraper/index.ts | 5 ++--- scraper/src/github-scraper/parseEvents.ts | 9 ++++----- scraper/src/github-scraper/types.ts | 2 +- scraper/src/github-scraper/utils.ts | 8 +++----- 6 files changed, 11 insertions(+), 15 deletions(-) create mode 100644 public/logo.png diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0050b13ba30c4c5527af5ae8ca3da4e7d30ede49 GIT binary patch literal 44232 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%0D0+(wyvDN-bo!{gez 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 { link: pr.html_url, title: pr.title, stale_for: staleFor, - labels: pr.labels.map((label: { name: string }) => label.name), + labels: pr.labels.map((label: { name?: string }) => label.name), }; }); diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index 2c66845f..53a18c05 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -1,5 +1,5 @@ import { formatISO, parseISO, startOfDay, subDays } from "date-fns"; -import { ProcessData } from "./types.js"; +import { IGitHubEvent, ProcessData } from "./types.js"; import { fetchMergeEvents, fetchOpenPulls } from "./fetchUserData.js"; import { fetchEvents } from "./fetchEvents.js"; import { parseEvents } from "./parseEvents.js"; @@ -19,9 +19,8 @@ const scrapeGitHub = async ( console.log( `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, ); - const events = await fetchEvents(org, startDate, endDate); - processedData = await parseEvents(events); + processedData = await parseEvents(events as IGitHubEvent[]); for (const user of Object.keys(processedData)) { if (!processedData[user]) { processedData[user] = { diff --git a/scraper/src/github-scraper/parseEvents.ts b/scraper/src/github-scraper/parseEvents.ts index 40574509..0c9bc4b1 100644 --- a/scraper/src/github-scraper/parseEvents.ts +++ b/scraper/src/github-scraper/parseEvents.ts @@ -8,7 +8,6 @@ import { calculateTurnaroundTime } from "./utils.js"; import { parseISO } from "date-fns"; import { isBlacklisted } from "./utils.js"; import { octokit } from "./config.js"; - const processedData: ProcessData = {}; function appendEvent(user: string, event: Activity) { @@ -136,7 +135,7 @@ export const parseEvents = async (events: IGitHubEvent[]) => { appendEvent(user, { type: "comment_created", title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), + time: eventTime?.toISOString(), link: event.payload.comment.html_url, text: event.payload.comment.body, }); @@ -146,7 +145,7 @@ export const parseEvents = async (events: IGitHubEvent[]) => { if (["opened", "assigned", "closed"].includes(event.payload.action)) { appendEvent(user, { type: `issue_${event.payload.action}`, - title: `${event.repo.name}#${event.payload.issue?.number}`, + title: `${event.repo.name}#${event.payload.issue.number}`, time: eventTime.toISOString(), link: event.payload.issue.html_url, text: event.payload.issue.title, @@ -170,7 +169,7 @@ export const parseEvents = async (events: IGitHubEvent[]) => { appendEvent(user, { type: "pr_merged", title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), + time: eventTime?.toISOString(), link: event.payload.pull_request.html_url, text: event.payload.pull_request.title, turnaround_time: turnaroundTime, @@ -181,7 +180,7 @@ export const parseEvents = async (events: IGitHubEvent[]) => { case "PullRequestReviewEvent": appendEvent(user, { type: "pr_reviewed", - time: eventTime.toISOString(), + time: eventTime?.toISOString(), title: `${event.repo.name}#${event.payload.pull_request.number}`, link: event.payload.review.html_url, text: event.payload.pull_request.title, diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index 332324f4..aacce91e 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -164,7 +164,7 @@ export interface OpenPr { link: string; title: string; stale_for: number; - labels: string[]; + labels: (string | undefined)[]; } export interface AuthoredIssueAndPr { diff --git a/scraper/src/github-scraper/utils.ts b/scraper/src/github-scraper/utils.ts index 31d81cf7..e2fdddc6 100644 --- a/scraper/src/github-scraper/utils.ts +++ b/scraper/src/github-scraper/utils.ts @@ -28,11 +28,10 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { `GET ${event.payload?.pull_request?.issue_url}`, ); // Fetch url all linked issues url from the response - // What if the issue was cross-referenced from another repository than the repository of the PR made also add this feature linkedIssues.push([event.repo.name, `#${linkedIssuesResponse.data.number}`]); // Fetch issue events to find cross-referenced issues - const issueEventsUrl = linkedIssuesResponse.data.events_url; + const issueEventsUrl = await linkedIssuesResponse.data.events_url; const issueEventsResponse = await octokit.request(`GET ${issueEventsUrl}`); type issueEvent = typeof issueEventsResponse.data; @@ -97,9 +96,8 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { issue_number: issueNumber, }, ); - - const issueTimeline = issueTimelineResponse.data; - issueTimeline.forEach((action: Action) => { + type Action = (typeof issueTimelineResponse.data)[0]; + issueTimelineResponse.data.forEach((action: Action) => { if (action.event === "assigned" && action.assignee.login === user) { assignedAts.push({ issue: `${org}/${repo}#${issueNumber}`, From cdda1488dd2bf86ba306d261eb2a0c58878ada49 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Tue, 2 Jul 2024 15:21:15 +0530 Subject: [PATCH 35/71] Modify types and remove all types error from scraper --- scraper/src/github-scraper/fetchUserData.ts | 2 +- scraper/src/github-scraper/index.ts | 5 +-- scraper/src/github-scraper/parseEvents.ts | 9 ++-- scraper/src/github-scraper/types.ts | 2 +- scraper/src/github-scraper/utils.ts | 49 +++++++++++---------- tests/github-data-schema.test.mjs | 1 - 6 files changed, 33 insertions(+), 35 deletions(-) diff --git a/scraper/src/github-scraper/fetchUserData.ts b/scraper/src/github-scraper/fetchUserData.ts index 95c90344..4d962ba2 100644 --- a/scraper/src/github-scraper/fetchUserData.ts +++ b/scraper/src/github-scraper/fetchUserData.ts @@ -52,7 +52,7 @@ export const fetchOpenPulls = async (user: string, org: string) => { link: pr.html_url, title: pr.title, stale_for: staleFor, - labels: pr.labels.map((label: { name: string }) => label.name), + labels: pr.labels.map((label: { name?: string }) => label.name), }; }); diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index 2c66845f..cc913d7e 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -1,6 +1,6 @@ import { formatISO, parseISO, startOfDay, subDays } from "date-fns"; -import { ProcessData } from "./types.js"; import { fetchMergeEvents, fetchOpenPulls } from "./fetchUserData.js"; +import { IGitHubEvent, ProcessData } from "./types.js"; import { fetchEvents } from "./fetchEvents.js"; import { parseEvents } from "./parseEvents.js"; import { mergedData } from "./saveData.js"; @@ -19,9 +19,8 @@ const scrapeGitHub = async ( console.log( `Scraping GitHub data for ${org} from ${formatISO(startDate)} to ${formatISO(endDate)}`, ); - const events = await fetchEvents(org, startDate, endDate); - processedData = await parseEvents(events); + processedData = await parseEvents(events as IGitHubEvent[]); for (const user of Object.keys(processedData)) { if (!processedData[user]) { processedData[user] = { diff --git a/scraper/src/github-scraper/parseEvents.ts b/scraper/src/github-scraper/parseEvents.ts index 40574509..0c9bc4b1 100644 --- a/scraper/src/github-scraper/parseEvents.ts +++ b/scraper/src/github-scraper/parseEvents.ts @@ -8,7 +8,6 @@ import { calculateTurnaroundTime } from "./utils.js"; import { parseISO } from "date-fns"; import { isBlacklisted } from "./utils.js"; import { octokit } from "./config.js"; - const processedData: ProcessData = {}; function appendEvent(user: string, event: Activity) { @@ -136,7 +135,7 @@ export const parseEvents = async (events: IGitHubEvent[]) => { appendEvent(user, { type: "comment_created", title: `${event.repo.name}#${event.payload.issue.number}`, - time: eventTime.toISOString(), + time: eventTime?.toISOString(), link: event.payload.comment.html_url, text: event.payload.comment.body, }); @@ -146,7 +145,7 @@ export const parseEvents = async (events: IGitHubEvent[]) => { if (["opened", "assigned", "closed"].includes(event.payload.action)) { appendEvent(user, { type: `issue_${event.payload.action}`, - title: `${event.repo.name}#${event.payload.issue?.number}`, + title: `${event.repo.name}#${event.payload.issue.number}`, time: eventTime.toISOString(), link: event.payload.issue.html_url, text: event.payload.issue.title, @@ -170,7 +169,7 @@ export const parseEvents = async (events: IGitHubEvent[]) => { appendEvent(user, { type: "pr_merged", title: `${event.repo.name}#${event.payload.pull_request.number}`, - time: eventTime.toISOString(), + time: eventTime?.toISOString(), link: event.payload.pull_request.html_url, text: event.payload.pull_request.title, turnaround_time: turnaroundTime, @@ -181,7 +180,7 @@ export const parseEvents = async (events: IGitHubEvent[]) => { case "PullRequestReviewEvent": appendEvent(user, { type: "pr_reviewed", - time: eventTime.toISOString(), + time: eventTime?.toISOString(), title: `${event.repo.name}#${event.payload.pull_request.number}`, link: event.payload.review.html_url, text: event.payload.pull_request.title, diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index 332324f4..aacce91e 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -164,7 +164,7 @@ export interface OpenPr { link: string; title: string; stale_for: number; - labels: string[]; + labels: (string | undefined)[]; } export interface AuthoredIssueAndPr { diff --git a/scraper/src/github-scraper/utils.ts b/scraper/src/github-scraper/utils.ts index 31d81cf7..84c95e9c 100644 --- a/scraper/src/github-scraper/utils.ts +++ b/scraper/src/github-scraper/utils.ts @@ -28,11 +28,10 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { `GET ${event.payload?.pull_request?.issue_url}`, ); // Fetch url all linked issues url from the response - // What if the issue was cross-referenced from another repository than the repository of the PR made also add this feature linkedIssues.push([event.repo.name, `#${linkedIssuesResponse.data.number}`]); // Fetch issue events to find cross-referenced issues - const issueEventsUrl = linkedIssuesResponse.data.events_url; + const issueEventsUrl = await linkedIssuesResponse.data.events_url; const issueEventsResponse = await octokit.request(`GET ${issueEventsUrl}`); type issueEvent = typeof issueEventsResponse.data; @@ -97,31 +96,33 @@ export async function calculateTurnaroundTime(event: PullRequestEvent) { issue_number: issueNumber, }, ); - - const issueTimeline = issueTimelineResponse.data; - issueTimeline.forEach((action: Action) => { - if (action.event === "assigned" && action.assignee.login === user) { - assignedAts.push({ - issue: `${org}/${repo}#${issueNumber}`, - time: parseISODate(action.created_at), - }); - } - - if (action.event === "unassigned" && action.assignee.login === user) { - assignedAts.pop(); + type IssueTimelineType = (typeof issueTimelineResponse.data)[0]; + issueTimelineResponse.data.forEach((action: IssueTimelineType) => { + if ("assignee" in action) { + if (action.event === "assigned" && action.assignee?.login === user) { + assignedAts.push({ + issue: `${org}/${repo}#${issueNumber}`, + time: parseISODate(new Date(action.created_at)), + }); + } + + if (action.event === "unassigned" && action.assignee?.login === user) { + assignedAts.pop(); + } } }); - } - const assignedAt: Date | null = - assignedAts.length === 0 - ? null - : assignedAts.reduce((min, current) => - current.time < min.time ? current : min, - ).time; - const turnaroundTime = - (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / 1000; - return turnaroundTime; + const assignedAt: Date | null = + assignedAts.length === 0 + ? null + : assignedAts.reduce((min, current) => + current.time < min.time ? current : min, + ).time; + const turnaroundTime = + (mergedAt.getTime() - (assignedAt || createdAt.getTime()).valueOf()) / + 1000; + return turnaroundTime; + } } export async function resolveAutonomyResponsibility( diff --git a/tests/github-data-schema.test.mjs b/tests/github-data-schema.test.mjs index 1a31f2b0..166583a4 100644 --- a/tests/github-data-schema.test.mjs +++ b/tests/github-data-schema.test.mjs @@ -18,7 +18,6 @@ use(chaiJsonSchema); const filesInDir = fs .readdirSync(GH_DATA) .filter((file) => path.extname(file) === ".json"); -console.log(filesInDir.length); filesInDir.forEach((file) => { const content = fs.readFileSync(join(GH_DATA, file)).toString(); From 6609994631403a9708ffc0a0d6ce752ee62c17a1 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 5 Jul 2024 17:04:47 +0530 Subject: [PATCH 36/71] Description added to discussion scraper --- scraper/src/github-scraper/discussion.ts | 2 ++ scraper/src/github-scraper/types.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scraper/src/github-scraper/discussion.ts b/scraper/src/github-scraper/discussion.ts index 3f7cbe44..ccc76896 100644 --- a/scraper/src/github-scraper/discussion.ts +++ b/scraper/src/github-scraper/discussion.ts @@ -18,6 +18,7 @@ query($org: String!, $cursor: String) { edges { node { title + body author { login } @@ -66,6 +67,7 @@ async function parseDiscussionData(allDiscussions: Discussion[]) { return { source: "github", title: d.node.title, + description: d.node.body, author: d.node.author.login, url: d.node.url, time: d.node.createdAt, diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index aacce91e..a2e05132 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -175,6 +175,7 @@ export interface AuthoredIssueAndPr { export type Discussion = { node: { title: string; + body: string; author: { login: string; }; @@ -199,6 +200,7 @@ export type Discussion = { export type ParsedDiscussion = { source: string; title: string; + description: string; author: string; url: string; time: string; From e3bb35d7b9e3191f3180b39bd3327a420e121370 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 5 Jul 2024 17:06:02 +0530 Subject: [PATCH 37/71] Description added to discussion scraper --- schemas/discussion-data.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/schemas/discussion-data.yaml b/schemas/discussion-data.yaml index 97da3401..3601a0a8 100644 --- a/schemas/discussion-data.yaml +++ b/schemas/discussion-data.yaml @@ -5,6 +5,7 @@ type: array required: - source - title + - description - author - url - category @@ -15,6 +16,8 @@ properties: type: string title: type: string + description: + type: string author: type: string url: From 93374847ba80b68b0e0f53a36ce3f0af2cfad21d Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 12 Jul 2024 14:23:38 +0530 Subject: [PATCH 38/71] Discussion UI created at home, disucssions and cotrnbutors profile route --- .env | 2 +- app/discussions/layout.tsx | 28 +++ app/discussions/page.tsx | 15 ++ app/issues/page.tsx | 6 - app/page.tsx | 22 ++- components/DscMarkdown.tsx | 36 ++++ components/contributors/GithubActivity.tsx | 32 ++- components/discussions/DiscussionMarkdown.tsx | 36 ++++ components/discussions/FilterDiscussions.tsx | 69 +++++++ components/discussions/GithubDiscussion.tsx | 183 ++++++++++++++++++ components/discussions/GithubDiscussions.tsx | 67 +++++++ components/navbar/Navbar.tsx | 1 + lib/api.ts | 29 ++- lib/discussion.ts | 56 ++++++ lib/types.ts | 5 +- lib/utils.ts | 3 +- scraper/src/github-scraper/discussion.ts | 11 +- scraper/src/github-scraper/index.ts | 4 +- scraper/src/github-scraper/types.ts | 14 +- 19 files changed, 593 insertions(+), 26 deletions(-) create mode 100644 app/discussions/layout.tsx create mode 100644 app/discussions/page.tsx create mode 100644 components/DscMarkdown.tsx create mode 100644 components/discussions/DiscussionMarkdown.tsx create mode 100644 components/discussions/FilterDiscussions.tsx create mode 100644 components/discussions/GithubDiscussion.tsx create mode 100644 components/discussions/GithubDiscussions.tsx create mode 100644 lib/discussion.ts diff --git a/.env b/.env index a4cbeb3d..eaa29b65 100644 --- a/.env +++ b/.env @@ -26,4 +26,4 @@ DATA_SOURCE="https://github.com/coronasafe/leaderboard-data.git" # SLACK_EOD_BOT_SIGNING_SECRET= ## -- Features -- ## -NEXT_PUBLIC_FEATURES=Leaderboard,Contributors,Feed,Releases,Projects +NEXT_PUBLIC_FEATURES=Leaderboard,Contributors,Feed,Releases,Projects,Discussions diff --git a/app/discussions/layout.tsx b/app/discussions/layout.tsx new file mode 100644 index 00000000..e29087f3 --- /dev/null +++ b/app/discussions/layout.tsx @@ -0,0 +1,28 @@ +import { Metadata } from "next"; +import { env } from "@/env.mjs"; +import { notFound } from "next/navigation"; +import { featureIsEnabled } from "@/lib/utils"; +import FilterDiscussions from "../../components/discussions/FilterDiscussions"; +import { categoriesMap } from "../../lib/discussion"; + +export const metadata: Metadata = { + title: `Disucssion | ${env.NEXT_PUBLIC_PAGE_TITLE}`, +}; + +export default function DiscussionsLayout({ + children, +}: { + children: React.ReactNode; +}) { + if (!featureIsEnabled("Discussions")) return notFound(); + const categories = Array.from(categoriesMap.values()); + return ( +
+
+

Disucssions

+ +
+ {children} +
+ ); +} diff --git a/app/discussions/page.tsx b/app/discussions/page.tsx new file mode 100644 index 00000000..42c2fb01 --- /dev/null +++ b/app/discussions/page.tsx @@ -0,0 +1,15 @@ +import { fetchGithubDiscussion } from "../../lib/discussion"; +import GithubDiscussions from "../../components/discussions/GithubDiscussions"; + +const page = async () => { + const discussions = await fetchGithubDiscussion(); + + return ( +
+ + {/* In future we have more discussion here */} +
+ ); +}; + +export default page; diff --git a/app/issues/page.tsx b/app/issues/page.tsx index 5975293f..5380fe1a 100644 --- a/app/issues/page.tsx +++ b/app/issues/page.tsx @@ -1,9 +1,3 @@ -import { env } from "@/env.mjs"; -import octokit from "@/lib/octokit"; -import Image from "next/image"; -import Link from "next/link"; -import { GoIssueOpened } from "react-icons/go"; -import Markdown from "@/components/Markdown"; import ActiveProjects from "../projects/ActiveProjects"; const page = async () => { diff --git a/app/page.tsx b/app/page.tsx index d0c4346a..4525b6a3 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -11,6 +11,8 @@ import { env } from "@/env.mjs"; import CommunityEngagemet from "@/app/CommunityEngagementSummary"; import { differenceInWeeks, parseISO } from "date-fns"; import { featureIsEnabled, formatDate } from "@/lib/utils"; +import GithubDiscussions from "../components/discussions/GithubDiscussions"; +import { fetchGithubDiscussion } from "../lib/discussion"; export default async function Home() { const contributors = (await getContributors()) @@ -21,7 +23,7 @@ export default async function Home() { .includes(contributor.role) ?? true, ) .sort((a, b) => b.weekSummary.points - a.weekSummary.points); - + const discussions = await fetchGithubDiscussion(5); const startDate = parseISO(env.NEXT_PUBLIC_ORG_START_DATE); return ( @@ -95,7 +97,23 @@ export default async function Home() {
)} - +
+
+
+

+ Discussions +

+ + More + + +
+ +
+
{featureIsEnabled("Projects") && (
diff --git a/components/DscMarkdown.tsx b/components/DscMarkdown.tsx new file mode 100644 index 00000000..d6d7960e --- /dev/null +++ b/components/DscMarkdown.tsx @@ -0,0 +1,36 @@ +"use client"; +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkRehype from "remark-rehype"; +import rehypeStringify from "rehype-stringify"; +import clsx from "clsx"; +import React, { useEffect, useState } from "react"; +export default function DscMarkdown(props: { + children: string; + className?: string; +}) { + const [processedContent, setProcessedContent] = useState(""); + + useEffect(() => { + const processMarkdown = async () => { + const result = await unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkRehype) + .use(rehypeStringify) + .process(props.children || ""); + + setProcessedContent(result.toString()); + }; + processMarkdown(); + }, [props.children]); + return ( +
+
+
+ ); +} diff --git a/components/contributors/GithubActivity.tsx b/components/contributors/GithubActivity.tsx index 3e7cca76..85d19698 100644 --- a/components/contributors/GithubActivity.tsx +++ b/components/contributors/GithubActivity.tsx @@ -3,11 +3,13 @@ import { ACTIVITY_TYPES, Activity, ActivityData } from "@/lib/types"; import { formatDuration, parseDateRangeSearchParam } from "@/lib/utils"; import OpenGraphImage from "../gh_events/OpenGraphImage"; -import { useMemo, useState } from "react"; +import { useState } from "react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import RelativeTime from "../RelativeTime"; import DateRangePicker from "../DateRangePicker"; import { format } from "date-fns"; +import GithubDiscussion from "../discussions/GithubDiscussion"; +import { IoIosChatboxes } from "react-icons/io"; let commentTypes = (activityEvent: string[]) => { switch (activityEvent[0]) { @@ -161,6 +163,31 @@ let renderText = (activity: Activity) => {
); + case "github_discussion": + return ( +
+
+

+ {activity.title}{" "} + + {activity["link"].split("/").slice(3, 5).join("/")} + + + {" "} + + +

+
+
+ {activity.discussion && ( + + )} +
+
+ ); default: return (
@@ -231,6 +258,8 @@ let icon = (type: string) => { /> ); + case "github_discussion": + return ; default: return ( diff --git a/components/discussions/DiscussionMarkdown.tsx b/components/discussions/DiscussionMarkdown.tsx new file mode 100644 index 00000000..18f02890 --- /dev/null +++ b/components/discussions/DiscussionMarkdown.tsx @@ -0,0 +1,36 @@ +"use client"; +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkGfm from "remark-gfm"; +import remarkRehype from "remark-rehype"; +import rehypeStringify from "rehype-stringify"; +import clsx from "clsx"; +import { useEffect, useState } from "react"; +export default function DiscussionMarkdown(props: { + children: string; + className?: string; +}) { + const [processedContent, setProcessedContent] = useState(""); + + useEffect(() => { + const processMarkdown = async () => { + const result = await unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkRehype) + .use(rehypeStringify) + .process(props.children || ""); + + setProcessedContent(result.toString()); + }; + processMarkdown(); + }, [props.children]); + return ( +
+
+
+ ); +} diff --git a/components/discussions/FilterDiscussions.tsx b/components/discussions/FilterDiscussions.tsx new file mode 100644 index 00000000..76b31f5b --- /dev/null +++ b/components/discussions/FilterDiscussions.tsx @@ -0,0 +1,69 @@ +"use client"; +import { useState } from "react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import DateRangePicker from "@/components/DateRangePicker"; +import { parseDateRangeSearchParam } from "@/lib/utils"; +import { format } from "date-fns"; + +interface params { + categories: { + name: string; + emoji: string; + }[]; +} + +const FilterDiscussions = ({ categories }: params) => { + const [selectedCategory, setSelectedCategory] = useState(""); + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + + const [start, end] = parseDateRangeSearchParam(searchParams.get("between")); + + const updateSearchParam = (key: string, value: string) => { + key === "category" && setSelectedCategory(value); + const current = new URLSearchParams(searchParams); + if (!value) { + current.delete(key); + } else { + current.set(key, value); + } + const search = current.toString(); + const query = search ? `?${search}` : ""; + router.replace(`${pathname}${query}`, { scroll: false }); + }; + + return ( +
+
+ +
+ { + updateSearchParam( + "between", + `${format(value.start, "yyyy-MM-dd")}...${format( + value.end, + "yyyy-MM-dd", + )}`, + ); + }} + /> +
+ ); +}; + +export default FilterDiscussions; diff --git a/components/discussions/GithubDiscussion.tsx b/components/discussions/GithubDiscussion.tsx new file mode 100644 index 00000000..bee4816e --- /dev/null +++ b/components/discussions/GithubDiscussion.tsx @@ -0,0 +1,183 @@ +"use client"; +import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; +import Image from "next/image"; +import Link from "next/link"; +import React, { useState } from "react"; +import { FaAngleDoubleDown, FaAngleDoubleUp } from "react-icons/fa"; +import RelativeTime from "../RelativeTime"; +import { env } from "@/env.mjs"; +import { FiGithub } from "react-icons/fi"; +import DiscussionMarkdown from "@/components/discussions/DiscussionMarkdown"; + +interface params { + discussion: ParsedDiscussion; + isProfilePage?: boolean; +} + +const GithubDiscussion = ({ discussion, isProfilePage = false }: params) => { + const [isFullDescription, setFullDescription] = useState(false); + const lengthOfDescription = isProfilePage ? 300 : 500; + const orgRepoName = discussion["link"].split("/").slice(3, 5).join("/"); + const repository = + orgRepoName === `orgs/${env.NEXT_PUBLIC_GITHUB_ORG}` ? "" : orgRepoName; + + return ( +
+ {/* Left side */} + {!isProfilePage && ( +
+ {/* Profile image */} + {`${discussion.author}'s + + {discussion.category?.emoji} + + {/* Vertical Line under Profile */} +
+ )} + + {/* Right side */} +
+ {/* Title and Time */} +
+
+ +

+ {discussion.title} +

+ + {!isProfilePage && ( +

+ + {discussion.author} + {" "} + started a discussion{" "} + {repository.length > 0 && ( + <> + in{" "} + + + {repository} + + + {repository.replace( + `${env.NEXT_PUBLIC_GITHUB_ORG}/`, + "", + )} + + + + )}{" "} + +

+ )} +
+ + + + Open in GitHub + +
+ + {/* Description */} +
+ {discussion.text.length > lengthOfDescription ? ( +
+ + {isFullDescription + ? discussion.text + : discussion.text.slice(0, lengthOfDescription)} + + +
+ ) : ( + {discussion.text} + )} +
+ + {/* Participants */} + {!isProfilePage && ( +
+

+ {discussion.participants && + discussion.participants.length > 0 && + "Participants"} +

+
+ {discussion.participants && + discussion.participants.length > 0 && + discussion.participants.slice(0, 5).map((participant) => ( + + {`${participant}'s + + ))} + {discussion.participants && + discussion.participants.length > 0 && ( + + {discussion.participants && + discussion.participants.length > 5 && + `${discussion.participants.length - 5} more...`} + + )} +
+
+ )} +
+
+ ); +}; + +export default GithubDiscussion; diff --git a/components/discussions/GithubDiscussions.tsx b/components/discussions/GithubDiscussions.tsx new file mode 100644 index 00000000..99999b69 --- /dev/null +++ b/components/discussions/GithubDiscussions.tsx @@ -0,0 +1,67 @@ +"use client"; +import GithubDiscussion from "@/components/discussions/GithubDiscussion"; +import { parseDateRangeSearchParam } from "@/lib/utils"; +import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; +import { useSearchParams } from "next/navigation"; +import { useState, useEffect } from "react"; + +interface Params { + discussions: ParsedDiscussion[]; + minimal?: boolean; +} + +const GithubDiscussions = ({ discussions }: Params) => { + const [filterDiscussions, setFilterDiscussions] = + useState(discussions); + + const filter = useSearchParams().get("category"); + const dateRange = useSearchParams().get("between"); + + useEffect(() => { + let filterData = discussions; + const [start, end] = parseDateRangeSearchParam(dateRange); + if (dateRange && filter) { + filterData = discussions.filter((discussion) => { + const discussionDate = new Date(discussion.time); + return ( + discussionDate >= new Date(start) && + discussionDate <= new Date(end) && + discussion.category?.name === filter + ); + }); + } else if (dateRange) { + filterData = discussions.filter((discussion) => { + const discussionDate = new Date(discussion.time); + return ( + discussionDate >= new Date(start) && discussionDate <= new Date(end) + ); + }); + } else if (filter) { + filterData = discussions.filter( + (discussion) => discussion.category?.name === filter, + ); + } + + setFilterDiscussions(filterData); + }, [filter, discussions, dateRange]); + + return ( +
+ {filterDiscussions.length > 0 ? ( + <> + {filterDiscussions.map( + (discussion: ParsedDiscussion, index: number) => ( + + ), + )} + + ) : ( +
+ No Discussions Found +
+ )} +
+ ); +}; + +export default GithubDiscussions; diff --git a/components/navbar/Navbar.tsx b/components/navbar/Navbar.tsx index b51823da..b15a17b1 100644 --- a/components/navbar/Navbar.tsx +++ b/components/navbar/Navbar.tsx @@ -16,6 +16,7 @@ const MenuItems = { "/feed": "Feed", "/releases": "Releases", "/issues": "Issues", + "/discussions": "Discussions", }; const availableMenuItems = Object.fromEntries( Object.entries(MenuItems).filter(([href, label]) => { diff --git a/lib/api.ts b/lib/api.ts index 2bebe85b..29bcff86 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -4,12 +4,14 @@ import { Activity, ActivityData, Contributor, Highlights } from "./types"; import { padZero } from "./utils"; import { readFile, readdir } from "fs/promises"; import { existsSync } from "fs"; +import { fetchGithubDiscussionForUser } from "@/lib/discussion"; +import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; const root = join(process.cwd(), "data-repo/contributors"); const slackRoot = join(process.cwd(), "data-repo/data/slack"); const githubRoot = join(process.cwd(), "data-repo/data/github"); -const points = { +const points: { [key: string]: number } = { comment_created: 1, issue_assigned: 1, pr_reviewed: 4, @@ -85,6 +87,24 @@ export async function getContributorsSlugs(): Promise<{ file: string }[]> { return contributorSlugs; } +async function getGithubDiscussions(githubHandle: string) { + const response = await fetchGithubDiscussionForUser(githubHandle); + const discussions = await response.map((discussion: ParsedDiscussion) => { + const title = + discussion.author === githubHandle + ? `Started a GitHub Discussion on` + : `Commented on a GitHub Discussion`; + return { + type: "github_discussion", + title: title, + time: discussion.time, + link: discussion.link, + discussion: discussion, + }; + }); + return discussions; +} + export async function getContributorBySlug(file: string, detail = false) { const fullPath = join(root, `${formatSlug(file)}.md`); const { data, content } = matter(await readFile(fullPath, "utf8")); @@ -97,6 +117,13 @@ export async function getContributorBySlug(file: string, detail = false) { activityData = JSON.parse( await readFile(join(githubRoot, `${githubHandle}.json`), "utf8"), ) as ActivityData; + // in activitydata need to add github discussion data + const discussions = await getGithubDiscussions(githubHandle); + // Add discussions to activityData in activity array + activityData = { + ...activityData, + activity: [...activityData.activity, ...discussions], + }; } catch (e) { activityData = { last_updated: undefined, diff --git a/lib/discussion.ts b/lib/discussion.ts new file mode 100644 index 00000000..36ddc6d7 --- /dev/null +++ b/lib/discussion.ts @@ -0,0 +1,56 @@ +import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; +import fs from "fs"; +import path, { join } from "path"; +import stripJsonComments from "strip-json-comments"; + +const cwd = process.cwd(); + +const GH_DATA = join( + cwd, + process.env.DATA_REPO ?? "data-repo", + "../data/github/discussions", +); +export const categoriesMap = new Map(); + +export async function fetchGithubDiscussion(noOfDiscussion?: number) { + const filesInDir = fs + .readdirSync(GH_DATA) + .filter((file) => path.extname(file) === ".json"); + + const discussions = filesInDir.map((file) => { + const content = fs.readFileSync(path.join(GH_DATA, file)).toString(); + return JSON.parse(stripJsonComments(content)); + }); + discussions[0].forEach((d: ParsedDiscussion) => { + if (d.category) { + const key = `${d.category.name}-${d.category.emoji}`; + if (!categoriesMap.has(key)) { + categoriesMap.set(key, d.category); + } + } + }); + + return noOfDiscussion + ? discussions[0].slice(0, noOfDiscussion) + : discussions[0]; +} + +export async function fetchGithubDiscussionForUser(user?: string) { + const filesInDir = fs + .readdirSync(GH_DATA) + .filter((file) => path.extname(file) === ".json"); + + const discussions = filesInDir.map((file) => { + const content = fs.readFileSync(path.join(GH_DATA, file)).toString(); + return JSON.parse(stripJsonComments(content)); + }); + + return ( + user && + discussions[0].filter( + (discussion: ParsedDiscussion) => + (discussion.participants ?? []).includes(user) || + discussion.author === user, + ) + ); +} diff --git a/lib/types.ts b/lib/types.ts index a9959c99..b50640c9 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,3 +1,4 @@ +import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; import { SORT_BY_OPTIONS, FILTER_BY_ROLE_OPTIONS, @@ -66,6 +67,7 @@ export const ACTIVITY_TYPES = [ "pr_opened", "pr_merged", "pr_collaborated", + "github_discussion", ] as const; export interface Activity { @@ -73,9 +75,10 @@ export interface Activity { title: string; time: string; link: string; - text: string; + text?: string; collaborated_with?: string[]; turnaround_time?: number; + discussion?: ParsedDiscussion; } export interface OpenPr { diff --git a/lib/utils.ts b/lib/utils.ts index f0785a11..464d6688 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -88,12 +88,13 @@ export const navLinks = [ { title: "People", path: "/people" }, { title: "Projects", path: "/projects" }, { title: "Releases", path: "/releases" }, + { title: "Discussion", path: "/discussion" }, ]; export const formatDate = (date: Date) => { return format(date, "MMM dd, yyyy"); }; -type Features = "Projects" | "Releases"; +type Features = "Projects" | "Releases" | "Discussions"; export const featureIsEnabled = (feature: Features) => { return env.NEXT_PUBLIC_FEATURES?.split(",").includes(feature); }; diff --git a/scraper/src/github-scraper/discussion.ts b/scraper/src/github-scraper/discussion.ts index ccc76896..8d5e91af 100644 --- a/scraper/src/github-scraper/discussion.ts +++ b/scraper/src/github-scraper/discussion.ts @@ -25,7 +25,7 @@ query($org: String!, $cursor: String) { url category{ name - emoji + emojiHTML } comments(first: 10) { edges { @@ -67,11 +67,14 @@ async function parseDiscussionData(allDiscussions: Discussion[]) { return { source: "github", title: d.node.title, - description: d.node.body, + text: d.node.body, author: d.node.author.login, - url: d.node.url, + link: d.node.url, time: d.node.createdAt, - category: d.node.category, + category: { + name: d.node.category.name, + emoji: d.node.category.emojiHTML.replace(/<\/?div>/g, ""), + }, participants, }; }); diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index cc913d7e..83186f5a 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -75,8 +75,8 @@ const main = async () => { console.error("Invalid date value:", dateArg); process.exit(1); } - await scrapeGitHub(orgName, date, Number(numDays), orgName); - await mergedData(dataDir, processedData); + // await scrapeGitHub(orgName, date, Number(numDays), orgName); + // await mergedData(dataDir, processedData); await fetchAllDiscussionEventsByOrg(orgName, dataDir); console.log("Done"); diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index a2e05132..4c68089c 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -182,7 +182,7 @@ export type Discussion = { url: string; category: { name: string; - emoji: string; + emojiHTML: string; }; comments: { edges: { @@ -198,15 +198,15 @@ export type Discussion = { }; export type ParsedDiscussion = { - source: string; + source?: string; title: string; - description: string; - author: string; - url: string; + text: string; + author?: string; + link: string; time: string; - category: { + category?: { name: string; emoji: string; }; - participants: string[]; + participants?: string[]; }; From 464efd61c83f54ee2253383babf082f11e39645b Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Fri, 12 Jul 2024 14:46:27 +0530 Subject: [PATCH 39/71] uncomment in scraper --- scraper/src/github-scraper/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scraper/src/github-scraper/index.ts b/scraper/src/github-scraper/index.ts index 83186f5a..cc913d7e 100644 --- a/scraper/src/github-scraper/index.ts +++ b/scraper/src/github-scraper/index.ts @@ -75,8 +75,8 @@ const main = async () => { console.error("Invalid date value:", dateArg); process.exit(1); } - // await scrapeGitHub(orgName, date, Number(numDays), orgName); - // await mergedData(dataDir, processedData); + await scrapeGitHub(orgName, date, Number(numDays), orgName); + await mergedData(dataDir, processedData); await fetchAllDiscussionEventsByOrg(orgName, dataDir); console.log("Done"); From 68f9b5e7d5412d5384c4b6ada33f6077effe474e Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Sat, 13 Jul 2024 11:56:41 +0530 Subject: [PATCH 40/71] Update Github Dicussions to Discussion --- components/contributors/GithubActivity.tsx | 21 +++++++++++++++------ lib/api.ts | 7 ++++--- lib/types.ts | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/components/contributors/GithubActivity.tsx b/components/contributors/GithubActivity.tsx index 85d19698..87d238c9 100644 --- a/components/contributors/GithubActivity.tsx +++ b/components/contributors/GithubActivity.tsx @@ -10,6 +10,7 @@ import DateRangePicker from "../DateRangePicker"; import { format } from "date-fns"; import GithubDiscussion from "../discussions/GithubDiscussion"; import { IoIosChatboxes } from "react-icons/io"; +import { env } from "@/env.mjs"; let commentTypes = (activityEvent: string[]) => { switch (activityEvent[0]) { @@ -163,15 +164,23 @@ let renderText = (activity: Activity) => {
); - case "github_discussion": + case "discussion": + const orgRepoName = activity["link"].split("/").slice(3, 5).join("/"); + const repository = + orgRepoName === `orgs/${env.NEXT_PUBLIC_GITHUB_ORG}` ? "" : orgRepoName; return (

{activity.title}{" "} - - {activity["link"].split("/").slice(3, 5).join("/")} - + {repository && ( + <> + in{" "} + + {repository} + + + )} {" "} @@ -258,7 +267,7 @@ let icon = (type: string) => { /> ); - case "github_discussion": + case "discussion": return ; default: return ( @@ -509,7 +518,7 @@ export const ActivityCheckbox = (props: { pr_merged: "PR merged", pr_opened: "PR opened", pr_reviewed: "Code Review", - github_discussion: "GitHub Discussion", + discussion: "Discussion", }[props.type] } diff --git a/lib/api.ts b/lib/api.ts index 29bcff86..e4f981ad 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -6,6 +6,7 @@ import { readFile, readdir } from "fs/promises"; import { existsSync } from "fs"; import { fetchGithubDiscussionForUser } from "@/lib/discussion"; import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; +import { env } from "@/env.mjs"; const root = join(process.cwd(), "data-repo/contributors"); const slackRoot = join(process.cwd(), "data-repo/data/slack"); @@ -92,10 +93,10 @@ async function getGithubDiscussions(githubHandle: string) { const discussions = await response.map((discussion: ParsedDiscussion) => { const title = discussion.author === githubHandle - ? `Started a GitHub Discussion on` - : `Commented on a GitHub Discussion`; + ? `Started a Discussion` + : `Commented on a Discussion`; return { - type: "github_discussion", + type: "discussion", title: title, time: discussion.time, link: discussion.link, diff --git a/lib/types.ts b/lib/types.ts index b50640c9..1dd198a0 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -67,7 +67,7 @@ export const ACTIVITY_TYPES = [ "pr_opened", "pr_merged", "pr_collaborated", - "github_discussion", + "discussion", ] as const; export interface Activity { From 9d1c1025c60b0ed4e6978c96e1f47a1790aea439 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Tue, 16 Jul 2024 10:54:23 +0530 Subject: [PATCH 41/71] Point mechanism for discussions and responsiveness added --- app/discussions/layout.tsx | 6 +- app/discussions/page.tsx | 11 +- app/globals.css | 9 ++ app/leaderboard/page.tsx | 2 +- app/page.tsx | 46 ++++--- components/contributors/GithubActivity.tsx | 32 +++-- .../discussions/DiscussionLeaderboard.tsx | 82 ++++++++++++ components/discussions/DiscussionMarkdown.tsx | 36 ----- components/discussions/FilterDiscussions.tsx | 1 - components/discussions/GithubDiscussion.tsx | 78 +++++------ components/discussions/GithubDiscussions.tsx | 74 +++++------ lib/api.ts | 123 +++++++++++++++--- lib/const.ts | 3 + lib/discussion.ts | 33 ++--- lib/types.ts | 12 +- lib/utils.ts | 18 +++ schemas/discussion-data.yaml | 3 + scraper/src/github-scraper/discussion.ts | 57 ++++---- scraper/src/github-scraper/types.ts | 3 +- 19 files changed, 403 insertions(+), 226 deletions(-) create mode 100644 components/discussions/DiscussionLeaderboard.tsx delete mode 100644 components/discussions/DiscussionMarkdown.tsx diff --git a/app/discussions/layout.tsx b/app/discussions/layout.tsx index e29087f3..c949f902 100644 --- a/app/discussions/layout.tsx +++ b/app/discussions/layout.tsx @@ -4,6 +4,7 @@ import { notFound } from "next/navigation"; import { featureIsEnabled } from "@/lib/utils"; import FilterDiscussions from "../../components/discussions/FilterDiscussions"; import { categoriesMap } from "../../lib/discussion"; +import DiscussionLeaderboard from "../../components/discussions/DiscussionLeaderboard"; export const metadata: Metadata = { title: `Disucssion | ${env.NEXT_PUBLIC_PAGE_TITLE}`, @@ -22,7 +23,10 @@ export default function DiscussionsLayout({

Disucssions

- {children} +
+ {children} + +
); } diff --git a/app/discussions/page.tsx b/app/discussions/page.tsx index 42c2fb01..6f25aede 100644 --- a/app/discussions/page.tsx +++ b/app/discussions/page.tsx @@ -1,14 +1,15 @@ import { fetchGithubDiscussion } from "../../lib/discussion"; import GithubDiscussions from "../../components/discussions/GithubDiscussions"; -const page = async () => { +interface Params { + searchParams: { [key: string]: string }; +} + +const page = async ({ searchParams }: Params) => { const discussions = await fetchGithubDiscussion(); return ( -
- - {/* In future we have more discussion here */} -
+ ); }; diff --git a/app/globals.css b/app/globals.css index d2cd20c5..1140b5a4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -130,3 +130,12 @@ html.dark { transform: translateX(0px); transform: translateY(0px); } + +.prose-for-h2-markdown :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)){ + color: var(--tw-prose-headings); + font-weight: 700; + font-size: 1.5em; + margin-top: 0.75em; + margin-bottom: 1em; + line-height: 1.3333333; +} diff --git a/app/leaderboard/page.tsx b/app/leaderboard/page.tsx index 3517d249..19e3a1d6 100644 --- a/app/leaderboard/page.tsx +++ b/app/leaderboard/page.tsx @@ -8,7 +8,7 @@ export default async function LeaderboardPage({ searchParams }: PageProps) { const keyString = `search=${searchParams?.search}`; return (
-
+
)} -
-
-
-

- Discussions -

- - More - - + {featureIsEnabled("Discussions") && ( +
+
+
+

+ Discussions +

+ + More + + +
+ {discussions.map( + (discussion: ParsedDiscussion, index: number) => { + return ( +
+ +
+ ); + }, + )}
-
-
+ )} {featureIsEnabled("Projects") && (
diff --git a/components/contributors/GithubActivity.tsx b/components/contributors/GithubActivity.tsx index 87d238c9..73a21bda 100644 --- a/components/contributors/GithubActivity.tsx +++ b/components/contributors/GithubActivity.tsx @@ -1,7 +1,11 @@ "use client"; import { ACTIVITY_TYPES, Activity, ActivityData } from "@/lib/types"; -import { formatDuration, parseDateRangeSearchParam } from "@/lib/utils"; +import { + formatDuration, + parseDateRangeSearchParam, + parseOrgRepoFromURL, +} from "@/lib/utils"; import OpenGraphImage from "../gh_events/OpenGraphImage"; import { useState } from "react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; @@ -10,7 +14,7 @@ import DateRangePicker from "../DateRangePicker"; import { format } from "date-fns"; import GithubDiscussion from "../discussions/GithubDiscussion"; import { IoIosChatboxes } from "react-icons/io"; -import { env } from "@/env.mjs"; +import Link from "next/link"; let commentTypes = (activityEvent: string[]) => { switch (activityEvent[0]) { @@ -164,10 +168,11 @@ let renderText = (activity: Activity) => {
); - case "discussion": - const orgRepoName = activity["link"].split("/").slice(3, 5).join("/"); - const repository = - orgRepoName === `orgs/${env.NEXT_PUBLIC_GITHUB_ORG}` ? "" : orgRepoName; + case "discussion_answered": + case "discussion_comment_created": + case "discussion_created": + const { org, repository } = parseOrgRepoFromURL(activity.link); + return (
@@ -176,9 +181,14 @@ let renderText = (activity: Activity) => { {repository && ( <> in{" "} - - {repository} - + + + {org}/{repository} + + )} @@ -518,7 +528,9 @@ export const ActivityCheckbox = (props: { pr_merged: "PR merged", pr_opened: "PR opened", pr_reviewed: "Code Review", - discussion: "Discussion", + discussion_comment_created: " Commented on Discussion", + discussion_created: "Discussion started", + discussion_answered: "Discussion Answered", }[props.type] } diff --git a/components/discussions/DiscussionLeaderboard.tsx b/components/discussions/DiscussionLeaderboard.tsx new file mode 100644 index 00000000..9b23b813 --- /dev/null +++ b/components/discussions/DiscussionLeaderboard.tsx @@ -0,0 +1,82 @@ +import { getContributors } from "@/lib/api"; +import Link from "next/link"; + +export async function calculateContributor() { + const contributors = await getContributors(); + const contributorsMap = new Map(); + + // If we have contributors then we will calculate the top contributors and save it in the map like object {name, points, githubHandle} + // points = 1 for each comment + 2 for creating a discussion + 5 discussion marked as helpful + if (contributors) { + contributors.forEach((contributor) => { + const existingInfo = contributorsMap.get(contributor.name) || { + points: 0, + githubHandle: contributor.slug, + }; + const points = + existingInfo.points + contributor.highlights.discussion_answered || + contributor.highlights.discussion_created || + contributor.highlights.discussion_comment_created || + 0; + + const contributorInfo = { + points: points, + githubHandle: contributor.slug, + }; + + contributorsMap.set(contributor.name, contributorInfo); + }); + } + + return Array.from(contributorsMap, ([name, { points, githubHandle }]) => ({ + name, + points, + githubHandle, + })); +} + +const DiscussionLeaderboard = async () => { + const contributors = await calculateContributor(); + + return ( + <> +
+
+

Most Helpful

+
+ +
+ {contributors + .filter((contributor) => contributor.points > 0) + .sort((a, b) => b.points - a.points) + .slice(0, 3) + .map((contributor, index) => ( + + + + {index + 1} + + + {contributor.name} + + + + ))} +
+
+ + Show More + +
+
+ + ); +}; + +export default DiscussionLeaderboard; diff --git a/components/discussions/DiscussionMarkdown.tsx b/components/discussions/DiscussionMarkdown.tsx deleted file mode 100644 index 18f02890..00000000 --- a/components/discussions/DiscussionMarkdown.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; -import remarkRehype from "remark-rehype"; -import rehypeStringify from "rehype-stringify"; -import clsx from "clsx"; -import { useEffect, useState } from "react"; -export default function DiscussionMarkdown(props: { - children: string; - className?: string; -}) { - const [processedContent, setProcessedContent] = useState(""); - - useEffect(() => { - const processMarkdown = async () => { - const result = await unified() - .use(remarkParse) - .use(remarkGfm) - .use(remarkRehype) - .use(rehypeStringify) - .process(props.children || ""); - - setProcessedContent(result.toString()); - }; - processMarkdown(); - }, [props.children]); - return ( -
-
-
- ); -} diff --git a/components/discussions/FilterDiscussions.tsx b/components/discussions/FilterDiscussions.tsx index 76b31f5b..8a4e4f28 100644 --- a/components/discussions/FilterDiscussions.tsx +++ b/components/discussions/FilterDiscussions.tsx @@ -21,7 +21,6 @@ const FilterDiscussions = ({ categories }: params) => { const [start, end] = parseDateRangeSearchParam(searchParams.get("between")); const updateSearchParam = (key: string, value: string) => { - key === "category" && setSelectedCategory(value); const current = new URLSearchParams(searchParams); if (!value) { current.delete(key); diff --git a/components/discussions/GithubDiscussion.tsx b/components/discussions/GithubDiscussion.tsx index bee4816e..16819a5c 100644 --- a/components/discussions/GithubDiscussion.tsx +++ b/components/discussions/GithubDiscussion.tsx @@ -1,31 +1,33 @@ -"use client"; import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; import Image from "next/image"; import Link from "next/link"; -import React, { useState } from "react"; -import { FaAngleDoubleDown, FaAngleDoubleUp } from "react-icons/fa"; import RelativeTime from "../RelativeTime"; -import { env } from "@/env.mjs"; import { FiGithub } from "react-icons/fi"; -import DiscussionMarkdown from "@/components/discussions/DiscussionMarkdown"; +import { parseOrgRepoFromURL } from "@/lib/utils"; +import Markdown from "../Markdown"; +import { FaAnglesRight } from "react-icons/fa6"; -interface params { +interface Props { discussion: ParsedDiscussion; + minimal?: boolean; isProfilePage?: boolean; } -const GithubDiscussion = ({ discussion, isProfilePage = false }: params) => { - const [isFullDescription, setFullDescription] = useState(false); +const GithubDiscussion = ({ + discussion, + minimal = false, + isProfilePage = false, +}: Props) => { const lengthOfDescription = isProfilePage ? 300 : 500; - const orgRepoName = discussion["link"].split("/").slice(3, 5).join("/"); - const repository = - orgRepoName === `orgs/${env.NEXT_PUBLIC_GITHUB_ORG}` ? "" : orgRepoName; + const { org, repository } = parseOrgRepoFromURL(discussion.link); return ( -
+
{/* Left side */} {!isProfilePage && ( -
+
{/* Profile image */} { {discussion.category?.emoji} - {/* Vertical Line under Profile */}
@@ -48,8 +49,8 @@ const GithubDiscussion = ({ discussion, isProfilePage = false }: params) => { {/* Right side */}
{/* Title and Time */} -
-
+
+

{discussion.title} @@ -73,13 +74,10 @@ const GithubDiscussion = ({ discussion, isProfilePage = false }: params) => { target="_blank" > - {repository} + {org}/{repository} - {repository.replace( - `${env.NEXT_PUBLIC_GITHUB_ORG}/`, - "", - )} + {repository.replace(`${org}/`, "")} @@ -103,34 +101,24 @@ const GithubDiscussion = ({ discussion, isProfilePage = false }: params) => {

{discussion.text.length > lengthOfDescription ? ( -
- - {isFullDescription - ? discussion.text - : discussion.text.slice(0, lengthOfDescription)} - - + Read More +
) : ( - {discussion.text} + + {discussion.text} + )}
diff --git a/components/discussions/GithubDiscussions.tsx b/components/discussions/GithubDiscussions.tsx index 99999b69..e5c6fa86 100644 --- a/components/discussions/GithubDiscussions.tsx +++ b/components/discussions/GithubDiscussions.tsx @@ -1,59 +1,43 @@ -"use client"; import GithubDiscussion from "@/components/discussions/GithubDiscussion"; import { parseDateRangeSearchParam } from "@/lib/utils"; import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; -import { useSearchParams } from "next/navigation"; -import { useState, useEffect } from "react"; -interface Params { +interface Props { discussions: ParsedDiscussion[]; - minimal?: boolean; + searchParams?: { [key: string]: string }; } -const GithubDiscussions = ({ discussions }: Params) => { - const [filterDiscussions, setFilterDiscussions] = - useState(discussions); +const GithubDiscussions = ({ discussions, searchParams }: Props) => { + const category = searchParams?.category; + const dateRange = searchParams?.between; + const [start, end] = parseDateRangeSearchParam(dateRange); - const filter = useSearchParams().get("category"); - const dateRange = useSearchParams().get("between"); - - useEffect(() => { - let filterData = discussions; - const [start, end] = parseDateRangeSearchParam(dateRange); - if (dateRange && filter) { - filterData = discussions.filter((discussion) => { - const discussionDate = new Date(discussion.time); - return ( - discussionDate >= new Date(start) && - discussionDate <= new Date(end) && - discussion.category?.name === filter - ); - }); - } else if (dateRange) { - filterData = discussions.filter((discussion) => { - const discussionDate = new Date(discussion.time); - return ( - discussionDate >= new Date(start) && discussionDate <= new Date(end) - ); - }); - } else if (filter) { - filterData = discussions.filter( - (discussion) => discussion.category?.name === filter, - ); - } - - setFilterDiscussions(filterData); - }, [filter, discussions, dateRange]); + if (category && dateRange) { + discussions = discussions.filter( + (discussion) => + new Date(discussion.time) >= new Date(start) && + new Date(discussion.time) <= new Date(end) && + discussion.category?.name === category, + ); + } else if (category) { + discussions = discussions.filter( + (discussion) => discussion.category?.name === category, + ); + } else if (dateRange) { + discussions = discussions.filter( + (discussion) => + new Date(discussion.time) >= new Date(start) && + new Date(discussion.time) <= new Date(end), + ); + } return ( -
- {filterDiscussions.length > 0 ? ( +
+ {discussions.length > 0 ? ( <> - {filterDiscussions.map( - (discussion: ParsedDiscussion, index: number) => ( - - ), - )} + {discussions.map((discussion: ParsedDiscussion, index: number) => ( + + ))} ) : (
diff --git a/lib/api.ts b/lib/api.ts index e4f981ad..95c7afae 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -1,18 +1,19 @@ import { join } from "path"; import matter from "gray-matter"; import { Activity, ActivityData, Contributor, Highlights } from "./types"; -import { padZero } from "./utils"; +import { padZero, parseOrgRepoFromURL } from "./utils"; import { readFile, readdir } from "fs/promises"; import { existsSync } from "fs"; -import { fetchGithubDiscussionForUser } from "@/lib/discussion"; +import { fetchGithubDiscussion } from "@/lib/discussion"; import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; import { env } from "@/env.mjs"; +import octokit from "./octokit"; const root = join(process.cwd(), "data-repo/contributors"); const slackRoot = join(process.cwd(), "data-repo/data/slack"); const githubRoot = join(process.cwd(), "data-repo/data/github"); -const points: { [key: string]: number } = { +const points = { comment_created: 1, issue_assigned: 1, pr_reviewed: 4, @@ -22,6 +23,9 @@ const points: { [key: string]: number } = { pr_merged: 7, pr_collaborated: 2, issue_closed: 0, + discussion_created: 2, + discussion_answered: 5, + discussion_comment_created: 1, }; // Comments will get a single point // Picking up an issue would get a point @@ -87,22 +91,74 @@ export async function getContributorsSlugs(): Promise<{ file: string }[]> { } return contributorSlugs; } - -async function getGithubDiscussions(githubHandle: string) { - const response = await fetchGithubDiscussionForUser(githubHandle); - const discussions = await response.map((discussion: ParsedDiscussion) => { - const title = - discussion.author === githubHandle - ? `Started a Discussion` - : `Commented on a Discussion`; - return { - type: "discussion", - title: title, - time: discussion.time, - link: discussion.link, - discussion: discussion, +async function checkAnsweredByUser( + github: string, + number: string, + repoName: string, +) { + const org = env.NEXT_PUBLIC_GITHUB_ORG; + interface Dicussion { + repository: { + discussion: { + answer: { + author: { + login: string; + }; + }; + }; }; - }); + } + const dicussion: Dicussion = await octokit.graphql(`query { + repository(owner: "${org}", name: "${repoName}") { + discussion (number: ${number}) { + answer { + author { + login + } + } + } + } + }`); + if (dicussion.repository.discussion.answer !== null) { + return dicussion.repository.discussion.answer.author.login === github; + } else return false; +} +async function getGithubDiscussions(githubHandle: string) { + const response = await fetchGithubDiscussion(null, githubHandle); + + const discussions = await Promise.all( + response.map(async (discussion: ParsedDiscussion) => { + const isAuthor = discussion.author === githubHandle; + let title, activityType; + + if (isAuthor) { + title = "Started a Discussion"; + activityType = "discussion_created"; + } else { + const isAnswered = await checkAnsweredByUser( + githubHandle, + discussion.link?.split("/").pop() ?? "", + discussion.repoName, + ); + title = isAnswered + ? "Answered a Discussion" + : "Commented on a Discussion"; + activityType = isAnswered + ? "discussion_answered" + : "discussion_comment_created"; + } + + return { + type: activityType, + title: title, + time: discussion.time, + link: discussion.link, + text: "", + discussion: discussion, + }; + }), + ); + return discussions; } @@ -120,6 +176,7 @@ export async function getContributorBySlug(file: string, detail = false) { ) as ActivityData; // in activitydata need to add github discussion data const discussions = await getGithubDiscussions(githubHandle); + // Add discussions to activityData in activity array activityData = { ...activityData, @@ -147,9 +204,9 @@ export async function getContributorBySlug(file: string, detail = false) { return { activity: [ ...acc.activity, - { ...activity, points: points[activity.type] || 0 }, + { ...activity, points: points[activity.type] }, ], - points: acc.points + (points[activity.type] || 0), + points: acc.points + points[activity.type], comment_created: acc.comment_created + (activity.type === "comment_created" ? 1 : 0), eod_update: acc.eod_update + (activity.type === "eod_update" ? 1 : 0), @@ -163,6 +220,15 @@ export async function getContributorBySlug(file: string, detail = false) { acc.issue_assigned + (activity.type === "issue_assigned" ? 1 : 0), issue_opened: acc.issue_opened + (activity.type === "issue_opened" ? 1 : 0), + discussion_created: + acc.discussion_created + + (activity.type === "discussion_created" ? 2 : 0), + discussion_answered: + acc.discussion_answered + + (activity.type === "discussion_answered" ? 5 : 0), + discussion_comment_created: + acc.discussion_comment_created + + (activity.type === "discussion_comment_created" ? 1 : 0), }; }, { @@ -176,6 +242,9 @@ export async function getContributorBySlug(file: string, detail = false) { pr_reviewed: 0, issue_assigned: 0, issue_opened: 0, + discussion_created: 0, + discussion_answered: 0, + discussion_comment_created: 0, } as Highlights & { activity: Activity[] }, ); @@ -213,6 +282,9 @@ export async function getContributorBySlug(file: string, detail = false) { pr_collaborated: weightedActivity.pr_collaborated, issue_assigned: weightedActivity.issue_assigned, issue_opened: weightedActivity.issue_opened, + discussion_created: weightedActivity.discussion_created, + discussion_answered: weightedActivity.discussion_answered, + discussion_comment_created: weightedActivity.discussion_comment_created, }, weekSummary: getLastWeekHighlights(calendarData), summarize, @@ -289,6 +361,9 @@ const HIGHLIGHT_KEYS = [ "pr_collaborated", "issue_assigned", "issue_opened", + "discussion_created", + "discussion_answered", + "discussion_comment_created", ] as const; const computePoints = ( @@ -311,6 +386,11 @@ const HighlightsReducer = (acc: Highlights, day: Highlights) => { pr_collaborated: acc.pr_collaborated + (day.pr_collaborated ?? 0), issue_assigned: acc.issue_assigned + (day.issue_assigned ?? 0), issue_opened: acc.issue_opened + (day.issue_opened ?? 0), + discussion_created: acc.discussion_created + (day.discussion_created ?? 0), + discussion_answered: + acc.discussion_answered + (day.discussion_answered ?? 0), + discussion_comment_created: + acc.discussion_comment_created + (day.discussion_comment_created ?? 0), }; }; @@ -324,6 +404,9 @@ const HighlightsInitialValue = { pr_collaborated: 0, issue_assigned: 0, issue_opened: 0, + discussion_created: 0, + discussion_answered: 0, + discussion_comment_created: 0, } as Highlights; const getLastWeekHighlights = (calendarData: Highlights[]) => { diff --git a/lib/const.ts b/lib/const.ts index 5d6a5147..0a3d778f 100644 --- a/lib/const.ts +++ b/lib/const.ts @@ -8,6 +8,9 @@ export const SORT_BY_OPTIONS = { pr_opened: "PR Opened", pr_reviewed: "PR Reviewed", pr_stale: "Stale PRs", + discussion_answered: "Discussion Answered", + discussion_comment_created: "Commented on discussion", + discussion_created: "Discussion Created", } as const; export const FILTER_BY_ROLE_OPTIONS = { diff --git a/lib/discussion.ts b/lib/discussion.ts index 36ddc6d7..82ef7f6f 100644 --- a/lib/discussion.ts +++ b/lib/discussion.ts @@ -12,7 +12,10 @@ const GH_DATA = join( ); export const categoriesMap = new Map(); -export async function fetchGithubDiscussion(noOfDiscussion?: number) { +export async function fetchGithubDiscussion( + noOfDiscussion?: number | null, + user?: string, +) { const filesInDir = fs .readdirSync(GH_DATA) .filter((file) => path.extname(file) === ".json"); @@ -30,27 +33,15 @@ export async function fetchGithubDiscussion(noOfDiscussion?: number) { } }); - return noOfDiscussion - ? discussions[0].slice(0, noOfDiscussion) - : discussions[0]; -} - -export async function fetchGithubDiscussionForUser(user?: string) { - const filesInDir = fs - .readdirSync(GH_DATA) - .filter((file) => path.extname(file) === ".json"); - - const discussions = filesInDir.map((file) => { - const content = fs.readFileSync(path.join(GH_DATA, file)).toString(); - return JSON.parse(stripJsonComments(content)); - }); - - return ( - user && - discussions[0].filter( + if (user) { + return discussions[0].filter( (discussion: ParsedDiscussion) => (discussion.participants ?? []).includes(user) || discussion.author === user, - ) - ); + ); + } + + return noOfDiscussion + ? discussions[0].slice(0, noOfDiscussion) + : discussions[0]; } diff --git a/lib/types.ts b/lib/types.ts index 1dd198a0..a695300b 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -43,6 +43,9 @@ export interface Highlights { pr_collaborated: number; issue_assigned: number; issue_opened: number; + discussion_created: number; + discussion_answered: number; + discussion_comment_created: number; } export interface WeekSummary { @@ -55,6 +58,9 @@ export interface WeekSummary { pr_collaborated: number; issue_assigned: number; issue_opened: number; + discussion_created: number; + discussion_answered: number; + discussion_comment_created: number; } export const ACTIVITY_TYPES = [ @@ -67,7 +73,9 @@ export const ACTIVITY_TYPES = [ "pr_opened", "pr_merged", "pr_collaborated", - "discussion", + "discussion_created", + "discussion_comment_created", + "discussion_answered", ] as const; export interface Activity { @@ -75,7 +83,7 @@ export interface Activity { title: string; time: string; link: string; - text?: string; + text: string; collaborated_with?: string[]; turnaround_time?: number; discussion?: ParsedDiscussion; diff --git a/lib/utils.ts b/lib/utils.ts index 464d6688..88c77ca0 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -98,3 +98,21 @@ type Features = "Projects" | "Releases" | "Discussions"; export const featureIsEnabled = (feature: Features) => { return env.NEXT_PUBLIC_FEATURES?.split(",").includes(feature); }; + +export const parseOrgRepoFromURL = (url: string) => { + const parts = url.split("/"); + + let org, repository; + + if (parts[3] === "orgs") { + // Handle the URL format: /orgs/{org}/discussions/{id} + org = parts[4]; + repository = ""; + } else { + // Handle the URL format: /{org}/{repo}/discussions/{id} + org = parts[3]; + repository = parts[4] === org ? "" : parts[4]; + } + + return { org, repository }; +}; diff --git a/schemas/discussion-data.yaml b/schemas/discussion-data.yaml index 3601a0a8..ca89a6e3 100644 --- a/schemas/discussion-data.yaml +++ b/schemas/discussion-data.yaml @@ -10,6 +10,7 @@ required: - url - category - time + - repoName properties: source: @@ -38,4 +39,6 @@ properties: type: array items: type: string + repoName: + type: string diff --git a/scraper/src/github-scraper/discussion.ts b/scraper/src/github-scraper/discussion.ts index 8d5e91af..8d46c669 100644 --- a/scraper/src/github-scraper/discussion.ts +++ b/scraper/src/github-scraper/discussion.ts @@ -52,32 +52,45 @@ async function fetchDiscussionsForOrg(org: string, cursor = null) { const response = await octokit.graphql.paginate(query, variables); type Edge = typeof response.organization.repositories.edges; + // const discussions = response.organization.repositories.edges.map( + // (edge: Edge) => edge.node.discussions.edges, + // ); + + // return discussions.flat(); const discussions = response.organization.repositories.edges.map( - (edge: Edge) => edge.node.discussions.edges, + (edge: Edge) => ({ + repoName: edge.node.name, + discussions: edge.node.discussions.edges, + }), ); - - return discussions.flat(); + return discussions; } -async function parseDiscussionData(allDiscussions: Discussion[]) { - const parsedDiscussions: ParsedDiscussion[] = allDiscussions.map((d) => { - const participants = Array.from( - new Set(d.node.comments.edges.map((c) => c.node.author.login)), - ); - return { - source: "github", - title: d.node.title, - text: d.node.body, - author: d.node.author.login, - link: d.node.url, - time: d.node.createdAt, - category: { - name: d.node.category.name, - emoji: d.node.category.emojiHTML.replace(/<\/?div>/g, ""), - }, - participants, - }; - }); +async function parseDiscussionData( + allDiscussions: { repoName: string; discussions: Discussion[] }[], +) { + const parsedDiscussions: ParsedDiscussion[] = allDiscussions.flatMap((repo) => + repo.discussions.map((d) => { + const participants = Array.from( + new Set(d.node.comments.edges.map((c) => c.node.author.login)), + ); + return { + source: "github", + title: d.node.title, + text: d.node.body, + author: d.node.author.login, + link: d.node.url, + time: d.node.createdAt, + category: { + name: d.node.category.name, + emoji: d.node.category.emojiHTML.replace(/<\/?div>/g, ""), + }, + participants, + repoName: repo.repoName, + }; + }), + ); + return parsedDiscussions; } diff --git a/scraper/src/github-scraper/types.ts b/scraper/src/github-scraper/types.ts index 4c68089c..977a911e 100644 --- a/scraper/src/github-scraper/types.ts +++ b/scraper/src/github-scraper/types.ts @@ -201,7 +201,7 @@ export type ParsedDiscussion = { source?: string; title: string; text: string; - author?: string; + author: string; link: string; time: string; category?: { @@ -209,4 +209,5 @@ export type ParsedDiscussion = { emoji: string; }; participants?: string[]; + repoName: string; }; From 59cddcf57b4b8400da6f8623cf808a93f04a1eed Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Tue, 16 Jul 2024 11:04:39 +0530 Subject: [PATCH 42/71] Site map updated for gh-discussion --- app/contributors/sitemap.ts | 2 +- app/sitemap.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/contributors/sitemap.ts b/app/contributors/sitemap.ts index 80ff7024..ed7dacde 100644 --- a/app/contributors/sitemap.ts +++ b/app/contributors/sitemap.ts @@ -1,5 +1,5 @@ import { sitemapEntry } from "@/app/sitemap"; -import { getContributors, getContributorsSlugs } from "@/lib/api"; +import { getContributors } from "@/lib/api"; import { MetadataRoute } from "next"; export default async function sitemap(): Promise { diff --git a/app/sitemap.ts b/app/sitemap.ts index dd801617..016aeebb 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -24,6 +24,7 @@ export default function sitemap(): MetadataRoute.Sitemap { entry("/releases"), entry("/leaderboard"), entry("/issues"), + entry("/discussions"), entry("/feed", { changeFrequency: "always", priority: 0.7 }), ]; } From a52d61e8cd66e60802db6e455ebeaf1da6625222 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Tue, 16 Jul 2024 11:31:47 +0530 Subject: [PATCH 43/71] type error fix in api.ts and modify logic of leaderboard for discussions --- app/api/leaderboard/functions.ts | 11 +++++++++++ lib/api.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/api/leaderboard/functions.ts b/app/api/leaderboard/functions.ts index e5066f2b..8572a068 100644 --- a/app/api/leaderboard/functions.ts +++ b/app/api/leaderboard/functions.ts @@ -26,6 +26,17 @@ export const getLeaderboardData = async ( .filter( (contributor) => roles.length == 0 || roles.includes(contributor.role), ) + .filter((contributor) => { + if (sortBy === "discussion_answered") { + return contributor.summary.discussion_answered > 0; + } + if (sortBy === "discussion_comment_created") { + return contributor.summary.discussion_comment_created > 0; + } + if (sortBy === "discussion_created") { + return contributor.summary.discussion_created > 0; + } + }) .sort((a, b) => { if (sortBy === "pr_stale") { return b.activityData.pr_stale - a.activityData.pr_stale; diff --git a/lib/api.ts b/lib/api.ts index 95c7afae..48ace95f 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -200,7 +200,7 @@ export async function getContributorBySlug(file: string, detail = false) { }; const weightedActivity = activityData.activity.reduce( - (acc, activity) => { + (acc, activity: Activity) => { return { activity: [ ...acc.activity, From 9a9f63b6bbd9e43111f12b5dbad891ed350a61a8 Mon Sep 17 00:00:00 2001 From: dhrumit parmar Date: Tue, 16 Jul 2024 12:10:07 +0530 Subject: [PATCH 44/71] prose-h2 added to fix markdown bug --- app/globals.css | 9 --------- components/Markdown.tsx | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/app/globals.css b/app/globals.css index 1140b5a4..d2cd20c5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -130,12 +130,3 @@ html.dark { transform: translateX(0px); transform: translateY(0px); } - -.prose-for-h2-markdown :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)){ - color: var(--tw-prose-headings); - font-weight: 700; - font-size: 1.5em; - margin-top: 0.75em; - margin-bottom: 1em; - line-height: 1.3333333; -} diff --git a/components/Markdown.tsx b/components/Markdown.tsx index 5469ccf2..48f37789 100644 --- a/components/Markdown.tsx +++ b/components/Markdown.tsx @@ -16,7 +16,7 @@ export default async function Markdown(props: { .process(props.children || ""); return ( -
+
Date: Thu, 18 Jul 2024 14:48:16 +0530 Subject: [PATCH 45/71] Modified suggested changes still one type error remaining --- app/api/leaderboard/functions.ts | 10 +- app/discussions/layout.tsx | 8 +- app/discussions/page.tsx | 13 ++- app/page.tsx | 24 ++-- components/contributors/GithubActivity.tsx | 19 ++-- .../discussions/DiscussionLeaderboard.tsx | 44 +++++--- components/discussions/FilterDiscussions.tsx | 9 +- components/discussions/GithubDiscussion.tsx | 13 +-- components/discussions/GithubDiscussions.tsx | 9 +- lib/api.ts | 72 +----------- lib/discussion.ts | 106 ++++++++++++++++-- lib/types.ts | 4 +- lib/utils.ts | 12 +- 13 files changed, 180 insertions(+), 163 deletions(-) diff --git a/app/api/leaderboard/functions.ts b/app/api/leaderboard/functions.ts index 8572a068..8e0df1d4 100644 --- a/app/api/leaderboard/functions.ts +++ b/app/api/leaderboard/functions.ts @@ -27,14 +27,8 @@ export const getLeaderboardData = async ( (contributor) => roles.length == 0 || roles.includes(contributor.role), ) .filter((contributor) => { - if (sortBy === "discussion_answered") { - return contributor.summary.discussion_answered > 0; - } - if (sortBy === "discussion_comment_created") { - return contributor.summary.discussion_comment_created > 0; - } - if (sortBy === "discussion_created") { - return contributor.summary.discussion_created > 0; + if (sortBy) { + return contributor.summary[sortBy] ?? 0 > 0; } }) .sort((a, b) => { diff --git a/app/discussions/layout.tsx b/app/discussions/layout.tsx index c949f902..7e118ccd 100644 --- a/app/discussions/layout.tsx +++ b/app/discussions/layout.tsx @@ -3,11 +3,11 @@ import { env } from "@/env.mjs"; import { notFound } from "next/navigation"; import { featureIsEnabled } from "@/lib/utils"; import FilterDiscussions from "../../components/discussions/FilterDiscussions"; -import { categoriesMap } from "../../lib/discussion"; +import { categoriesArray } from "../../lib/discussion"; import DiscussionLeaderboard from "../../components/discussions/DiscussionLeaderboard"; export const metadata: Metadata = { - title: `Disucssion | ${env.NEXT_PUBLIC_PAGE_TITLE}`, + title: `Disucssions | ${env.NEXT_PUBLIC_PAGE_TITLE}`, }; export default function DiscussionsLayout({ @@ -16,12 +16,12 @@ export default function DiscussionsLayout({ children: React.ReactNode; }) { if (!featureIsEnabled("Discussions")) return notFound(); - const categories = Array.from(categoriesMap.values()); + return (

Disucssions

- +
{children} diff --git a/app/discussions/page.tsx b/app/discussions/page.tsx index 6f25aede..c38cd303 100644 --- a/app/discussions/page.tsx +++ b/app/discussions/page.tsx @@ -5,12 +5,15 @@ interface Params { searchParams: { [key: string]: string }; } -const page = async ({ searchParams }: Params) => { +export default async function Page({ searchParams }: Params) { const discussions = await fetchGithubDiscussion(); return ( - + discussions && ( + + ) ); -}; - -export default page; +} diff --git a/app/page.tsx b/app/page.tsx index 9f47a2a3..72c5d69a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -13,7 +13,6 @@ import { differenceInWeeks, parseISO } from "date-fns"; import { featureIsEnabled, formatDate } from "@/lib/utils"; import { fetchGithubDiscussion } from "../lib/discussion"; import GithubDiscussion from "@/components/discussions/GithubDiscussion"; -import { ParsedDiscussion } from "@/scraper/src/github-scraper/types"; export default async function Home() { const contributors = (await getContributors()) @@ -98,7 +97,7 @@ export default async function Home() {
)} - {featureIsEnabled("Discussions") && ( + {discussions && (
@@ -113,18 +112,15 @@ export default async function Home() {
- {discussions.map( - (discussion: ParsedDiscussion, index: number) => { - return ( -
- -
- ); - }, - )} + {discussions.map((discussion, index) => { + return ( + + ); + })}
)} diff --git a/components/contributors/GithubActivity.tsx b/components/contributors/GithubActivity.tsx index 73a21bda..d772c231 100644 --- a/components/contributors/GithubActivity.tsx +++ b/components/contributors/GithubActivity.tsx @@ -171,22 +171,19 @@ let renderText = (activity: Activity) => { case "discussion_answered": case "discussion_comment_created": case "discussion_created": - const { org, repository } = parseOrgRepoFromURL(activity.link); + const { org, repo } = parseOrgRepoFromURL(activity.link); return (

{activity.title}{" "} - {repository && ( + {repo && ( <> in{" "} - + - {org}/{repository} + {org}/{repo} @@ -278,7 +275,7 @@ let icon = (type: string) => { ); case "discussion": - return ; + return ; default: return ( diff --git a/components/discussions/DiscussionLeaderboard.tsx b/components/discussions/DiscussionLeaderboard.tsx index 9b23b813..75d68708 100644 --- a/components/discussions/DiscussionLeaderboard.tsx +++ b/components/discussions/DiscussionLeaderboard.tsx @@ -1,38 +1,46 @@ import { getContributors } from "@/lib/api"; import Link from "next/link"; +interface Contributor { + name: string; + points: number; + githubHandle: string; +} + export async function calculateContributor() { const contributors = await getContributors(); - const contributorsMap = new Map(); - // If we have contributors then we will calculate the top contributors and save it in the map like object {name, points, githubHandle} + // If we have contributors then we will calculate the top contributors and save it in an array of objects {name, points, githubHandle} // points = 1 for each comment + 2 for creating a discussion + 5 discussion marked as helpful if (contributors) { + const contributorsArray: Contributor[] = []; + contributors.forEach((contributor) => { - const existingInfo = contributorsMap.get(contributor.name) || { - points: 0, - githubHandle: contributor.slug, - }; + const existingContributor = contributorsArray.find( + (c) => c.name === contributor.name, + ); + const points = - existingInfo.points + contributor.highlights.discussion_answered || + contributor.highlights.discussion_answered || contributor.highlights.discussion_created || contributor.highlights.discussion_comment_created || 0; - const contributorInfo = { - points: points, - githubHandle: contributor.slug, - }; - - contributorsMap.set(contributor.name, contributorInfo); + if (existingContributor) { + existingContributor.points += points; + } else { + contributorsArray.push({ + name: contributor.name, + points: points, + githubHandle: contributor.slug, + }); + } }); + + return contributorsArray; } - return Array.from(contributorsMap, ([name, { points, githubHandle }]) => ({ - name, - points, - githubHandle, - })); + return []; } const DiscussionLeaderboard = async () => { diff --git a/components/discussions/FilterDiscussions.tsx b/components/discussions/FilterDiscussions.tsx index 8a4e4f28..37c575fb 100644 --- a/components/discussions/FilterDiscussions.tsx +++ b/components/discussions/FilterDiscussions.tsx @@ -1,23 +1,22 @@ "use client"; -import { useState } from "react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import DateRangePicker from "@/components/DateRangePicker"; import { parseDateRangeSearchParam } from "@/lib/utils"; import { format } from "date-fns"; -interface params { +interface Props { categories: { name: string; emoji: string; }[]; } -const FilterDiscussions = ({ categories }: params) => { - const [selectedCategory, setSelectedCategory] = useState(""); +const FilterDiscussions = ({ categories }: Props) => { const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); + const currentCategory = searchParams.get("category") || ""; const [start, end] = parseDateRangeSearchParam(searchParams.get("between")); const updateSearchParam = (key: string, value: string) => { @@ -36,7 +35,7 @@ const FilterDiscussions = ({ categories }: params) => {