Skip to content

Commit

Permalink
feat: send reminder comment to stale initial PRs
Browse files Browse the repository at this point in the history
Turns out some of our users are confused why Greenkeeper is not enabled
for their repos and it turns out, it's because they never merged the
initial PR. With this, we send out a reminder comment on the PR so
people have a clearer understanding of what happens.

We don't nag them again once we reminded them for that repo. We also
don't comment on very old PRs, as this likely causes more problems for
our users then it does good (super outdated dependencies etc.)

Hope this helps a couple of users to run
Greenkeeper :)
  • Loading branch information
espy authored and Realtin committed Jul 4, 2017
1 parent d4cbe3d commit 5cd7b78
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 0 deletions.
14 changes: 14 additions & 0 deletions content/stale-initial-pr-reminder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const md = require('./template')

module.exports = () => md`
Hey there 👋,
we noticed that this PR is not merged yet. Just to let you know _Greenkeeper will
not be enabled_ and you won't receive updates for your dependencies, if you don't merge
this PR.
In case you don't want to enable Greenkeeper, just close or ignore this PR, we
won't nag you again. ✌️
Have a great day! 🌴
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function (doc) {
if(doc.type === 'pr' && doc.initial && doc.state === 'open' && !doc.staleInitialPRReminder) {
emit(doc.createdAt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_count
26 changes: 26 additions & 0 deletions jobs/schedule-stale-initial-pr-reminders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const dbs = require('./lib/dbs')

module.exports = async function () {
const {repositories} = await dbs()
const minAgeInDays = 7
const maxAgeInDays = 14
const startDate = new Date(Date.now() - maxAgeInDays * 24 * 60 * 60 * 1000).toJSON()
const endDate = new Date(Date.now() - minAgeInDays * 24 * 60 * 60 * 1000).toJSON()

const stalePRs = await repositories.query('open_initial_pr', {
startkey: startDate,
endkey: endDate,
inclusive_end: true,
include_docs: true
})
return stalePRs.rows.map(function (row) {
return {
data: {
name: 'send-stale-initial-pr-reminder',
prNumber: row.doc.number,
repositoryId: row.doc.repositoryId,
accountId: row.doc.accountId
}
}
})
}
40 changes: 40 additions & 0 deletions jobs/send-stale-initial-pr-reminder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const githubQueue = require('../lib/github-queue')
const dbs = require('../lib/dbs')
const upsert = require('../lib/upsert')
const staleInitialPRReminderComment = require('../content/stale-initial-pr-reminder')

module.exports = async function (
{ prNumber, repositoryId, accountId }
) {
accountId = String(accountId)
repositoryId = String(repositoryId)

const { installations, repositories } = await dbs()
const installation = await installations.get(accountId)
const repository = await repositories.get(repositoryId)
const installationId = installation.installation
const ghqueue = githubQueue(installationId)

if (repository.enabled) return

const [owner, repo] = repository.fullName.split('/')

const issue = await ghqueue.read(github => github.issues.get({
owner,
repo,
number: prNumber
}))

if (issue.state !== 'open' || issue.locked) return

await ghqueue.write(github => github.issues.createComment({
owner,
repo,
number: prNumber,
body: staleInitialPRReminderComment
}))

await upsert(repositories, repositoryId, {
staleInitialPRReminder: true
})
}
149 changes: 149 additions & 0 deletions test/jobs/send-stale-initial-pr-reminder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
const { test } = require('tap')
const nock = require('nock')
const worker = require('../../jobs/send-stale-initial-pr-reminder')
const upsert = require('../../lib/upsert')

const dbs = require('../../lib/dbs')

const waitFor = (milliseconds) => {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds)
})
}

test('send-stale-initial-pr-reminder', async t => {
const { installations, repositories } = await dbs()

let githubNock

t.beforeEach(async () => {
githubNock = nock('https://api.github.com')
.post('/installations/37/access_tokens')
.reply(200, {
token: 'secret'
})
.get('/rate_limit')
.reply(200, {})

await installations.put({
_id: '123',
installation: 37
})

await repositories.put({
_id: '42',
accountId: '123',
enabled: false,
fullName: 'finnp/test'
})
})

t.afterEach(async () => {
nock.cleanAll()
await installations.remove(await installations.get('123'))
await repositories.remove(await repositories.get('42'))
})

t.test('send reminders for stale initial pr', async t => {
t.plan(2)

githubNock
.get('/repos/finnp/test/issues/1234')
.reply(200, {
state: 'open',
locked: false
})
.post('/repos/finnp/test/issues/1234/comments')
.reply(201, () => {
t.pass('comment added')
return {}
})

await worker({
prNumber: 1234,
repositoryId: 42,
accountId: 123
})

const repoDoc = await repositories.get('42')
t.ok(repoDoc.staleInitialPRReminder, 'staleInitialPRReminder set to true')
await waitFor(50)
})

t.test('does nothing if the repo is already enabled', async t => {
t.plan(1)

await upsert(repositories, '42', {enabled: true})

githubNock
.get('/repos/finnp/test/issues/1234')
.reply(200, () => {
t.fail('Should not query issue status')
return {}
})
.post('/repos/finnp/test/issues/1234/comments')
.reply(201, () => {
t.fail('Should not post comment')
return {}
})

const newJob = await worker({
prNumber: 1234,
repositoryId: 42,
accountId: 123
})

t.notOk(newJob, 'no new job')
await waitFor(50)
})

t.test('does nothing if the issue was closed in the meanwhile', async t => {
t.plan(1)

githubNock
.get('/repos/finnp/test/issues/1234')
.reply(200, {
state: 'closed',
locked: false
})
.post('/repos/finnp/test/issues/1234/comments')
.reply(201, () => {
t.fail('Should not post comment')
return {}
})

const newJob = await worker({
prNumber: 1234,
repositoryId: 42,
accountId: 123
})

t.notOk(newJob, 'no new job')
await waitFor(50)
})

t.test('does nothing if the issue was locked in the meanwhile', async t => {
t.plan(1)

githubNock
.get('/repos/finnp/test/issues/1234')
.reply(200, {
state: 'open',
locked: true
})
.post('/repos/finnp/test/issues/1234/comments')
.reply(201, () => {
t.fail('Should not post comment')
return {}
})

const newJob = await worker({
prNumber: 1234,
repositoryId: 42,
accountId: 123
})

t.notOk(newJob, 'no new job')
await waitFor(50)
})
})

0 comments on commit 5cd7b78

Please sign in to comment.