Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support adding multiple users in a single message #127

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

252 changes: 252 additions & 0 deletions src/tasks/processIssueComment/probot-processIssueComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
const Analytics = require('../../utils/Analytics')
const CommentReply = require('./CommentReply')
const Repository = require('./Repository')
const OptionsConfig = require('./OptionsConfig')
const ContentFiles = require('./ContentFiles')

const getUserDetails = require('./utils/getUserDetails')
const parseComment = require('./utils/parse-comment')

const {
AllContributorBotError,
BranchNotFoundError,
ResourceNotFoundError,
} = require('./utils/errors')

const getSafeRef = require('./utils/git/getSafeRef')

async function processAddContributors({
context,
commentReply,
repository,
optionsConfig,
contributors,
branchName,
}) {

const usersAdded = []
const usersMissed = []

async function addContributor(who, contributions) {
if (contributions.length === 0) {
context.log.debug(`No contributions for ${who}`)
usersMissed.push(who)
return
}

// TODO: wrap this blog in a try catch, if one user fails, don't fail the whole pull request
const { name, avatar_url, profile } = await getUserDetails({
github: context.github,
username: who,
})

await optionsConfig.addContributor({
login: who,
contributions,
name,
avatar_url,
profile,
})

usersAdded.push(who)
}

// TODO: throttle max paralle requests
const addContributorPromises = contributors.map(function(contributor) {
return addContributor((contributor.who, contributor.contributions))
})

await Promise.all(addContributorPromises)

if (usersAdded === 0) {
return commentReply.reply(
`I couldn't determine any contributions for ${usersAdded.join(', ')}.
Did you specify any contributions? Please make sure to use [valid contribution names](https://allcontributors.org/docs/en/emoji-key).`,
)
tenshiAMD marked this conversation as resolved.
Show resolved Hide resolved

const contentFiles = new ContentFiles({
repository,
})
await contentFiles.fetch(optionsConfig)
if (optionsConfig.getOriginalSha() === undefined) {
contentFiles.init()
}
contentFiles.generate(optionsConfig)
const filesByPathToUpdate = contentFiles.get()
filesByPathToUpdate[optionsConfig.getPath()] = {
content: optionsConfig.getRaw(),
originalSha: optionsConfig.getOriginalSha(),
}

const pullRequestBodyAdds = `Adds the following contributors:
- ${usersAdded.join('\n- ')}\n\n`

const pullRequestBodyMissed = `Unable to determine contributions for the following contributors, these were excluded from this PR:
- ${usersMissed.join('\\n- ')}\n\n`

const pullRequestBodyRequester = `This was requested by ${commentReply.replyingToWho()} [in this comment](${commentReply.replyingToWhere()})`

const {
pullRequestURL,
pullCreated,
} = await repository.createPullRequestFromFiles({
title: `docs: add new contributors`,
body: `${pullRequestBodyAdds}.\n\n
${usersMissed > 0 ? pullRequestBodyMissed : ''}
${pullRequestBodyRequester}
`,
filesByPath: filesByPathToUpdate,
branchName,
})

if (pullCreated) {
commentReply.reply(
`I've put up [a pull request](${pullRequestURL}) to add new contributors! :tada:`,
)
return
}
// Updated
commentReply.reply(
`I've updated [the pull request](${pullRequestURL}) to add contributors! :tada:`,
)
}

async function setupRepository({ context, branchName }) {
const defaultBranch = context.payload.repository.default_branch
const repository = new Repository({
...context.repo(),
github: context.github,
defaultBranch,
log: context.log,
})

try {
await repository.getRef(branchName)
context.log.info(
`Branch: ${branchName} EXISTS, will work from this branch`,
)
repository.setBaseBranch(branchName)
} catch (error) {
if (error instanceof BranchNotFoundError) {
context.log.info(
`Branch: ${branchName} DOES NOT EXIST, will work from default branch`,
)
} else {
throw error
}
}

return repository
}

async function setupOptionsConfig({ repository }) {
const optionsConfig = new OptionsConfig({
repository,
})

try {
await optionsConfig.fetch()
} catch (error) {
if (error instanceof ResourceNotFoundError) {
optionsConfig.init()
} else {
throw error
}
}

return optionsConfig
}

async function probotProcessIssueComment({ context, commentReply, analytics }) {
const commentBody = context.payload.comment.body
analytics.track('processComment', {
commentBody: commentBody,
})
const { action, contributors } = parseComment(commentBody)

if (action === 'add') {
analytics.track('addContributors', {
who: commentBody,
contributors,
})

const whos = contributors.map(({ who }) => who).join('--')
const safeWho = getSafeRef(whos)
// TODO: max length on branch name?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were to have a max length, what would it be?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest 255 chars. While it's true that people could limit themselves to using GitHub's ui for everything, and with recent changes, it's possible to renaming existing branches, branch names are generally stored in file systems and can be subject to the whims of file systems, some of whom don't particularly like very long file names (I'm not suggesting an 11 character limit...).

I suspect that would give you ~16-20 contributors, which is quite a lot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsoref That makes sense, yeah 255 chars seems good to me.

// TODO: should this be the branch name
const branchName = `all-contributors/add-${safeWho}`

const repository = await setupRepository({
context,
branchName,
})
const optionsConfig = await setupOptionsConfig({ repository })

await processAddContributors({
context,
commentReply,
repository,
optionsConfig,
contributors,
branchName,
})
analytics.track('processCommentSuccess')
return
}

analytics.track('unknownIntent', {
action,
})

commentReply.reply(`I could not determine your intention.`)
commentReply.reply(
`Basic usage: @all-contributors please add @jakebolam for code, doc and infra`,
)
commentReply.reply(
`For other usages see the [documentation](https://allcontributors.org/docs/en/bot/usage)`,
)
return
}

async function probotProcessIssueCommentSafe({ context }) {
const analytics = new Analytics({
...context.repo(),
user: context.payload.sender.login,
log: context.log,
})
const commentReply = new CommentReply({ context })
try {
await probotProcessIssueComment({ context, commentReply, analytics })
} catch (error) {
if (error instanceof AllContributorBotError) {
context.log.info(error)
commentReply.reply(error.message)
analytics.track('errorKnown', {
errorMessage: error.message,
})
} else {
context.log.error(error)
commentReply.reply(
`We had trouble processing your request. Please try again later.`,
)
analytics.track('errorUnKnown', {
errorMessage: error.message,
})
throw error
}
} finally {
await commentReply.send()
await analytics.finishQueue()
}
}

function processIssueCommentApp(app) {
// issueComment.edited
// Issue comments and PR comments both create issue_comment events
app.on('issue_comment.created', async context => {
app.log.trace(context)
await probotProcessIssueCommentSafe({ context })
})
}

module.exports = processIssueCommentApp
Loading