2018-02-07 16:59:55 +00:00
|
|
|
// Description:
|
|
|
|
// GtHub-related helpers
|
|
|
|
//
|
|
|
|
// Dependencies:
|
|
|
|
// github: "^13.1.0"
|
|
|
|
//
|
|
|
|
// Author:
|
|
|
|
// PombeirP
|
|
|
|
|
2018-02-22 20:58:54 +00:00
|
|
|
module.exports = {
|
|
|
|
getPullRequestReviewStates: _getPullRequestReviewStates,
|
|
|
|
getReviewApprovalState: _getReviewApprovalState,
|
|
|
|
getProjectCardForIssue: _getProjectCardForIssue,
|
|
|
|
getOrgProjectByName: _getOrgProjectByName,
|
|
|
|
getRepoProjectByName: _getRepoProjectByName,
|
2019-01-10 12:57:39 +00:00
|
|
|
getProjectColumnByName: _getProjectColumnByName,
|
|
|
|
getPullRequestCurrentStatusForContext: _getPullRequestCurrentStatusForContext
|
2018-02-22 20:58:54 +00:00
|
|
|
}
|
2018-02-07 16:59:55 +00:00
|
|
|
|
2018-02-16 20:35:49 +00:00
|
|
|
async function _getPullRequestReviewStates (github, prInfo) {
|
2018-02-07 16:59:55 +00:00
|
|
|
let finalReviewsMap = new Map()
|
|
|
|
const ghreviews = await github.paginate(
|
2018-02-16 20:35:49 +00:00
|
|
|
github.pullRequests.getReviews({ ...prInfo, per_page: 100 }),
|
2018-02-07 16:59:55 +00:00
|
|
|
res => res.data)
|
2018-03-27 18:39:11 +00:00
|
|
|
for (const review of ghreviews) {
|
2018-02-07 16:59:55 +00:00
|
|
|
switch (review.state) {
|
|
|
|
case 'APPROVED':
|
|
|
|
case 'CHANGES_REQUESTED':
|
|
|
|
case 'PENDING':
|
|
|
|
finalReviewsMap.set(review.user.id, review.state)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Array.from(finalReviewsMap.values())
|
|
|
|
}
|
|
|
|
|
2018-03-14 13:06:59 +00:00
|
|
|
async function _getReviewApprovalState (github, robot, prInfo, testedPullRequestLabelName) {
|
2018-02-07 16:59:55 +00:00
|
|
|
// Get detailed pull request
|
2018-02-16 20:35:49 +00:00
|
|
|
const pullRequestPayload = await github.pullRequests.get(prInfo)
|
2018-02-07 16:59:55 +00:00
|
|
|
const pullRequest = pullRequestPayload.data
|
|
|
|
if (pullRequest.mergeable !== null && pullRequest.mergeable !== undefined && !pullRequest.mergeable) {
|
|
|
|
robot.log.debug(`pullRequest.mergeable is ${pullRequest.mergeable}, considering as failed`)
|
|
|
|
return 'failed'
|
|
|
|
}
|
|
|
|
|
|
|
|
let state
|
|
|
|
switch (pullRequest.mergeable_state) {
|
|
|
|
case 'clean':
|
2018-03-14 13:06:59 +00:00
|
|
|
if (testedPullRequestLabelName !== null && pullRequest.labels.find(label => label.name === testedPullRequestLabelName)) {
|
|
|
|
robot.log.debug(`Pull request is labeled '${testedPullRequestLabelName}', ignoring`)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2018-02-07 16:59:55 +00:00
|
|
|
state = 'approved'
|
|
|
|
break
|
|
|
|
case 'dirty':
|
|
|
|
state = 'failed'
|
|
|
|
break
|
|
|
|
}
|
|
|
|
robot.log.debug(`pullRequest.mergeable_state is ${pullRequest.mergeable_state}, considering state as ${state}`)
|
|
|
|
|
|
|
|
if (state !== 'approved') {
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
|
|
|
const threshold = 2 // Minimum number of approvers
|
|
|
|
|
2018-02-16 20:35:49 +00:00
|
|
|
const finalReviews = await _getPullRequestReviewStates(github, prInfo)
|
2018-02-07 16:59:55 +00:00
|
|
|
robot.log.debug(finalReviews)
|
|
|
|
|
|
|
|
const approvedReviews = finalReviews.filter(reviewState => reviewState === 'APPROVED')
|
|
|
|
if (approvedReviews.length >= threshold) {
|
|
|
|
const reviewsWithChangesRequested = finalReviews.filter(reviewState => reviewState === 'CHANGES_REQUESTED')
|
|
|
|
if (reviewsWithChangesRequested.length === 0) {
|
2018-04-18 09:53:38 +00:00
|
|
|
robot.log.debug(`No changes requested, considering state as approved`)
|
2018-02-07 16:59:55 +00:00
|
|
|
return 'approved'
|
|
|
|
}
|
|
|
|
|
2018-04-18 09:53:38 +00:00
|
|
|
robot.log.debug(`${reviewsWithChangesRequested.length} changes requested, considering state as changes_requested`)
|
2018-02-07 16:59:55 +00:00
|
|
|
return 'changes_requested'
|
|
|
|
}
|
|
|
|
|
2018-04-18 09:53:38 +00:00
|
|
|
robot.log.debug(`Not enough reviewers yet, considering state as awaiting_reviewers`)
|
2018-02-07 16:59:55 +00:00
|
|
|
return 'awaiting_reviewers'
|
|
|
|
}
|
|
|
|
|
|
|
|
async function _getProjectCardForIssue (github, columnId, issueUrl) {
|
|
|
|
const ghcardsPayload = await github.projects.getProjectCards({column_id: columnId})
|
|
|
|
const ghcard = ghcardsPayload.data.find(c => c.content_url === issueUrl)
|
|
|
|
|
|
|
|
return ghcard
|
|
|
|
}
|
2018-02-22 15:37:53 +00:00
|
|
|
|
|
|
|
async function _getOrgProjectByName (github, robot, orgName, projectName, botName) {
|
2018-02-22 15:59:09 +00:00
|
|
|
if (!projectName) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2018-02-22 15:37:53 +00:00
|
|
|
try {
|
|
|
|
// Fetch org projects
|
|
|
|
// TODO: The org project and project column info should be cached
|
|
|
|
// in order to improve performance and reduce roundtrips
|
|
|
|
const ghprojectsPayload = await github.projects.getOrgProjects({
|
|
|
|
org: orgName,
|
|
|
|
state: 'open'
|
|
|
|
})
|
|
|
|
|
|
|
|
const project = ghprojectsPayload.data.find(p => p.name === projectName)
|
|
|
|
if (!project) {
|
|
|
|
robot.log.error(`${botName} - Couldn't find project ${projectName} in ${orgName} org`)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
robot.log.debug(`${botName} - Fetched ${project.name} project (${project.id})`)
|
|
|
|
|
|
|
|
return project
|
|
|
|
} catch (err) {
|
|
|
|
robot.log.error(`${botName} - Couldn't fetch the github projects for org`, orgName, err)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function _getRepoProjectByName (github, robot, repoInfo, projectName, botName) {
|
2018-02-22 15:59:09 +00:00
|
|
|
if (!projectName) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2018-02-22 15:37:53 +00:00
|
|
|
try {
|
|
|
|
const ghprojectsPayload = await github.projects.getRepoProjects({ ...repoInfo, state: 'open' })
|
|
|
|
const project = ghprojectsPayload.data.find(p => p.name === projectName)
|
|
|
|
if (!project) {
|
|
|
|
robot.log.error(`${botName} - Couldn't find project ${projectName} in repo ${repoInfo.owner}/${repoInfo.repo}`)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
robot.log.debug(`${botName} - Fetched ${project.name} project (${project.id})`)
|
|
|
|
|
|
|
|
return project
|
|
|
|
} catch (err) {
|
|
|
|
robot.log.error(`${botName} - Couldn't fetch the github projects for repo: ${err}`, repoInfo)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function _getProjectColumnByName (github, robot, project, columnName, botName) {
|
|
|
|
if (!project) {
|
|
|
|
return null
|
|
|
|
}
|
2018-02-22 15:59:09 +00:00
|
|
|
if (!columnName) {
|
|
|
|
return null
|
|
|
|
}
|
2018-02-22 15:37:53 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
const ghcolumnsPayload = await github.projects.getProjectColumns({ project_id: project.id })
|
|
|
|
const column = ghcolumnsPayload.data.find(c => c.name === columnName)
|
|
|
|
if (!column) {
|
|
|
|
robot.log.error(`${botName} - Couldn't find ${columnName} column in project ${project.name}`)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
robot.log.debug(`${botName} - Fetched ${column.name} column (${column.id})`)
|
|
|
|
|
|
|
|
return column
|
|
|
|
} catch (err) {
|
|
|
|
robot.log.error(`${botName} - Couldn't fetch the github columns for project: ${err}`, project.id)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
2019-01-10 12:57:39 +00:00
|
|
|
|
|
|
|
async function _getPullRequestCurrentStatusForContext (context, statusContext, pullRequest) {
|
|
|
|
if (!pullRequest) {
|
|
|
|
pullRequest = context.payload.pull_request
|
|
|
|
}
|
|
|
|
|
|
|
|
const { data: { statuses } } = await context.github.repos.getCombinedStatusForRef(context.repo({
|
|
|
|
ref: pullRequest.head.sha
|
|
|
|
}))
|
|
|
|
|
|
|
|
return (statuses.find(status => status.context === statusContext) || {}).state
|
|
|
|
}
|