Skip to content

Commit

Permalink
feat: complete overall flow of app
Browse files Browse the repository at this point in the history
  • Loading branch information
jakebolam committed Jan 10, 2019
1 parent c567478 commit 4b7ebb1
Show file tree
Hide file tree
Showing 15 changed files with 528 additions and 208 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
module.exports = {
extends: ['@tophat', '@tophat/eslint-config/jest']
extends: ['@tophat', '@tophat/eslint-config/jest'],
rules: {
camelcase: 0
}
}
30 changes: 30 additions & 0 deletions src/CommentReply/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Queues all replies, and sends in one message
*/
class CommentReply {
constructor({ context }) {
this.context = context
this.message = ''
}

replyingToWho() {
return this.context.payload.comment.user.login
}

replyingToWhere() {
return this.context.payload.comment.html_url
}

reply(message) {
this.message = `\n\n${message}`
}

async send() {
const fromUser = this.replyingToWho()
const body = `@${fromUser} ${this.message}`
const issueComment = this.context.issue({ body })
return this.context.github.issues.createComment(issueComment)
}
}

module.exports = CommentReply
43 changes: 43 additions & 0 deletions src/ContentFiles/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { generate: generateContentFile } = require('all-contributors-cli')

/*
* Fetches, stores, generates, and updates the readme content files for the contributors list
*/
class ContentFiles {
constructor({ context, repository }) {
this.context = context
this.repository = repository
this.contentFilesByPath = null
}

async fetch(optionsConfig) {
const options = optionsConfig.get()
if (Array.isArray(options.files)) {
this.contentFilesByPath = this.repository.getMultipleFileContents(
options.files,
)
} else {
this.contentFilesByPath = this.repository.getMultipleFileContents([
'README.md',
])
}
}

async generate(optionsConfig) {
const options = optionsConfig.get()
const newReadmeFileContentsByPath = {}
Object.entires(this.contentFilesByPath).forEach(
([filePath, fileContents]) => {
const newFileContents = generateContentFile(
options,
options.contributors,
fileContents,
)
newReadmeFileContentsByPath[filePath] = newFileContents
},
)
this.contentFilesByPath = newReadmeFileContentsByPath
}
}

module.exports = ContentFiles
79 changes: 79 additions & 0 deletions src/OptionsConfig/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const ALL_CONTRIBUTORS_RC = '.all-contributorsrc'

const { addContributorWithDetails } = require('all-contributors-cli')

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

class OptionsConfig {
constructor({ context, repository, commentReply }) {
this.context = context
this.repository = repository
this.commentReply = commentReply
this.options = null
}

async fetch() {
try {
const rawOptionsFileContent = await this.repository.getFileContents(
ALL_CONTRIBUTORS_RC,
)
try {
const optionsConfig = JSON.parse(rawOptionsFileContent)
this.options = optionsConfig
return optionsConfig
} catch (error) {
if (error instanceof SyntaxError) {
this.commentReply.reply(
`This project's configuration file has malformed JSON: ${ALL_CONTRIBUTORS_RC}. Error:: ${
error.message
}`,
)
this.context.log(error)
error.handled = true
throw error
}
}
} catch (error) {
if (error instanceof ResourceNotFoundError) {
this.commentReply
.reply(`This project is not yet setup for [all-contributors](https://github.com/all-contributors/all-contributors).\n
You will need to first setup [${
this.repository.repo
}](https://github.com/${this.repository.getFullname()}) using the [all-contributors-cli](https://github.com/all-contributors/all-contributors-cli) tool.`)
error.handled = true
throw error
}
}
}

get() {
return this.options
}

getRaw() {
return JSON.stringify(this.options)
}

getPath() {
return ALL_CONTRIBUTORS_RC
}

async addContributor({ login, contributions, name, avatar_url, profile }) {
const newContributorsList = await addContributorWithDetails({
options: this.options,
login,
contributions,
name,
avatar_url,
profile,
})
const newOptions = {
...this.options,
contributors: newContributorsList,
}
this.options = newOptions
return newOptions
}
}

module.exports = OptionsConfig
24 changes: 14 additions & 10 deletions src/repository/index.js → src/Repository/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
class ResourceNotFoundError extends Error {
constructor(filepath, fullRepoName) {
super(`File ${filepath} was not found in repository (${fullRepoName}).`)
this.name = this.constructor.name
}
}
const { ResourceNotFoundError } = require('../utils/errors')

class Repository {
constructor(context) {
Expand Down Expand Up @@ -38,6 +33,7 @@ class Repository {
}

async getMultipleFileContents(filePathsArray) {
// TODO: can probably optimise this instead of sending a request per file
const repository = this
if (filePathsArray.length > 5) {
throw new Error(`Cannot fetch more than 5 files.`)
Expand All @@ -58,9 +54,17 @@ class Repository {

return
}
}

module.exports = {
Repository,
ResourceNotFoundError,
async createPullRequest({title, body, fileContentsByPath}) {
// TODO: Create branch, update files
// GET master state when we read files
// https://octokit.github.io/rest.js/#api-Git-createRef
// https://octokit.github.io/rest.js/#api-Repos-updateFile

// TODO: post pull request
// https://octokit.github.io/rest.js/#api-Pulls-createFromIssue
return pullRequestNumber
}
}

module.exports = Repository
185 changes: 3 additions & 182 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,189 +1,10 @@
/* eslint-disable camelcase */

const {
addContributorWithDetails,
generate: generateContentFile,
} = require('all-contributors-cli')

const { Repository, ResourceNotFoundError } = require('./repository')
// const parseComment = require('./parse-comment')

const GIHUB_BOT_NAME = '@AllContributorsBot'
const ALL_CONTRIBUTORS_RC = '.all-contributorsrc'

async function createComment({ context, body }) {
const issueComment = context.issue({ body })
return context.github.issues.createComment(issueComment)
}

async function getReadmeFileContentsByPath({ repository, files }) {
if (Array.isArray(files)) {
return repository.getMultipleFileContents(files)
} else {
// default 'files' is ['README.md']
return repository.getMultipleFileContents(['README.md'])
}
}

async function getUserDetials({ context, username }) {
// TODO: optimzation, if commenting user is the user we're adding we can avoid an api call
// const commentUser = context.payload.comment.user.login
// if (user === commentUser) {
// return {
// name: context.payload.comment.user.name
// avatarUrl: context.payload.comment.avatar_url
// profile:
// }
// }

const result = await context.github.users.getByUsername({ username })
const { avatar_url, blog, html_url, name } = result.data

return {
name: name || username,
avatar_url,
profile: blog || html_url,
}
}

async function addContributor({
options,
login,
contributions,
name,
avatar_url,
profile,
}) {
const newContributorsList = await addContributorWithDetails({
options,
login,
contributions,
name,
avatar_url,
profile,
})
return { ...options, contributors: newContributorsList }
}

async function generateContentFiles({ options, readmeFileContentsByPath }) {
const newReadmeFileContentsByPath = {}
Object.entires(readmeFileContentsByPath).forEach(
([filePath, fileContents]) => {
const newFileContents = generateContentFile(
options,
options.contributors,
fileContents,
)
newReadmeFileContentsByPath[filePath] = newFileContents
},
)
return newReadmeFileContentsByPath
}

async function processNewIssueComment(context) {
if (context.isBot) {
context.log('From a bot, exiting')
return
}

const fromUser = context.payload.comment.user.login
const commentBody = context.payload.comment.body
const hasMentionedBotName = commentBody.includes(GIHUB_BOT_NAME)

if (!hasMentionedBotName) {
context.log('Message not for us, exiting')
return
}

const repository = new Repository(context)

let optionsFileContent
try {
const rawOptionsFileContent = await repository.getFileContents(
ALL_CONTRIBUTORS_RC,
)
optionsFileContent = JSON.parse(rawOptionsFileContent)
// TODO: if JSON has error report that
} catch (error) {
if (error instanceof ResourceNotFoundError) {
await createComment({
context,
body: `@${fromUser} This project is not yet setup for [all-contributors](https://github.com/all-contributors/all-contributors).\n
You will need to first setup [${
repository.repo
}](https://github.com/${repository.getFullname()}) using the [all-contributors-cli](https://github.com/all-contributors/all-contributors-cli) tool.`,
})
context.log(error)
return
}
}
context.log('Options Content')
context.log(optionsFileContent)

// TODO parse comment and gain intentions
// const { who, contributions } = parseComment(commentBody)
// We had trouble reading your comment. Basic usage:\n\n\`@${GIHUB_BOT_NAME} please add jakebolam for code\`
const who = 'jakebolam'
const contributions = ['code']

const { name, avatar_url, profile } = await getUserDetials({
context,
username: who,
})

const newOptionsContent = await addContributor({
options: optionsFileContent,
login: who,
contributions,
name,
avatar_url,
profile,
})
context.log('New Options Content')
context.log(newOptionsContent)

const readmeFileContentsByPath = await getReadmeFileContentsByPath({
repository,
files: optionsFileContent.files,
})

context.log('Readme file contents by path')
context.log(readmeFileContentsByPath)

const newReadmeFileContentsByPath = await generateContentFiles({
options: newOptionsContent,
readmeFileContentsByPath,
})
context.log('New readme file contents by path')
context.log(newReadmeFileContentsByPath)

// TODO: Create branch, update files
// GET master state when we read files
// https://octokit.github.io/rest.js/#api-Git-createRef
// https://octokit.github.io/rest.js/#api-Repos-updateFile

// TODO: post pull request
// https://octokit.github.io/rest.js/#api-Pulls-createFromIssue

// TODO: Comment back with link to pull request
}
const processIssueComment = require('./processIssueComment')

module.exports = app => {
// issueComment.edited
// Issue comments and PR comments both create issue_comment events
app.on('issue_comment.created', async context => {
try {
await processNewIssueComment(context)
} catch (error) {
await createComment({
context,
body: `@${
context.payload.comment.user.login
} we had trouble processing your request. \n\nError: ${
error.message
}`,
})
throw error
}
app.log(context)
await processIssueComment(context)
})
}
Loading

0 comments on commit 4b7ebb1

Please # to comment.