Created
January 6, 2017 10:34
-
-
Save GrayHatter/0aa943cab7742c03ef741c1328d298c3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is the built-in review completion condition that | |
// Reviewable uses by default. | |
// It checks that all files have been reviewed by at least one | |
// user and that all discussions have been resolved. All the | |
// information about the current review is supplied in a | |
// predefined `review` variable that will look like the JSON | |
// structure on the right. You can edit it here for testing | |
// how the code will behave in different scenarios. | |
// You can load other examples from the dropdown menu above. | |
let reasons = []; // pieces of the status description | |
let shortReasons = []; // pieces of the short status desc. | |
const summary = review.summary; // shortcut to summary | |
const completed = | |
!summary.numUnresolvedDiscussions && | |
!summary.numUnreviewedFiles; | |
if (summary.numUnreviewedFiles) { | |
reasons.push( | |
(summary.numFiles - summary.numUnreviewedFiles) + | |
' of ' + summary.numFiles + ' files reviewed'); | |
shortReasons.push( | |
summary.numUnreviewedFiles + ' file' + | |
(summary.numUnreviewedFiles > 1 ? 's' : '') | |
); | |
} else { | |
reasons.push('all files reviewed'); | |
} | |
if (summary.numUnresolvedDiscussions) { | |
reasons.push( | |
summary.numUnresolvedDiscussions + | |
' unresolved discussion' + | |
(summary.numUnresolvedDiscussions > 1 ? 's' : '')); | |
shortReasons.push( | |
summary.numUnresolvedDiscussions + ' discussion' + | |
(summary.numUnresolvedDiscussions > 1 ? 's' : '') | |
); | |
} else { | |
reasons.push('all discussions resolved'); | |
} | |
let discussionBlockers = _(review.discussions) | |
.where({resolved: false}) | |
.pluck('participants') | |
.flatten() | |
.where({resolved: false}) | |
.map(user => _.pick(user, 'username')) | |
.value(); | |
let fileBlockers = _(review.files) | |
.filter(file => _.isEmpty(_.last(file.revisions).reviewers)) | |
.map(file => _(file.revisions).findLast( | |
rev => !_.isEmpty(rev.reviewers))) | |
.compact() | |
.pluck('reviewers') | |
.flatten() | |
.value(); | |
if (!completed && _.some(fileBlockers, user => !user)) { | |
fileBlockers = | |
fileBlockers.concat(review.pullRequest.assignees); | |
} | |
let shortDescription; | |
if (completed) { | |
shortDescription = | |
summary.numFiles + ' file' + | |
(summary.numFiles > 1 ? 's' : '') + ' reviewed'; | |
} else { | |
shortDescription = shortReasons.join(', ') + ' left'; | |
} | |
return { | |
completed, | |
description: reasons.join(', '), | |
shortDescription, | |
pendingReviewers: | |
_.uniq(fileBlockers.concat(discussionBlockers), 'username') | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This code will check that all discussions have been resolved, | |
// and all files reviewed at the latest revision by all the | |
// assignees and by at least the required number of people, while | |
// ignoring the pull request's author. It makes use of Lodash | |
// (_), which is supplied by the execution environment. | |
const numReviewersRequired = 1; | |
let completed = true; | |
let reasons = []; // pieces of the status description | |
let shortReasons = []; // pieces of the short status desc. | |
let fileBlockers = []; // users who still need to review files | |
const assignees = _(review.pullRequest.assignees) | |
.pluck('username') | |
.without(review.pullRequest.author.username) | |
.value(); | |
let numUnreviewedFiles = 0; | |
_.each(review.files, function(file) { | |
const reviewers = _(_.last(file.revisions).reviewers) | |
.pluck('username') | |
.without(review.pullRequest.author.username) | |
.value(); | |
const missingAssignees = _.difference(assignees, reviewers); | |
if (reviewers.length >= numReviewersRequired && | |
_.isEmpty(missingAssignees)) return; | |
numUnreviewedFiles++; | |
const lastReviewedRev = | |
_(file.revisions).findLast(rev => !_.isEmpty(rev.reviewers)); | |
fileBlockers = fileBlockers.concat( | |
_.map(missingAssignees, username => ({username})), | |
lastReviewedRev ? lastReviewedRev.reviewers : [] | |
); | |
}); | |
if (numUnreviewedFiles) { | |
completed = false; | |
reasons.push( | |
(review.summary.numFiles - numUnreviewedFiles) + ' of ' + | |
review.summary.numFiles + ' files reviewed'); | |
shortReasons.push( | |
numUnreviewedFiles + ' file' + | |
(numUnreviewedFiles > 1 ? 's' : '') | |
); | |
} else { | |
reasons.push('all files reviewed'); | |
} | |
if (review.summary.numUnresolvedDiscussions) { | |
completed = false; | |
reasons.push( | |
review.summary.numUnresolvedDiscussions + | |
' unresolved discussion' + | |
(review.summary.numUnresolvedDiscussions > 1 ? 's' : '')); | |
shortReasons.push( | |
review.summary.numUnresolvedDiscussions + ' discussion' + | |
(review.summary.numUnresolvedDiscussions > 1 ? 's' : '') | |
); | |
} else { | |
reasons.push('all discussions resolved'); | |
} | |
let discussionBlockers = _(review.discussions) | |
.where({resolved: false}) | |
.pluck('participants') | |
.flatten() | |
.where({resolved: false}) | |
.map(user => _.pick(user, 'username')) | |
.value(); | |
let shortDescription; | |
if (completed) { | |
shortDescription = | |
review.summary.numFiles + ' file' + | |
(review.summary.numFiles > 1 ? 's' : '') + ' reviewed'; | |
} else { | |
shortDescription = shortReasons.join(', ') + ' left'; | |
} | |
return { | |
completed, | |
description: reasons.join(', '), | |
shortDescription, | |
pendingReviewers: | |
_.uniq(fileBlockers.concat(discussionBlockers), 'username') | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This code will check that the pull request has been approved | |
// via LGTM (Looks Good To Me) emojis by a minimum number of | |
// reviewers and by all assignees. | |
// | |
// Approval is granted via the :lgtm: and :lgtm_strong: emojis, | |
// and can be withdrawn with :lgtm_cancel:. An :lgtm: is only | |
// good for the last non-provisional revision at the time the | |
// comment is sent, so any new commits will require another | |
// approval. An :lgtm_strong: is good for all revisions unless | |
// canceled. | |
// The number of LGTMs required to merge. | |
let numApprovalsRequired = 1; | |
// Approval by username: true if current LGTM, false if stale, | |
// missing if not given or canceled. | |
let approvals = {}; | |
// Timestamp of the currently latest revision. | |
const lastRevisionTimestamp = | |
_.last(review.revisions).snapshotTimestamp; | |
_.each(review.sentiments, function(sentiment) { | |
const emojis = _.indexBy(sentiment.emojis); | |
if (emojis.lgtm_cancel) { | |
delete approvals[sentiment.username]; | |
} else if (emojis.lgtm_strong) { | |
approvals[sentiment.username] = true; | |
} else if (emojis.lgtm && !approvals[sentiment.username]) { | |
approvals[sentiment.username] = | |
sentiment.timestamp >= lastRevisionTimestamp; | |
} | |
}); | |
const numApprovals = _.countBy(approvals); | |
let numGranted = numApprovals.true || 0; | |
let pendingReviewers = []; | |
const assignees = | |
_.pluck(review.pullRequest.assignees, 'username'); | |
if (assignees.length) { | |
numApprovalsRequired = | |
_.max([assignees.length, numApprovalsRequired]); | |
numGranted = | |
(_(approvals).pick(assignees).countBy().value().true || 0) + | |
_.min([numGranted, numApprovalsRequired - assignees.length]); | |
pendingReviewers = | |
_(assignees) | |
.reject(username => approvals[username]) | |
.map(username => ({username})) | |
.value(); | |
} | |
let description = | |
numGranted + ' of ' + numApprovalsRequired + ' LGTMs obtained'; | |
let shortDescription = | |
numGranted + '/' + numApprovalsRequired + ' LGTMs'; | |
if (numApprovals.false) { | |
description += ', and ' + numApprovals.false + ' stale'; | |
shortDescription += ', ' + numApprovals.false + ' stale'; | |
} | |
return { | |
completed: numGranted >= numApprovalsRequired, | |
description, shortDescription, pendingReviewers, | |
debug: approvals | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment