diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e0ae6e7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "runtimeExecutable": "/Users/alexma/.nvm/versions/node/v14.17.4/bin/node", + "program": "${workspaceFolder}/src/index.js" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 7ddaf16..21a6911 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "org-repo-summarizer", "version": "1.0.0", - "main": "index.js", + "main": "src/index.js", "repository": "git@github.com:gioragutt/org-repo-summarizer.git", "author": "Giora Guttsait ", "license": "MIT", "scripts": { "lint": "eslint .", "start": "node .", - "dc": "docker compose -f docker-compose.yml" + "dc": "docker-compose up" }, "devDependencies": { "@babel/eslint-parser": "^7.14.7", @@ -20,6 +20,7 @@ }, "dependencies": { "@babel/core": "^7.14.6", + "date-fns": "^2.23.0", "dotenv": "^10.0.0", "eslint-config-prettier": "^8.3.0", "fast-csv": "^4.3.6", diff --git a/src/lib/queries.js b/src/lib/queries.js index f0eb63c..11564b5 100644 --- a/src/lib/queries.js +++ b/src/lib/queries.js @@ -2,12 +2,17 @@ const {octokit} = require('../providers/octokit'); const {getCachedOrCalculate} = require('../providers/redis'); const fetch = require('node-fetch').default; const {toArray} = require('ix/asynciterable'); +const {differenceInYears} = require('date-fns'); /** * @param {string} name */ function isBot(name) { - return name.endsWith('bot') || name.endsWith('[bot]'); + return name?.endsWith('bot') || name?.endsWith('[bot]'); +} + +function isBotInPR(pull) { + return isBot(pull?.user?.login) || !!pull?.title?.toLowerCase().includes('snyk'); } /** @@ -101,7 +106,7 @@ async function lastPullRequestExcludingBots(owner, repo) { for await (const {data: pulls} of iterator) { for (const pull of pulls) { - if (!isBot(pull.user.login)) { + if (!isBotInPR(pull)) { return pull; } } @@ -131,6 +136,54 @@ async function lastIssueExcludingBots(owner, repo) { }); } +async function issuesInPastYear(owner, repo) { + return getCachedOrCalculate(`issues_in_past_year/${owner}/${repo}`, async () => { + const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, { + owner, + repo, + per_page: 30, + direction: 'desc', + state: 'all', + }); + + let counter = 0; + for await (const {data: issues} of iterator) { + for (const issue of issues) { + if ( + !isBot(issue.user.login) && + !issue.pull_request && + differenceInYears(new Date(), new Date(issue.updated_at)) < 1 + ) { + counter++; + } + } + } + return counter; + }); +} + +async function prsInPastYear(owner, repo) { + return getCachedOrCalculate(`prs_in_past_year/${owner}/${repo}`, async () => { + const iterator = octokit.paginate.iterator(octokit.rest.pulls.list, { + owner, + repo, + per_page: 30, + direction: 'desc', + state: 'all', + }); + + let counter = 0; + for await (const {data: pulls} of iterator) { + for (const pr of pulls) { + if (!isBotInPR(pr) && differenceInYears(new Date(), new Date(pr.updated_at)) < 1) { + counter++; + } + } + } + return counter; + }); +} + class RepoQueries { /** * @param {string} owner @@ -160,9 +213,18 @@ class RepoQueries { async lastPullRequestExcludingBots() { return await lastPullRequestExcludingBots(this.owner, this.repo); } + async lastIssueExcludingBots() { return await lastIssueExcludingBots(this.owner, this.repo); } + + async issuesInPastYear() { + return await issuesInPastYear(this.owner, this.repo); + } + + async prsInPastYear() { + return await prsInPastYear(this.owner, this.repo); + } } module.exports = { @@ -173,5 +235,7 @@ module.exports = { lastPullRequestExcludingBots, lastIssueExcludingBots, repositoriesForOrg, + issuesInPastYear, + prsInPastYear, RepoQueries, }; diff --git a/src/lib/summarize_repo.js b/src/lib/summarize_repo.js index 9eb9a32..528725c 100644 --- a/src/lib/summarize_repo.js +++ b/src/lib/summarize_repo.js @@ -46,13 +46,16 @@ async function getPackageSummary(queries) { async function summarizeRepo(owner, repo) { const queries = new RepoQueries(owner, repo.name); - const [contributors, packageSummary, lastCommit, lastPR, lastIssue] = await Promise.all([ - queries.contributors(), - getPackageSummary(queries), - queries.lastCommitExcludingBots(), - queries.lastPullRequestExcludingBots(), - queries.lastIssueExcludingBots(), - ]); + const [contributors, packageSummary, lastCommit, lastPR, lastIssue, issuesInPastYear, prsInPastYear] = + await Promise.all([ + queries.contributors(), + getPackageSummary(queries), + queries.lastCommitExcludingBots(), + queries.lastPullRequestExcludingBots(), + queries.lastIssueExcludingBots(), + queries.issuesInPastYear(), + queries.prsInPastYear(), + ]); const lastInteractions = { commit: lastCommit && { @@ -76,12 +79,16 @@ async function summarizeRepo(owner, repo) { owner, repo: repo.name, isFork: repo.fork, + forks: repo.forks, + stars: repo.stargazers_count, contributors, packageSummary, lastCommit, lastPR, lastIssue, lastInteractions, + issuesInPastYear, + prsInPastYear, }; } diff --git a/yarn.lock b/yarn.lock index e4d3d96..62da40c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -766,6 +766,11 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +date-fns@^2.23.0: + version "2.23.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9" + integrity sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA== + debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"