Automation: Report on CEO PR open time (#13674)

.

---------

Co-authored-by: Mike McNeil <mikermcneil@users.noreply.github.com>
This commit is contained in:
Sampfluger88 2023-09-02 18:59:30 -05:00 committed by GitHub
parent fe97a59447
commit 09be914450
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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.
`);
}
};