diff --git a/index.js b/index.js index 8ee32f7..8f8cc10 100644 --- a/index.js +++ b/index.js @@ -2,4 +2,4 @@ require = require('esm')(module); const mod = require('./src/index.mjs').default; -module.exports = mod; +mod(); diff --git a/index.mjs b/index.mjs index 5329a9f..ab478a1 100644 --- a/index.mjs +++ b/index.mjs @@ -1,3 +1,3 @@ import mod from './src/index.mjs'; -export default mod; +mod(); diff --git a/src/index.mjs b/src/index.mjs index 8ace411..697146e 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1,3 +1,79 @@ -export default function() { - // es6 module code goes here +import createRepo from './lib/create_repo.mjs'; +import getComments from './lib/get_comments.mjs'; +import getPull from './lib/get_pull.mjs'; +import getCommits from './lib/get_commits.mjs'; +import getCommitStatus from './lib/get_commit_status.mjs'; +import History from './lib/history.mjs'; + +export default async function() { + // parse repo name from cli and create repo instance + const repo = createRepo(process.argv.splice(2)[0]); + + // load the history module + const history = new History(); + + // fetch comment info from event stream, filter for only new comments + const comments = (await getComments(repo, { + body: /build failed/i, + actor: 'elasticmachine', + })).filter(comment => true /*!history.get(comment.id)*/); + + console.log( + comments + .map(comment => { + history.add(comment); // temp + return comment.number; + }) + .join(',') + ); + + // read pull data, filter out any closed pulls + const pulls = (await Promise.all( + comments.map(async comment => { + const pull = await getPull(repo, comment.number); + if (pull.state !== 'open') return false; + return { comment, pull }; + }) + )).filter(Boolean); + + const commits = await Promise.all(pulls.map(pull => getCommits(repo, pull.number, true))); + + const buildStatus = await getCommitStatus(repo, commits[1].sha); + + console.log(buildStatus); + process.exit(); + + Promise.resolve() + + .then(commit => + // get the status of the commit + repo.commits(commit.sha).statuses.fetch() + ) + .then(statuses => { + if (statuses.lastPage) return statuses.lastPage.fetch(); + return statuses; + }) + .then(({ items }) => { + const buildStatus = items.find(item => item.context === 'kibana-ci'); + // status will be one of: error, failure, pending, success + // we should skip the retest if the state is pending or success + console.log(`PR state: ${buildStatus.state}`); + return buildStatus; + }) + .catch(err => console.error(err)); + + /* + + TODO: + + - [ ] keep track of seen comment ids, only process new ones + - [x] check the pr's status and only retest if no longer "Pending" + - [ ] add a retest comment + - POST /repos/:owner/:repo/issues/:number/comments + - [ ] delete the build comment + - DELETE /repos/:owner/:repo/issues/comments/:comment_id + - [ ] delete ALL build failure comments + - [ ] delete the retest comment + + */ } diff --git a/src/lib/create_repo.mjs b/src/lib/create_repo.mjs new file mode 100644 index 0000000..0eb721d --- /dev/null +++ b/src/lib/create_repo.mjs @@ -0,0 +1,18 @@ +/* eslint no-console: 0 */ +import Octokat from 'octokat'; + +function usageError() { + console.error('You must provide a github repo in the form of "owner/repo"'); + process.exit(1); +} + +export default function(repo) { + if (typeof repo !== 'string' || repo.length === 0) usageError(); + + const octo = new Octokat(); + const [owner, name] = repo.split('/'); + + if (!owner || !name) usageError(); + + return octo.repos(owner, name); +} diff --git a/src/lib/get_comments.mjs b/src/lib/get_comments.mjs new file mode 100644 index 0000000..dfa4b6e --- /dev/null +++ b/src/lib/get_comments.mjs @@ -0,0 +1,88 @@ +export default async function getEvents(repo, { body, actor } = {}) { + return cache; + const { items: events } = await repo.events.fetch(); + + return events + .map(comment => { + // only comments created by specific user + if (comment.type !== 'IssueCommentEvent') return false; + if (comment.payload.action !== 'created') return false; + if (comment.actor.login !== actor) return false; + if (body && !body.test(comment.payload.comment.body)) return false; + + return { + id: comment.id, + number: comment.payload.issue.number, + owner: comment.payload.issue.user.login, + labels: comment.payload.issue.labels.map(label => label.name), + comment_id: comment.payload.comment.id, + comment_author: comment.payload.comment.user.login, + comment_body: comment.payload.comment.body, + }; + }) + .filter(Boolean); +} + +const cache = [ { id: '8438445273', +number: 23819, +owner: 'mattapperson', +labels: [ ':Beats', 'beats-cm' ], +comment_id: 430790622, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5174/)\n\n' }, +{ id: '8438364880', +number: 24158, +owner: 'joshdover', +labels: [], +comment_id: 430786140, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5153/)\n\n' }, +{ id: '8438241644', +number: 24104, +owner: 'graphaelli', +labels: [], +comment_id: 430777985, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5150/)\n\n' }, +{ id: '8438206753', +number: 24158, +owner: 'joshdover', +labels: [], +comment_id: 430775791, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5147/)\n\n' }, +{ id: '8438199856', +number: 23819, +owner: 'mattapperson', +labels: [ ':Beats', 'beats-cm' ], +comment_id: 430775402, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5165/)\n\n' }, +{ id: '8438188563', +number: 23648, +owner: 'jbudz', +labels: [ ':Operations' ], +comment_id: 430774756, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5144/)\n\n' }, +{ id: '8438183173', +number: 24038, +owner: 'cqliu1', +labels: [ ':Canvas', ':Home', 'review', 'v6.5.0', 'v7.0.0' ], +comment_id: 430774424, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5145/)\n\n' }, +{ id: '8438163617', +number: 24100, +owner: 'jakelandis', +labels: [ 'v6.5.0', 'v7.0.0' ], +comment_id: 430773251, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5142/)\n\n' }, +{ id: '8438139342', +number: 24153, +owner: 'cchaos', +labels: [ 'backport' ], +comment_id: 430771841, +comment_author: 'elasticmachine', +comment_body: '## :broken_heart: Build Failed\n* [continuous-integration/kibana-ci/pull-request](https://kibana-ci.elastic.co/job/elastic-kibana-pull-request/5143/)\n\n' } ]; \ No newline at end of file diff --git a/src/lib/get_commit_status.mjs b/src/lib/get_commit_status.mjs new file mode 100644 index 0000000..65e2d5b --- /dev/null +++ b/src/lib/get_commit_status.mjs @@ -0,0 +1,27 @@ +function getBuildStatus({ items: statuses }) { + const buildStatus = statuses.find(status => status.context === 'kibana-ci'); + if (!buildStatus) return buildStatus; + return { + id: buildStatus.id, + url: buildStatus.url, + state: buildStatus.state, + }; +} + +export default async function getCommitStatus(repo, sha) { + return cache + const statuses = await repo.commits(sha).statuses.fetch(); + + const buildStatus = await (async stats => { + if (stats.lastPage) { + return getBuildStatus(await stats.lastPage.fetch()); + } + return getBuildStatus(stats); + })(statuses); + + return buildStatus; +} + +const cache = { id: 5666338700, + url: 'https://api.github.com/repos/elastic/kibana/statuses/5f948924cc4fe34c7b9399e685927e93a18d7450', + state: 'pending' }; \ No newline at end of file diff --git a/src/lib/get_commits.mjs b/src/lib/get_commits.mjs new file mode 100644 index 0000000..7852b9d --- /dev/null +++ b/src/lib/get_commits.mjs @@ -0,0 +1,28 @@ +function formatCommit(commit) { + return { + sha: commit.sha, + owner: commit.committer.login, + message: commit.commit.message, + } +} + +function formatCommits(commits, lastOnly) { + if (lastOnly) return formatCommit(commits.pop()); + return commits.map(c => formatCommit(c)); +} + +export default async function getCommits(repo, id, lastOnly = true) { + return cache; + const commits = await repo.pulls(id).commits.fetch(); + + if (commits.lastPage) { + const { items } = await commits.lastPage.fetch(); + return formatCommits(items, lastOnly); + } + + return formatCommits(commits.items, lastOnly); +} + +const cache = { sha: '5f948924cc4fe34c7b9399e685927e93a18d7450', +owner: 'mattapperson', +message: 'Merge branch \'feature/x-pack/management/beats\' of github.com:elastic/kibana into feature/x-pack/management/beats' }; \ No newline at end of file diff --git a/src/lib/get_pull.mjs b/src/lib/get_pull.mjs new file mode 100644 index 0000000..100f447 --- /dev/null +++ b/src/lib/get_pull.mjs @@ -0,0 +1,15 @@ +export default async function getPull(repo, id) { + return cache; + const pull = await repo.pulls(id).fetch(); + return { + id: pull.id, + url: pull.url, + number: pull.number, + state: pull.state, + }; +} + +const cache = { id: 220387229, + url: 'https://api.github.com/repos/elastic/kibana/pulls/23819', + number: 23819, + state: 'open' }; \ No newline at end of file