From 09be914450f7b50839344e5ddaddc48496fa890e Mon Sep 17 00:00:00 2001 From: Sampfluger88 <108141731+Sampfluger88@users.noreply.github.com> Date: Sat, 2 Sep 2023 18:59:30 -0500 Subject: [PATCH] Automation: Report on CEO PR open time (#13674) . --------- Co-authored-by: Mike McNeil --- website/scripts/get-bug-and-pr-report.js | 101 +++++++++++++++++++---- 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/website/scripts/get-bug-and-pr-report.js b/website/scripts/get-bug-and-pr-report.js index f4a509ace2..2262f99790 100644 --- a/website/scripts/get-bug-and-pr-report.js +++ b/website/scripts/get-bug-and-pr-report.js @@ -4,7 +4,7 @@ module.exports = { friendlyName: 'Get bug and PR report', - description: 'Get information about open bugs and closed pull requests in the fleetdm/fleet GitHub repo.', + description: 'Get information about open bugs and pull requests.', inputs: { @@ -14,8 +14,6 @@ module.exports = { fn: async function ({}) { - sails.log('Getting metrics for issues with the "bug" label and pull requests in the fleetdm/fleet Github repo...'); - if(!sails.config.custom.githubAccessToken) { throw new Error('Missing GitHub access token! To use this script, a GitHub access token is required. To resolve, add a GitHub access token to your local configuration (website/config/local.js) as sails.config.custom.githubAccessToken or provide one when running this script. (ex: "sails_custom__githubAccessToken=YOUR_PERSONAL_ACCESS_TOKEN sails run get-bug-and-pr-report")'); } @@ -35,6 +33,11 @@ module.exports = { let daysSinceContributorPullRequestsWereOpened = []; let commitToMergeTimesInDays = []; + let allPublicOpenPrs = []; + let publicPrsMergedInThePastThreeWeeks = []; + let allNonPublicOpenPrs = []; + let nonPublicPrsClosedInThePastThreeWeeks = []; + await sails.helpers.flow.simultaneously([ @@ -93,10 +96,9 @@ module.exports = { async()=>{ let pageNumberForPaginatedResults = 0; - let pullRequestsMergedInThePastThreeWeeks = []; - // Fetch the last 300 closed pull requests from the fleetdm/fleet GitHub Repo + // [?] https://docs.github.com/en/free-pro-team@latest/rest/pulls/pulls#list-pull-requests await sails.helpers.flow.until(async ()=>{ // Increment the page of results we're requesting. pageNumberForPaginatedResults += 1; @@ -118,18 +120,21 @@ module.exports = { }); // Add the filtered array of PRs to the array of all pull requests merged in the past three weeks. - pullRequestsMergedInThePastThreeWeeks = pullRequestsMergedInThePastThreeWeeks.concat(resultsToAdd); + publicPrsMergedInThePastThreeWeeks = publicPrsMergedInThePastThreeWeeks.concat(resultsToAdd); // Stop when we've received results from the third page. return pageNumberForPaginatedResults === 3; }); // To get the timestamp of the first commit for each pull request, we'll need to send a request to the commits API endpoint. - await sails.helpers.flow.simultaneouslyForEach(pullRequestsMergedInThePastThreeWeeks, async (pullRequest)=>{ + await sails.helpers.flow.simultaneouslyForEach(publicPrsMergedInThePastThreeWeeks, async (pullRequest)=>{ // Create a date object from the PR's merged_at timestamp. let pullRequestMergedOn = new Date(pullRequest.merged_at); - // https://docs.github.com/en/rest/commits/commits#list-commits + + // Get commits on this PR. + // [?] https://docs.github.com/en/rest/commits/commits#list-commits let commitsOnThisPullRequest = await sails.helpers.http.get(pullRequest.commits_url, {}, baseHeaders).retry(); + // Create a new Date from the timestamp of the first commit on this pull request. let firstCommitAt = new Date(commitsOnThisPullRequest[0].commit.author.date); // https://docs.github.com/en/rest/commits/commits#list-commits--code-samples // Get the amount of time this issue has been open in milliseconds. @@ -149,10 +154,10 @@ module.exports = { // async()=>{ let pullRequestResultsPageNumber = 0; - let allOpenPullRequests = []; let contributorPullRequests = []; // Fetch all open pull requests in the fleetdm/fleet repo. // Note: This will send requests to GitHub until the number of results is less than the number we requested. + // [?] https://docs.github.com/en/free-pro-team@latest/rest/pulls/pulls#list-pull-requests await sails.helpers.flow.until(async ()=>{ // Increment the page of results we're requesting. pullRequestResultsPageNumber += 1; @@ -166,12 +171,12 @@ module.exports = { baseHeaders ).retry(); // Add the results to the array of results. - allOpenPullRequests = allOpenPullRequests.concat(pullRequests); + allPublicOpenPrs = allPublicOpenPrs.concat(pullRequests); // If we received less results than we requested, we've reached the last page of the results. return pullRequests.length !== NUMBER_OF_RESULTS_REQUESTED; }, 10000); - for(let pullRequest of allOpenPullRequests) { + for(let pullRequest of allPublicOpenPrs) { // Create a date object from the PR's created_at timestamp. let pullRequestOpenedOn = new Date(pullRequest.created_at); // Get the amount of time this issue has been open in milliseconds. @@ -189,6 +194,43 @@ module.exports = { } }//∞ + }, + + // ███╗ ██╗ ██████╗ ███╗ ██╗ ██████╗ ██╗ ██╗██████╗ ██╗ ██╗ ██████╗ + // ████╗ ██║██╔═══██╗████╗ ██║ ██╔══██╗██║ ██║██╔══██╗██║ ██║██╔════╝ + // ██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗██████╔╝██║ ██║██████╔╝██║ ██║██║ + // ██║╚██╗██║██║ ██║██║╚██╗██║╚════╝██╔═══╝ ██║ ██║██╔══██╗██║ ██║██║ + // ██║ ╚████║╚██████╔╝██║ ╚████║ ██║ ╚██████╔╝██████╔╝███████╗██║╚██████╗ + // ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ + // + async()=>{ + + // Fetch confidential and classified PRs (current open, and recent closed) + for (let repoName of ['classified', 'confidential']) { + // [?] https://docs.github.com/en/free-pro-team@latest/rest/pulls/pulls#list-pull-requests + let openPrs = await sails.helpers.http.get(`https://api.github.com/repos/fleetdm/${encodeURIComponent(repoName)}/pulls`, { + state: 'open', + 'per_page': 100, + page: 1, + }, baseHeaders); + allNonPublicOpenPrs = allNonPublicOpenPrs.concat(openPrs); + + // [?] https://docs.github.com/en/free-pro-team@latest/rest/pulls/pulls#list-pull-requests + let last100ClosedPrs = await sails.helpers.http.get(`https://api.github.com/repos/fleetdm/${encodeURIComponent(repoName)}/pulls`, { + state: 'closed', + sort: 'updated', + direction: 'desc', + 'per_page': 100, + page: 1, + }, baseHeaders); + + // Exclude draft PRs and filter the PRs we received from Github using the pull request's closed_at date. + nonPublicPrsClosedInThePastThreeWeeks = nonPublicPrsClosedInThePastThreeWeeks.concat( + last100ClosedPrs.filter((pr)=>{ + return !pr.draft && threeWeeksAgo.getTime() <= (new Date(pr.closed_at)).getTime(); + }) + ); + }//∞ } ]); @@ -199,17 +241,40 @@ module.exports = { let averageDaysPullRequestsAreOpenFor = Math.round(_.sum(daysSincePullRequestsWereOpened)/daysSincePullRequestsWereOpened.length); let averageDaysContributorPullRequestsAreOpenFor = Math.round(_.sum(daysSinceContributorPullRequestsWereOpened)/daysSinceContributorPullRequestsWereOpened.length); + + // Compute CEO-dependent PR KPIs, which are slightly simpler. + // FUTURE: Refactor this to be less messy. + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + let ceoDependentOpenPrs = []; + ceoDependentOpenPrs = ceoDependentOpenPrs.concat(allPublicOpenPrs.filter((pr) => !pr.draft && _.pluck(pr.labels, 'name').includes('#g-ceo'))); + ceoDependentOpenPrs = ceoDependentOpenPrs.concat(allNonPublicOpenPrs.filter((pr) => !pr.draft && _.pluck(pr.labels, 'name').includes('#g-ceo'))); + + let ceoDependentPrsMergedRecently = []; + ceoDependentPrsMergedRecently = ceoDependentPrsMergedRecently.concat(publicPrsMergedInThePastThreeWeeks.filter((pr) => !pr.draft && _.pluck(pr.labels, 'name').includes('#g-ceo'))); + ceoDependentPrsMergedRecently = ceoDependentPrsMergedRecently.concat(nonPublicPrsClosedInThePastThreeWeeks.filter((pr) => !pr.draft && _.pluck(pr.labels, 'name').includes('#g-ceo'))); + + let ceoDependentPrOpenTime = ceoDependentPrsMergedRecently.reduce((avgDaysOpen, pr)=>{ + let openedAt = new Date(pr.created_at).getTime(); + let closedAt = new Date(pr.closed_at).getTime(); + let daysOpen = Math.abs(closedAt - openedAt) / ONE_DAY_IN_MILLISECONDS; + avgDaysOpen = avgDaysOpen + (daysOpen / ceoDependentPrsMergedRecently.length); + sails.log.verbose('Processing',pr.head.repo.name,':: #'+pr.number,'open '+daysOpen+' days', 'rolling avg now '+avgDaysOpen); + return avgDaysOpen; + }, 0); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // Log the results sails.log(` Bugs: --------------------------- - Number of open issues with the "bug" label: ${daysSinceBugsWereOpened.length} + Number of open issues with the "bug" label in fleetdm/fleet: ${daysSinceBugsWereOpened.length} Average open time: ${averageNumberOfDaysBugsAreOpenFor} days. Closed pull requests: --------------------------- - Number of pull requests merged in the past three weeks: ${commitToMergeTimesInDays.length} + Number of pull requests merged in the past three weeks in fleetdm/fleet: ${commitToMergeTimesInDays.length} Average time from first commit to merge: ${averageNumberOfDaysFromCommitToMerge} days. @@ -219,7 +284,15 @@ module.exports = { Average open time: ${averageDaysPullRequestsAreOpenFor} days. Number of open pull requests in the fleetdm/fleet Github repo (no bots, no handbook, no ceo): ${daysSinceContributorPullRequestsWereOpened.length} - Average open time (no bots, no handbook, no ceo): ${averageDaysContributorPullRequestsAreOpenFor} days.`); + Average open time (no bots, no handbook, no ceo): ${averageDaysContributorPullRequestsAreOpenFor} days. + + + Pull requests requiring CEO review + --------------------------------------- + Number of open #g-ceo pull requests in the fleetdm Github org: ${ceoDependentOpenPrs.length} + Average open time (#g-ceo PRs): ${Math.round(ceoDependentPrOpenTime*100)/100} days. + `); + } };