2018-01-23 14:31:10 +00:00
// Description:
2018-02-05 18:12:32 +00:00
// Script that periodically checks to GitHub pull request reviews
// and assigns the PR to CONTRIBUTOR/REVIEW/TO TEST columns on the 'Pipeline for QA' project
2018-01-23 14:31:10 +00:00
//
// Dependencies:
// github: "^13.1.0"
2018-01-23 18:38:25 +00:00
// probot-config: "^0.1.0"
// probot-scheduler: "^1.0.3"
2018-01-23 14:31:10 +00:00
//
// Author:
// PombeirP
2018-01-23 18:38:25 +00:00
const createScheduler = require ( 'probot-scheduler' )
2018-02-08 12:09:59 +00:00
const getConfig = require ( 'probot-config' )
const defaultConfig = require ( '../lib/config' )
const gitHubHelpers = require ( '../lib/github-helpers' )
2018-02-05 09:30:08 +00:00
const slackHelper = require ( '../lib/slack' )
2018-01-23 14:31:10 +00:00
2018-02-13 14:51:00 +00:00
const botName = 'assign-approved-pr-to-test'
2018-01-23 14:31:10 +00:00
2018-01-28 08:24:30 +00:00
module . exports = robot => {
2018-04-18 09:53:38 +00:00
createScheduler ( robot , { interval : 10 * 60 * 1000 , delay : ! process . env . DISABLE _DELAY } )
2018-01-23 18:38:25 +00:00
robot . on ( 'schedule.repository' , context => checkOpenPullRequests ( robot , context ) )
2018-01-23 14:31:10 +00:00
}
2018-01-28 08:24:30 +00:00
async function checkOpenPullRequests ( robot , context ) {
2018-02-08 12:09:59 +00:00
const { github , payload } = context
const repo = payload . repository
2018-02-16 20:35:49 +00:00
const repoInfo = { owner : payload . repository . owner . login , repo : payload . repository . name }
2018-02-08 12:09:59 +00:00
const config = await getConfig ( context , 'github-bot.yml' , defaultConfig ( robot , '.github/github-bot.yml' ) )
const projectBoardConfig = config ? config [ 'project-board' ] : null
2018-01-28 08:24:30 +00:00
2018-01-23 18:38:25 +00:00
if ( ! projectBoardConfig ) {
2018-02-16 20:35:49 +00:00
robot . log . debug ( ` ${ botName } - Project board not configured in repo ${ repoInfo . owner } / ${ repoInfo . repo } , ignoring ` )
2018-01-23 18:38:25 +00:00
return
2018-01-23 14:31:10 +00:00
}
2018-01-28 08:24:30 +00:00
2018-03-14 13:06:59 +00:00
const testedPullRequestLabelName = projectBoardConfig [ 'tested-pr-label-name' ]
2018-02-05 09:30:08 +00:00
const contributorColumnName = projectBoardConfig [ 'contributor-column-name' ]
2018-01-23 18:38:25 +00:00
const reviewColumnName = projectBoardConfig [ 'review-column-name' ]
const testColumnName = projectBoardConfig [ 'test-column-name' ]
2018-01-28 08:24:30 +00:00
2018-02-22 15:37:53 +00:00
// Find 'Pipeline for QA' project
const project = await gitHubHelpers . getRepoProjectByName ( github , robot , repoInfo , projectBoardConfig . name , botName )
if ( ! project ) {
2018-02-05 09:30:08 +00:00
return
}
2018-01-28 08:24:30 +00:00
2018-02-05 09:30:08 +00:00
// Fetch column IDs
let ghcolumns
try {
2018-02-05 16:12:16 +00:00
const ghcolumnsPayload = await github . projects . getProjectColumns ( { project _id : project . id } )
ghcolumns = ghcolumnsPayload . data
2018-02-05 09:30:08 +00:00
} catch ( err ) {
2018-02-16 20:35:49 +00:00
robot . log . error ( ` ${ botName } - Couldn't fetch the github columns for project: ${ err } ` , repoInfo , project . id )
2018-02-05 09:30:08 +00:00
return
}
2018-01-28 08:24:30 +00:00
2018-02-16 20:35:49 +00:00
try {
const contributorColumn = findColumnByName ( ghcolumns , contributorColumnName )
const reviewColumn = findColumnByName ( ghcolumns , reviewColumnName )
const testColumn = findColumnByName ( ghcolumns , testColumnName )
2018-04-18 10:50:29 +00:00
const columns = { contributor : contributorColumn , review : reviewColumn , test : testColumn }
2018-01-28 08:24:30 +00:00
2018-02-16 20:35:49 +00:00
robot . log . debug ( ` ${ botName } - Fetched ${ contributorColumn . name } ( ${ contributorColumn . id } ), ${ reviewColumn . name } ( ${ reviewColumn . id } ), ${ testColumn . name } ( ${ testColumn . id } ) columns ` )
2018-02-05 09:30:08 +00:00
2018-02-16 20:35:49 +00:00
try {
// Gather all open PRs in this repo
const allPullRequests = await github . paginate (
github . pullRequests . getAll ( { ... repoInfo , per _page : 100 } ) ,
res => res . data
)
// And make sure they are assigned to the correct project column
for ( const pullRequest of allPullRequests ) {
try {
2018-04-18 10:50:29 +00:00
await assignPullRequestToCorrectColumn ( github , robot , repo , pullRequest , testedPullRequestLabelName , columns , config . slack . notification . room )
2018-02-16 20:35:49 +00:00
} catch ( err ) {
robot . log . error ( ` ${ botName } - Unhandled exception while processing PR: ${ err } ` , repoInfo )
}
2018-02-05 09:30:08 +00:00
}
2018-02-16 20:35:49 +00:00
} catch ( err ) {
robot . log . error ( ` ${ botName } - Couldn't fetch the github pull requests for repo: ${ err } ` , repoInfo )
2018-01-23 18:38:25 +00:00
}
} catch ( err ) {
2018-02-16 20:35:49 +00:00
robot . log . error ( err . message , project . name )
2018-01-23 18:38:25 +00:00
}
}
2018-03-14 13:06:59 +00:00
async function assignPullRequestToCorrectColumn ( github , robot , repo , pullRequest , testedPullRequestLabelName , columns , room ) {
2018-02-16 20:35:49 +00:00
const prInfo = { owner : repo . owner . login , repo : repo . name , number : pullRequest . number }
2018-01-28 08:24:30 +00:00
let state = null
2018-01-23 18:38:25 +00:00
try {
2018-03-14 13:06:59 +00:00
state = await gitHubHelpers . getReviewApprovalState ( github , robot , prInfo , testedPullRequestLabelName )
2018-01-23 18:38:25 +00:00
} catch ( err ) {
2018-02-16 20:35:49 +00:00
robot . log . error ( ` ${ botName } - Couldn't calculate the PR approval state: ${ err } ` , prInfo )
2018-01-23 18:38:25 +00:00
}
2018-01-28 08:24:30 +00:00
2018-02-16 20:35:49 +00:00
const { srcColumns , dstColumn } = getColumns ( state , columns )
if ( ! dstColumn ) {
2018-04-18 09:53:38 +00:00
robot . log . debug ( ` ${ botName } - No dstColumn, state= ${ state } , columns= ${ JSON . stringify ( columns ) } , srcColumns= ${ srcColumns } ` )
2018-02-16 20:35:49 +00:00
return
2018-01-23 18:38:25 +00:00
}
2018-01-28 08:24:30 +00:00
2018-02-16 20:35:49 +00:00
robot . log . debug ( ` ${ botName } - Handling Pull Request # ${ prInfo . number } on repo ${ prInfo . owner } / ${ prInfo . repo } . PR should be in ${ dstColumn . name } column ` )
2018-01-28 08:24:30 +00:00
2018-02-05 09:30:08 +00:00
// Look for PR card in source column(s)
2018-02-05 16:12:16 +00:00
let existingGHCard = null
2018-02-05 09:30:08 +00:00
let srcColumn = null
for ( const c of srcColumns ) {
try {
2018-02-07 16:59:55 +00:00
existingGHCard = await gitHubHelpers . getProjectCardForIssue ( github , c . id , pullRequest . issue _url )
2018-02-05 16:12:16 +00:00
if ( existingGHCard ) {
2018-02-05 09:30:08 +00:00
srcColumn = c
break
}
} catch ( err ) {
2018-02-13 14:51:00 +00:00
robot . log . error ( ` ${ botName } - Failed to retrieve project card for the PR, aborting: ${ err } ` , c . id , pullRequest . issue _url )
2018-02-05 09:30:08 +00:00
return
}
2018-01-23 18:38:25 +00:00
}
2018-01-28 08:24:30 +00:00
2018-02-05 16:12:16 +00:00
if ( existingGHCard ) {
2018-01-23 18:38:25 +00:00
// Move PR card to the destination column
try {
2018-02-13 14:51:00 +00:00
robot . log . trace ( ` ${ botName } - Found card in source column ${ srcColumn . name } ` , existingGHCard . id , srcColumn . id )
2018-01-28 08:24:30 +00:00
2018-02-05 17:52:07 +00:00
if ( dstColumn === srcColumn ) {
return
}
2018-01-28 08:24:30 +00:00
if ( process . env . DRY _RUN || process . env . DRY _RUN _PR _TO _TEST ) {
2018-02-16 20:35:49 +00:00
robot . log . info ( ` ${ botName } - Would have moved card ${ existingGHCard . id } to ${ dstColumn . name } for PR # ${ prInfo . number } ` )
2018-01-28 08:24:30 +00:00
} else {
2018-01-23 18:38:25 +00:00
// Found in the source column, let's move it to the destination column
2018-02-05 16:12:16 +00:00
await github . projects . moveProjectCard ( { id : existingGHCard . id , position : 'bottom' , column _id : dstColumn . id } )
2018-01-28 08:24:30 +00:00
2018-02-16 20:35:49 +00:00
robot . log . info ( ` ${ botName } - Moved card ${ existingGHCard . id } to ${ dstColumn . name } for PR # ${ prInfo . number } ` )
2018-02-05 18:12:32 +00:00
}
2018-01-28 08:24:30 +00:00
2018-02-14 11:37:49 +00:00
slackHelper . sendMessage ( robot , room , ` Assigned PR to ${ dstColumn . name } column \n ${ pullRequest . html _url } ` )
2018-02-05 09:30:08 +00:00
} catch ( err ) {
2018-02-13 14:51:00 +00:00
robot . log . error ( ` ${ botName } - Couldn't move project card for the PR: ${ err } ` , srcColumn . id , dstColumn . id , pullRequest . id )
2018-02-14 11:37:49 +00:00
slackHelper . sendMessage ( robot , room , ` I couldn't move the PR to ${ dstColumn . name } column :confused: \n ${ pullRequest . html _url } ` )
2018-01-23 18:38:25 +00:00
}
} else {
try {
2018-02-13 14:51:00 +00:00
robot . log . debug ( ` ${ botName } - Didn't find card in source column(s) ` , srcColumns . map ( c => c . id ) )
2018-01-28 08:24:30 +00:00
2018-01-23 18:38:25 +00:00
// Look for PR card in destination column
2018-01-23 14:31:10 +00:00
try {
2018-02-07 16:59:55 +00:00
const existingGHCard = await gitHubHelpers . getProjectCardForIssue ( github , dstColumn . id , pullRequest . issue _url )
2018-02-05 16:12:16 +00:00
if ( existingGHCard ) {
2018-02-13 14:51:00 +00:00
robot . log . trace ( ` ${ botName } - Found card in target column, ignoring ` , existingGHCard . id , dstColumn . id )
2018-01-23 18:38:25 +00:00
return
}
2018-01-23 14:31:10 +00:00
} catch ( err ) {
2018-02-13 14:51:00 +00:00
robot . log . error ( ` ${ botName } - Failed to retrieve project card for the PR, aborting: ${ err } ` , dstColumn . id , pullRequest . issue _url )
2018-01-23 14:31:10 +00:00
return
}
2018-01-28 08:24:30 +00:00
if ( process . env . DRY _RUN || process . env . DRY _RUN _PR _TO _TEST ) {
2018-02-16 20:35:49 +00:00
robot . log . info ( ` Would have created card in ${ dstColumn . name } column for PR # ${ prInfo . number } ` )
2018-01-28 08:24:30 +00:00
} else {
2018-01-23 18:38:25 +00:00
// It wasn't in either the source nor the destination columns, let's create a new card for it in the destination column
2018-02-05 16:12:16 +00:00
const ghcardPayload = await github . projects . createProjectCard ( {
2018-01-23 18:38:25 +00:00
column _id : dstColumn . id ,
content _type : 'PullRequest' ,
content _id : pullRequest . id
} )
2018-01-28 08:24:30 +00:00
2018-02-16 20:35:49 +00:00
robot . log . info ( ` ${ botName } - Created card ${ ghcardPayload . data . id } in ${ dstColumn . name } for PR # ${ prInfo . number } ` )
2018-01-23 14:31:10 +00:00
}
} catch ( err ) {
2018-01-23 18:38:25 +00:00
// We normally arrive here because there is already a card for the PR in another column
2018-02-13 14:51:00 +00:00
robot . log . debug ( ` ${ botName } - Couldn't create project card for the PR: ${ err } ` , dstColumn . id , pullRequest . id )
2018-01-23 14:31:10 +00:00
}
2018-01-23 18:38:25 +00:00
}
2018-01-23 14:31:10 +00:00
}
2018-02-16 20:35:49 +00:00
function getColumns ( state , columns ) {
switch ( state ) {
case 'awaiting_reviewers' :
return { srcColumns : [ columns . contributor , columns . test ] , dstColumn : columns . review }
case 'changes_requested' :
return { srcColumns : [ columns . review , columns . test ] , dstColumn : columns . contributor }
case 'failed' :
return { srcColumns : [ columns . review , columns . test ] , dstColumn : columns . contributor }
case 'approved' :
return { srcColumns : [ columns . contributor , columns . review ] , dstColumn : columns . test }
default :
return { srcColumns : [ ] , dstColumn : null }
}
}
function findColumnByName ( ghcolumns , columnName ) {
const column = ghcolumns . find ( c => c . name === columnName )
if ( ! column ) {
throw new Error ( ` ${ botName } - Couldn't find ${ columnName } column ` )
}
return column
}