├── .gitignore ├── README.md ├── .travis.yml ├── script ├── test └── bootstrap ├── index.coffee ├── package.json ├── LICENSE └── src └── jira-issues.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hubot Jira 2 | 3 | A hubot script to list jira issues, statuses and move issues 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | notifications: 6 | email: false 7 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # bootstrap environment 4 | source script/bootstrap 5 | 6 | mocha --compilers coffee:coffee-script 7 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure everything is development forever 4 | export NODE_ENV=development 5 | 6 | # Load environment specific environment variables 7 | if [ -f .env ]; then 8 | source .env 9 | fi 10 | 11 | if [ -f .env.${NODE_ENV} ]; then 12 | source .env.${NODE_ENV} 13 | fi 14 | 15 | npm install 16 | 17 | # Make sure coffee and mocha are on the path 18 | export PATH="node_modules/.bin:$PATH" 19 | -------------------------------------------------------------------------------- /index.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | 4 | module.exports = (robot, scripts) -> 5 | scriptsPath = path.resolve(__dirname, 'src') 6 | fs.exists scriptsPath, (exists) -> 7 | if exists 8 | for script in fs.readdirSync(scriptsPath) 9 | if scripts? and '*' not in scripts 10 | robot.loadFile(scriptsPath, script) if script in scripts 11 | else 12 | robot.loadFile(scriptsPath, script) 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hubot-jira", 3 | "description": "An updated version of the hubot jira script", 4 | "version": "1.0.2", 5 | "authors": [ 6 | "David Newell ", 7 | "stuartf" 8 | ], 9 | "license": "MIT", 10 | "keywords": [ 11 | "hubot", 12 | "hubot-scripts", 13 | "jira" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git@github.com:rustedgrail/hubot-jira.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/rustedgrail/hubot-jira/issues" 21 | }, 22 | "dependencies": { 23 | "coffee-script": "~1.6" 24 | }, 25 | "main": "index.coffee", 26 | "directories": { 27 | "test": "test" 28 | }, 29 | "scripts": { 30 | "test": "echo \"Error: no test specified\" && exit 1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 hubot-scripts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/jira-issues.coffee: -------------------------------------------------------------------------------- 1 | # Description: 2 | # Looks up jira issues when they're mentioned in chat 3 | # 4 | # Will ignore users set in HUBOT_JIRA_IGNORE_USERS (by default, JIRA and GitHub). 5 | # 6 | # Dependencies: 7 | # None 8 | # 9 | # Configuration: 10 | # HUBOT_JIRA_URL (format: "https://jira-domain.com:9090") 11 | # HUBOT_JIRA_IGNORECASE (optional; default is "true") 12 | # HUBOT_JIRA_USERNAME (optional) 13 | # HUBOT_JIRA_PASSWORD (optional) 14 | # HUBOT_JIRA_IGNORE_USERS (optional, format: "user1|user2", default is "jira|github") 15 | # 16 | # Commands: 17 | # hubot move jira to - Changes the status of to 18 | # hubot jira status - List the available statuses 19 | # 20 | # Author: 21 | # rustedgrail 22 | # stuartf 23 | 24 | module.exports = (robot) -> 25 | cache = [] 26 | 27 | # In case someone upgrades form the previous version, we'll default to the 28 | # previous behavior. 29 | jiraUrl = process.env.HUBOT_JIRA_URL || "https://#{process.env.HUBOT_JIRA_DOMAIN}" 30 | jiraUsername = process.env.HUBOT_JIRA_USERNAME 31 | jiraPassword = process.env.HUBOT_JIRA_PASSWORD 32 | 33 | if jiraUsername != undefined && jiraUsername.length > 0 34 | auth = "#{jiraUsername}:#{jiraPassword}" 35 | 36 | jiraIgnoreUsers = process.env.HUBOT_JIRA_ISSUES_IGNORE_USERS 37 | if jiraIgnoreUsers == undefined 38 | jiraIgnoreUsers = "jira|github" 39 | 40 | robot.http(jiraUrl + "/rest/api/2/project") 41 | .auth(auth) 42 | .get() (err, res, body) -> 43 | if err 44 | errmsg = "Unable to initialize hubot-jira:\n #{err}" 45 | robot.logger.error errmsg 46 | return 47 | if res.statusCode != 200 48 | errmsg = "Unable to initialize hubot-jira: #{res.statusCode}:#{res.statusMessage}" 49 | robot.logger.error errmsg 50 | return 51 | json = JSON.parse(body) 52 | jiraPrefixes = ( entry.key for entry in json ) 53 | reducedPrefixes = jiraPrefixes.reduce (x,y) -> x + "-|" + y 54 | jiraPattern = "/\\b(" + reducedPrefixes + "-)(\\d+)\\b/g" 55 | ic = process.env.HUBOT_JIRA_IGNORECASE 56 | if ic == undefined || ic == "true" 57 | jiraPattern += "i" 58 | jiraPattern = eval(jiraPattern) 59 | 60 | robot.hear /move jira (.+) to (.+)/, (msg) -> 61 | issue = msg.match[1] 62 | msg.send "Getting transitions for #{issue}" 63 | robot.http(jiraUrl + "/rest/api/2/issue/#{issue}/transitions") 64 | .auth(auth).get() (err, res, body) -> 65 | jsonBody = JSON.parse(body) 66 | status = jsonBody.transitions.filter (trans) -> 67 | trans.name.toLowerCase() == msg.match[2].toLowerCase() 68 | if status.length == 0 69 | trans = jsonBody.transitions.map (trans) -> trans.name 70 | msg.send "The only transitions of #{issue} are: #{trans.reduce (t, s) -> t + "," + s}" 71 | return 72 | msg.send "Changing the status of #{issue} to #{status[0].name}" 73 | robot.http(jiraUrl + "/rest/api/2/issue/#{issue}/transitions") 74 | .header("Content-Type", "application/json").auth(auth).post(JSON.stringify({ 75 | transition: status[0] 76 | })) (err, res, body) -> 77 | msg.send if res.statusCode == 204 then "Success!" else body 78 | 79 | robot.hear /jira status/, (msg) -> 80 | robot.http(jiraUrl + "/rest/api/2/status") 81 | .auth(auth).get() (err, res, body) -> 82 | response = "/code " 83 | for status in JSON.parse(body) 84 | response += status.name + ": " + status.description + '\n' 85 | msg.send response 86 | 87 | robot.hear jiraPattern, (msg) -> 88 | return if msg.message.user.name.match(new RegExp(jiraIgnoreUsers, "gi")) 89 | return if msg.message.text.match(new RegExp(/move jira (.+) to (.+)/)) 90 | 91 | for i in msg.match 92 | issue = i.toUpperCase() 93 | now = new Date().getTime() 94 | if cache.length > 0 95 | cache.shift() until cache.length is 0 or cache[0].expires >= now 96 | 97 | msg.send item.message for item in cache when item.issue is issue 98 | 99 | if cache.length == 0 or (item for item in cache when item.issue is issue).length == 0 100 | robot.http(jiraUrl + "/rest/api/2/issue/" + issue) 101 | .auth(auth) 102 | .get() (err, res, body) -> 103 | try 104 | json = JSON.parse(body) 105 | key = json.key 106 | 107 | message = "[" + key + "] " + json.fields.summary 108 | message += '\nStatus: '+json.fields.status.name 109 | 110 | if (json.fields.assignee == null) 111 | message += ', unassigned' 112 | else if ('value' of json.fields.assignee or 'displayName' of json.fields.assignee) 113 | if (json.fields.assignee.name == "assignee" and json.fields.assignee.value.displayName) 114 | message += ', assigned to ' + json.fields.assignee.value.displayName 115 | else if (json.fields.assignee and json.fields.assignee.displayName) 116 | message += ', assigned to ' + json.fields.assignee.displayName 117 | else 118 | message += ', unassigned' 119 | message += ", rep. by "+json.fields.reporter.displayName 120 | if json.fields.fixVersions and json.fields.fixVersions.length > 0 121 | message += ', fixVersion: '+json.fields.fixVersions[0].name 122 | else 123 | message += ', fixVersion: NONE' 124 | 125 | if json.fields.priority and json.fields.priority.name 126 | message += ', priority: ' + json.fields.priority.name 127 | 128 | urlRegex = new RegExp(jiraUrl + "[^\\s]*" + key) 129 | if not msg.message.text.match(urlRegex) 130 | message += "\n" + jiraUrl + "/browse/" + key 131 | 132 | msg.send message 133 | cache.push({issue: issue, expires: now + 120000, message: message}) 134 | catch error 135 | try 136 | msg.send "[*ERROR*] " + json.errorMessages[0] 137 | catch reallyError 138 | msg.send "[*ERROR*] " + reallyError 139 | --------------------------------------------------------------------------------