diff --git a/external-scripts.json b/external-scripts.json index a869d9f..ae9b019 100644 --- a/external-scripts.json +++ b/external-scripts.json @@ -8,5 +8,7 @@ "hubot-maps", "hubot-redis-brain", "hubot-rules", - "hubot-shipit" -] \ No newline at end of file + "hubot-shipit", + "hubot-commit-streak", + "hubot-github-webhook-listener" +] diff --git a/hubot-scripts.json b/hubot-scripts.json deleted file mode 100644 index 0637a08..0000000 --- a/hubot-scripts.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 229ed6e..08cd38b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -201,6 +201,11 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", @@ -231,15 +236,27 @@ "supports-color": "2.0.0" } }, + "cheerio": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", + "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", + "requires": { + "css-select": "1.0.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.8.3", + "lodash": "3.10.1" + } + }, "cline": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/cline/-/cline-0.8.2.tgz", "integrity": "sha1-6RHnQaCtLiTSnm+rLPifoyLVnHY=" }, "coffee-script": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", - "integrity": "sha1-Y1XTLPGwTN/2tITl5xF4Ky8MOb4=" + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==" }, "colors": { "version": "1.0.3", @@ -433,6 +450,22 @@ "uid-safe": "2.1.4" } }, + "css-select": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", + "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", + "requires": { + "boolbase": "1.0.0", + "css-what": "1.0.0", + "domutils": "1.4.3", + "nth-check": "1.0.1" + } + }, + "css-what": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", + "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" + }, "csurf": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.8.3.tgz", @@ -487,6 +520,48 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", + "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", + "requires": { + "domelementtype": "1.3.0" + } + }, + "dotenv": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", + "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=" + }, "double-ended-queue": { "version": "2.1.0-0", "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", @@ -506,6 +581,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz", "integrity": "sha1-ag18YiHkkP7v2S7D9EHJzozQl/Q=" }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, "errorhandler": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.4.3.tgz", @@ -549,6 +629,19 @@ } } }, + "es6-promise": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.2.tgz", + "integrity": "sha512-LSas5vsuA6Q4nEdf9wokY5/AJYXry98i0IzXsv49rYsgDGDNDPbqAYR1Pe23iFxygfbGZNR/5VrHXBCh2BhvUQ==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "4.2.2" + } + }, "escape-html": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", @@ -732,6 +825,57 @@ } } }, + "github": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/github/-/github-13.1.0.tgz", + "integrity": "sha512-xCen2waPsGF4MT9nE4gDwXYSD+ktmDyhQX3l/7cJcfZ0H6hjetkTZh8vzX39q/8sLtRS6iA01Mt5UIGQG9qJng==", + "requires": { + "debug": "3.1.0", + "dotenv": "4.0.0", + "https-proxy-agent": "2.1.1", + "is-stream": "1.1.0", + "lodash": "4.17.4", + "proxy-from-env": "1.0.0", + "url-template": "2.0.8" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "requires": { + "es6-promisify": "5.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "https-proxy-agent": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.1.1.tgz", + "integrity": "sha512-LK6tQUR/VOkTI6ygAfWUKKP95I+e6M1h7N3PncGu1CATHCnex+CAv9ttR0lbHu1Uk2PXm/WoAHFo6JCGwMjVMw==", + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "har-validator": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", @@ -774,6 +918,34 @@ "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.3.0", + "domutils": "1.5.1", + "entities": "1.0.0", + "readable-stream": "1.1.14" + }, + "dependencies": { + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + } + } + }, "http-errors": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", @@ -817,6 +989,31 @@ "log": "1.4.0", "optparse": "1.0.4", "scoped-http-client": "0.11.0" + }, + "dependencies": { + "coffee-script": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", + "integrity": "sha1-Y1XTLPGwTN/2tITl5xF4Ky8MOb4=" + } + } + }, + "hubot-commit-streak": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/hubot-commit-streak/-/hubot-commit-streak-1.0.7.tgz", + "integrity": "sha1-CY0CylyiS09dCV6CHf45khdHf5U=", + "requires": { + "bluebird": "2.7.1", + "cheerio": "0.19.0", + "lodash": "3.10.1", + "request": "2.76.0" + }, + "dependencies": { + "bluebird": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.7.1.tgz", + "integrity": "sha1-ObTHuvUSNosKKOjQgeGzX6TDOrE=" + } } }, "hubot-diagnostics": { @@ -824,6 +1021,15 @@ "resolved": "https://registry.npmjs.org/hubot-diagnostics/-/hubot-diagnostics-0.0.2.tgz", "integrity": "sha1-oEKyzcn9jwFYPhBWWBMKbYua84Y=" }, + "hubot-github-webhook-listener": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hubot-github-webhook-listener/-/hubot-github-webhook-listener-0.9.1.tgz", + "integrity": "sha1-wSEW9pgYYfbe+ImxUGZPM3h0Uis=", + "requires": { + "querystring": "0.2.0", + "url": "0.10.3" + } + }, "hubot-google-images": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/hubot-google-images/-/hubot-google-images-0.2.7.tgz", @@ -927,6 +1133,11 @@ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1119,6 +1330,14 @@ "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "requires": { + "boolbase": "1.0.0" + } + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -1184,6 +1403,11 @@ "ipaddr.js": "1.0.5" } }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=" + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -1194,6 +1418,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-2.2.5.tgz", "integrity": "sha1-EIirr53MCuWuRbcJ5sa1iIsjkjw=" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -1617,11 +1846,32 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, "url-join": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", "integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g=" }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + }, "utils-merge": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", diff --git a/package.json b/package.json index 669d524..704b9f3 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,12 @@ "author": "Pedro Pombeiro ", "description": "A simple helpful robot for your Company", "dependencies": { + "coffee-script": "^1.12.7", + "github": "^13.1.0", "hubot": "^2.19.0", + "hubot-commit-streak": "^1.0.7", "hubot-diagnostics": "0.0.2", + "hubot-github-webhook-listener": "^0.9.1", "hubot-google-images": "^0.2.7", "hubot-google-translate": "^0.2.1", "hubot-help": "^0.2.2", diff --git a/scripts/assign-new-pr-to-review.coffee b/scripts/assign-new-pr-to-review.coffee new file mode 100644 index 0000000..fc43745 --- /dev/null +++ b/scripts/assign-new-pr-to-review.coffee @@ -0,0 +1,122 @@ +# Description: +# Script that listens to new GitHub pull requests +# and assigns them to the REVIEW column on the "Pipeline for QA" project + +module.exports = (robot) -> + + projectBoardName = "Pipeline for QA" + reviewColumnName = "REVIEW" + notifyRoomName = "core" + + GitHubApi = require("github") + + robot.on "github-repo-event", (repo_event) -> + githubPayload = repo_event.payload + + switch(repo_event.eventType) + when "pull_request" + # Make sure we don't listen to our own messages + return if equalsRobotName(robot, githubPayload.pull_request.user.login) + + token = process.env.HUBOT_GITHUB_TOKEN + return console.error "No Github token provided to Hubot" unless token + + action = githubPayload.action + if action == "opened" + # A new PR was opened + github = new GitHubApi { version: "3.0.0" } + github.authenticate({ + type: "token", + token: token + }) + + assignPullRequestToReview github, githubPayload, robot + +assignPullRequestToReview = (github, githubPayload, robot) -> + ownerName = githubPayload.repository.owner.login + repoName = githubPayload.repository.name + prNumber = githubPayload.pull_request.number + robot.logger.info "assignPullRequestToReview - Handling Pull Request ##{prNumber} on repo #{ownerName}/#{repoName}" + + # Fetch repo projects + # TODO: The repo project and project column info should be cached in order to improve performance and reduce roundtrips + github.projects.getRepoProjects { + owner: ownerName, + repo: repoName, + state: "open" + }, (err, ghprojects) -> + if err + robot.logger.error "Couldn't fetch the github projects for repo: #{err}", + ownerName, repoName + return + + # Find "Pipeline for QA" project + project = findProject ghprojects.data, projectBoardName + if !project + robot.logger.warn "Couldn't find project #{projectBoardName}" + + " in repo #{ownerName}/#{repoName}" + return + + robot.logger.debug "Fetched #{project.name} project (#{project.id})" + + # Fetch REVIEW column ID + github.projects.getProjectColumns { project_id: project.id }, (err, ghcolumns) -> + if err + robot.logger.error "Couldn't fetch the github columns for project: #{err}", + ownerName, repoName, project.id + return + + column = findColumn ghcolumns.data, reviewColumnName + if !column + robot.logger.warn "Couldn't find #{projectBoardName} column" + + " in project #{project.name}" + return + + robot.logger.debug "Fetched #{column.name} column (#{column.id})" + + # Create project card for the PR in the REVIEW column + github.projects.createProjectCard { + column_id: column.id, + content_type: 'PullRequest', + content_id: githubPayload.pull_request.id + }, (err, ghcard) -> + if err + robot.logger.error "Couldn't create project card for the PR: #{err}", + column.id, githubPayload.pull_request.id + return + + robot.logger.debug "Created card: #{ghcard.data.url}", ghcard.data.id + + # Send message to Slack + robot.messageRoom notifyRoomName, + "Moved PR #{githubPayload.pull_request.number} to " + + "#{reviewColumnName} in #{projectBoardName} project" + + +findProject = (projects, name) -> + for idx, project of projects + return project if project.name == name + return null + +findColumn = (columns, name) -> + for idx, column of columns + return column if column.name == name + return null + +equalsRobotName = (robot, str) -> + return getRegexForRobotName(robot).test(str) + +RegExp cachedRobotNameRegex = null +getRegexForRobotName = (robot) -> + # This comes straight out of Hubot's Robot.coffee + # - they didn't get a nice way of extracting that method though + if !cachedRobotNameRegex + name = robot.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + + if robot.alias + alias = robot.alias.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + namePattern = "^\\s*[@]?(?:#{alias}|#{name})" + else + namePattern = "^\\s*[@]?#{name}" + cachedRobotNameRegex = new RegExp(namePattern, 'i') + return cachedRobotNameRegex