Remove stale bot which will become standalone
This commit is contained in:
parent
2f1d5af807
commit
765111ea52
|
@ -1,81 +0,0 @@
|
|||
// Description:
|
||||
// A GitHub App built with Probot that closes abandoned Issues and Pull Requests after a period of inactivity. https://probot.github.io/apps/stale/
|
||||
//
|
||||
// Dependencies:
|
||||
// github: "^13.1.0"
|
||||
// joi: "^13.1.2"
|
||||
// probot-config: "^1.0.0"
|
||||
// probot-scheduler: "^1.2.0"
|
||||
//
|
||||
// Author:
|
||||
// https://probot.github.io/apps/stale/
|
||||
|
||||
const getConfig = require('probot-config')
|
||||
const createScheduler = require('probot-scheduler')
|
||||
const defaultConfig = require('../../lib/config')
|
||||
const Stale = require('./lib/stale')
|
||||
|
||||
module.exports = async app => {
|
||||
// Visit all repositories to mark and sweep stale issues
|
||||
const scheduler = createScheduler(app)
|
||||
|
||||
// Unmark stale issues if a user comments
|
||||
const events = [
|
||||
'issue_comment',
|
||||
'issues',
|
||||
'pull_request',
|
||||
'pull_request_review',
|
||||
'pull_request_review_comment'
|
||||
]
|
||||
|
||||
app.on(events, context => unmark(app, context))
|
||||
app.on('schedule.repository', context => markAndSweep(app, context))
|
||||
|
||||
async function unmark (robot, context) {
|
||||
if (!context.isBot) {
|
||||
const stale = await forRepository(robot, context)
|
||||
let issue = context.payload.issue || context.payload.pull_request
|
||||
const type = context.payload.issue ? 'issues' : 'pulls'
|
||||
|
||||
// Some payloads don't include labels
|
||||
if (!issue.labels) {
|
||||
try {
|
||||
issue = (await context.github.issues.get(context.issue())).data
|
||||
} catch (error) {
|
||||
context.log('Issue not found')
|
||||
}
|
||||
}
|
||||
|
||||
const staleLabelAdded = context.payload.action === 'labeled' &&
|
||||
context.payload.label.name === stale.config.staleLabel
|
||||
|
||||
if (stale.hasStaleLabel(type, issue) && issue.state !== 'closed' && !staleLabelAdded) {
|
||||
stale.unmark(type, issue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function markAndSweep (robot, context) {
|
||||
const stale = await forRepository(robot, context)
|
||||
await stale.markAndSweep('pulls')
|
||||
await stale.markAndSweep('issues')
|
||||
}
|
||||
|
||||
async function forRepository (robot, context) {
|
||||
let config = await getConfig(context, 'github-bot.yml', defaultConfig(robot, '.github/github-bot.yml'))
|
||||
|
||||
if (config) {
|
||||
config = config.stale
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
scheduler.stop(context.payload.repository)
|
||||
// Don't actually perform for repository without a config
|
||||
config = { perform: false }
|
||||
}
|
||||
|
||||
config = Object.assign(config, context.repo({ logger: app.log }))
|
||||
|
||||
return new Stale(context.github, config)
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
const Joi = require('joi')
|
||||
|
||||
const fields = {
|
||||
daysUntilStale: Joi.number()
|
||||
.description('Number of days of inactivity before an Issue or Pull Request becomes stale'),
|
||||
|
||||
daysUntilPullRequestStale: Joi.number()
|
||||
.description('Number of days of inactivity before a Pull Request becomes stale (overrides daysUntilStale for PRs)'),
|
||||
|
||||
daysUntilClose: Joi.alternatives().try(Joi.number(), Joi.boolean().only(false))
|
||||
.error(() => '"daysUntilClose" must be a number or false')
|
||||
.description('Number of days of inactivity before a stale Issue or Pull Request is closed. If disabled, issues still need to be closed manually, but will remain marked as stale.'),
|
||||
|
||||
exemptLabels: Joi.alternatives().try(Joi.any().valid(null), Joi.array().single())
|
||||
.description('Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable'),
|
||||
|
||||
exemptProjects: Joi.boolean()
|
||||
.description('Set to true to ignore issues in a project (defaults to false)'),
|
||||
|
||||
exemptMilestones: Joi.boolean()
|
||||
.description('Set to true to ignore issues in a milestone (defaults to false)'),
|
||||
|
||||
staleLabel: Joi.string()
|
||||
.description('Label to use when marking as stale'),
|
||||
|
||||
markComment: Joi.alternatives().try(Joi.string(), Joi.any().only(false))
|
||||
.error(() => '"markComment" must be a string or false')
|
||||
.description('Comment to post when marking as stale. Set to `false` to disable'),
|
||||
|
||||
unmarkComment: Joi.alternatives().try(Joi.string(), Joi.boolean().only(false))
|
||||
.error(() => '"unmarkComment" must be a string or false')
|
||||
.description('Comment to post when removing the stale label. Set to `false` to disable'),
|
||||
|
||||
closeComment: Joi.alternatives().try(Joi.string(), Joi.boolean().only(false))
|
||||
.error(() => '"closeComment" must be a string or false')
|
||||
.description('Comment to post when closing a stale Issue or Pull Request. Set to `false` to disable'),
|
||||
|
||||
limitPerRun: Joi.number().integer().min(1).max(30)
|
||||
.error(() => '"limitPerRun" must be an integer between 1 and 30')
|
||||
.description('Limit the number of actions per hour, from 1-30. Default is 30')
|
||||
}
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
daysUntilStale: fields.daysUntilStale.default(60),
|
||||
daysUntilPullRequestStale: fields.daysUntilPullRequestStale,
|
||||
daysUntilClose: fields.daysUntilClose.default(7),
|
||||
exemptLabels: fields.exemptLabels.default(['pinned', 'security']),
|
||||
exemptProjects: fields.exemptProjects.default(false),
|
||||
exemptMilestones: fields.exemptMilestones.default(false),
|
||||
staleLabel: fields.staleLabel.default('wontfix'),
|
||||
markComment: fields.markComment.default(
|
||||
'This issue has been automatically marked as stale because ' +
|
||||
'it has not had recent activity. It will be closed if no further ' +
|
||||
'activity occurs. Thank you for your contributions.'
|
||||
),
|
||||
unmarkComment: fields.unmarkComment.default(false),
|
||||
closeComment: fields.closeComment.default(false),
|
||||
limitPerRun: fields.limitPerRun.default(30),
|
||||
perform: Joi.boolean().default(!process.env.DRY_RUN),
|
||||
only: Joi.any().valid('issues', 'pulls', null).description('Limit to only `issues` or `pulls`'),
|
||||
pulls: Joi.object().keys(fields),
|
||||
issues: Joi.object().keys(fields),
|
||||
_extends: Joi.string().description('Repository to extend settings from')
|
||||
})
|
||||
|
||||
module.exports = schema
|
|
@ -1,218 +0,0 @@
|
|||
const schema = require('./schema')
|
||||
const maxActionsPerRun = 30
|
||||
|
||||
module.exports = class Stale {
|
||||
constructor (github, { owner, repo, logger = console, ...config }) {
|
||||
this.github = github
|
||||
this.logger = logger
|
||||
this.remainingActions = 0
|
||||
|
||||
const { error, value } = schema.validate(config)
|
||||
|
||||
this.config = value
|
||||
if (error) {
|
||||
// Report errors to sentry
|
||||
logger.warn({ err: new Error(error), owner, repo }, 'Invalid config')
|
||||
}
|
||||
|
||||
Object.assign(this.config, { owner, repo })
|
||||
}
|
||||
|
||||
async markAndSweep (type) {
|
||||
const { only } = this.config
|
||||
if (only && only !== type) {
|
||||
return
|
||||
}
|
||||
if (!this.getConfigValue(type, 'perform')) {
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.info(this.config, `starting mark and sweep of ${type}`)
|
||||
|
||||
const limitPerRun = this.getConfigValue(type, 'limitPerRun') || maxActionsPerRun
|
||||
this.remainingActions = Math.min(limitPerRun, maxActionsPerRun)
|
||||
|
||||
await this.ensureStaleLabelExists(type)
|
||||
|
||||
const staleItems = (await this.getStale(type)).data.items
|
||||
|
||||
await Promise.all(staleItems.filter(issue => !issue.locked).map(issue => {
|
||||
return this.mark(type, issue)
|
||||
}))
|
||||
|
||||
const { owner, repo } = this.config
|
||||
const daysUntilClose = this.getConfigValue(type, 'daysUntilClose')
|
||||
|
||||
if (daysUntilClose) {
|
||||
this.logger.trace({ owner, repo }, 'Configured to close stale issues')
|
||||
const closableItems = (await this.getClosable(type)).data.items
|
||||
|
||||
await Promise.all(closableItems.filter(issue => !issue.locked).map(issue => {
|
||||
this.close(type, issue)
|
||||
}))
|
||||
} else {
|
||||
this.logger.trace({ owner, repo }, 'Configured to leave stale issues open')
|
||||
}
|
||||
}
|
||||
|
||||
getStale (type) {
|
||||
const staleLabel = this.getConfigValue(type, 'staleLabel')
|
||||
const exemptLabels = this.getConfigValue(type, 'exemptLabels')
|
||||
const exemptProjects = this.getConfigValue(type, 'exemptProjects')
|
||||
const exemptMilestones = this.getConfigValue(type, 'exemptMilestones')
|
||||
const labels = [staleLabel].concat(exemptLabels)
|
||||
const queryParts = labels.map(label => `-label:"${label}"`)
|
||||
queryParts.push(Stale.getQueryTypeRestriction(type))
|
||||
|
||||
queryParts.push(exemptProjects ? 'no:project' : '')
|
||||
queryParts.push(exemptMilestones ? 'no:milestone' : '')
|
||||
|
||||
const query = queryParts.join(' ')
|
||||
let days = this.getConfigValue(type, 'days')
|
||||
if (!days && type === 'pulls') {
|
||||
days = this.getConfigValue(type, 'daysUntilPullRequestStale')
|
||||
}
|
||||
if (!days) {
|
||||
days = this.getConfigValue(type, 'daysUntilStale')
|
||||
}
|
||||
return this.search(type, days, query)
|
||||
}
|
||||
|
||||
getClosable (type) {
|
||||
const staleLabel = this.getConfigValue(type, 'staleLabel')
|
||||
const queryTypeRestriction = Stale.getQueryTypeRestriction(type)
|
||||
const query = `label:"${staleLabel}" ${queryTypeRestriction}`
|
||||
const days = this.getConfigValue(type, 'days') || this.getConfigValue(type, 'daysUntilClose')
|
||||
return this.search(type, days, query)
|
||||
}
|
||||
|
||||
static getQueryTypeRestriction (type) {
|
||||
if (type === 'pulls') {
|
||||
return 'is:pr'
|
||||
} else if (type === 'issues') {
|
||||
return 'is:issue'
|
||||
}
|
||||
throw new Error(`Unknown type: ${type}. Valid types are 'pulls' and 'issues'`)
|
||||
}
|
||||
|
||||
search (type, days, query) {
|
||||
const { owner, repo } = this.config
|
||||
const timestamp = this.since(days).toISOString().replace(/\.\d{3}\w$/, '')
|
||||
|
||||
query = `repo:${owner}/${repo} is:open updated:<${timestamp} ${query}`
|
||||
|
||||
const params = { q: query, sort: 'updated', order: 'desc', per_page: maxActionsPerRun }
|
||||
|
||||
this.logger.info(params, 'searching %s/%s for stale issues', owner, repo)
|
||||
return this.github.search.issues(params)
|
||||
}
|
||||
|
||||
async mark (type, issue) {
|
||||
if (this.remainingActions === 0) {
|
||||
return
|
||||
}
|
||||
this.remainingActions--
|
||||
|
||||
const { owner, repo } = this.config
|
||||
const perform = this.getConfigValue(type, 'perform')
|
||||
const staleLabel = this.getConfigValue(type, 'staleLabel')
|
||||
const markComment = this.getConfigValue(type, 'markComment')
|
||||
const number = issue.number
|
||||
|
||||
if (perform) {
|
||||
this.logger.info('%s/%s#%d is being marked', owner, repo, number)
|
||||
if (markComment) {
|
||||
await this.github.issues.createComment({ owner, repo, number, body: markComment })
|
||||
}
|
||||
return this.github.issues.addLabels({ owner, repo, number, labels: [staleLabel] })
|
||||
} else {
|
||||
this.logger.info('%s/%s#%d would have been marked (dry-run)', owner, repo, number)
|
||||
}
|
||||
}
|
||||
|
||||
async close (type, issue) {
|
||||
if (this.remainingActions === 0) {
|
||||
return
|
||||
}
|
||||
this.remainingActions--
|
||||
|
||||
const { owner, repo } = this.config
|
||||
const perform = this.getConfigValue(type, 'perform')
|
||||
const closeComment = this.getConfigValue(type, 'closeComment')
|
||||
const number = issue.number
|
||||
|
||||
if (perform) {
|
||||
this.logger.info('%s/%s#%d is being closed', owner, repo, number)
|
||||
if (closeComment) {
|
||||
await this.github.issues.createComment({ owner, repo, number, body: closeComment })
|
||||
}
|
||||
return this.github.issues.update({ owner, repo, number, state: 'closed' })
|
||||
} else {
|
||||
this.logger.info('%s/%s#%d would have been closed (dry-run)', owner, repo, number)
|
||||
}
|
||||
}
|
||||
|
||||
async unmark (type, issue) {
|
||||
const { owner, repo } = this.config
|
||||
const perform = this.getConfigValue(type, 'perform')
|
||||
const staleLabel = this.getConfigValue(type, 'staleLabel')
|
||||
const unmarkComment = this.getConfigValue(type, 'unmarkComment')
|
||||
const number = issue.number
|
||||
|
||||
if (perform) {
|
||||
this.logger.info('%s/%s#%d is being unmarked', owner, repo, number)
|
||||
|
||||
if (unmarkComment) {
|
||||
await this.github.issues.createComment({ owner, repo, number, body: unmarkComment })
|
||||
}
|
||||
|
||||
return this.github.issues.removeLabel({ owner, repo, number, name: staleLabel }).catch((err) => {
|
||||
// ignore if it's a 404 because then the label was already removed
|
||||
if (err.code !== 404) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.logger.info('%s/%s#%d would have been unmarked (dry-run)', owner, repo, number)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if at least one exempt label is present.
|
||||
hasExemptLabel (type, issue) {
|
||||
const exemptLabels = this.getConfigValue(type, 'exemptLabels')
|
||||
return issue.labels.some(label => exemptLabels.includes(label.name))
|
||||
}
|
||||
|
||||
hasStaleLabel (type, issue) {
|
||||
const staleLabel = this.getConfigValue(type, 'staleLabel')
|
||||
return issue.labels.map(label => label.name).includes(staleLabel)
|
||||
}
|
||||
|
||||
// returns a type-specific config value if it exists, otherwise returns the top-level value.
|
||||
getConfigValue (type, key) {
|
||||
if (this.config[type] && typeof this.config[type][key] !== 'undefined') {
|
||||
return this.config[type][key]
|
||||
}
|
||||
return this.config[key]
|
||||
}
|
||||
|
||||
async ensureStaleLabelExists (type) {
|
||||
const { owner, repo } = this.config
|
||||
const staleLabel = this.getConfigValue(type, 'staleLabel')
|
||||
|
||||
return this.github.issues.getLabel({ owner, repo, name: staleLabel }).catch(() => {
|
||||
return this.github.issues.createLabel({ owner, repo, name: staleLabel, color: 'ffffff' })
|
||||
})
|
||||
}
|
||||
|
||||
since (days) {
|
||||
const ttl = days * 24 * 60 * 60 * 1000
|
||||
let date = new Date(new Date() - ttl)
|
||||
|
||||
// GitHub won't allow it
|
||||
if (date < new Date(0)) {
|
||||
date = new Date(0)
|
||||
}
|
||||
return date
|
||||
}
|
||||
}
|
1
index.js
1
index.js
|
@ -39,7 +39,6 @@ module.exports = async (robot) => {
|
|||
// require('./bot_scripts/tip-kudos-recipients')(robot)
|
||||
// require('./bot_scripts/check-bot-balance')(robot)
|
||||
require('./bot_scripts/manage-pr-checklist')(robot)
|
||||
require('./bot_scripts/stale/index')(robot)
|
||||
|
||||
// For more information on building apps:
|
||||
// https://probot.github.io/docs/
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
"hashmap": "^2.3.0",
|
||||
"hashset": "0.0.6",
|
||||
"jenkins": "^0.20.1",
|
||||
"joi": "^13.7.0",
|
||||
"mem-cache": "0.0.5",
|
||||
"memjs": "^1.2.0",
|
||||
"probot": "^7.5.0",
|
||||
|
|
43
yarn.lock
43
yarn.lock
|
@ -2532,16 +2532,6 @@ hmac-drbg@^1.0.0:
|
|||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoek@5.x.x:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.4.tgz#0f7fa270a1cafeb364a4b2ddfaa33f864e4157da"
|
||||
integrity sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w==
|
||||
|
||||
hoek@6.x.x:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.2.tgz#99e6d070561839de74ee427b61aa476bd6bddfd6"
|
||||
integrity sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==
|
||||
|
||||
home-or-tmp@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
||||
|
@ -3017,13 +3007,6 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
isemail@3.x.x:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.2.0.tgz#59310a021931a9fb06bbb51e155ce0b3f236832c"
|
||||
integrity sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==
|
||||
dependencies:
|
||||
punycode "2.x.x"
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
|
@ -3407,15 +3390,6 @@ jest@^22.4.4:
|
|||
import-local "^1.0.0"
|
||||
jest-cli "^22.4.4"
|
||||
|
||||
joi@^13.7.0:
|
||||
version "13.7.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-13.7.0.tgz#cfd85ebfe67e8a1900432400b4d03bbd93fb879f"
|
||||
integrity sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==
|
||||
dependencies:
|
||||
hoek "5.x.x"
|
||||
isemail "3.x.x"
|
||||
topo "3.x.x"
|
||||
|
||||
js-sha3@0.5.7:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7"
|
||||
|
@ -4703,16 +4677,16 @@ pump@^3.0.0:
|
|||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||
|
||||
punycode@^2.1.0, punycode@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qs@6.5.2, qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
|
@ -5741,13 +5715,6 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
|||
regex-not "^1.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
topo@3.x.x:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.3.tgz#d5a67fb2e69307ebeeb08402ec2a2a6f5f7ad95c"
|
||||
integrity sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==
|
||||
dependencies:
|
||||
hoek "6.x.x"
|
||||
|
||||
tough-cookie@>=2.3.3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.0.tgz#d2bceddebde633153ff20a52fa844a0dc71dacef"
|
||||
|
|
Loading…
Reference in New Issue