Skip to content
This repository
file 239 lines (195 sloc) 7.424 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
# Description:
# Messing with the JIRA REST API
#
# Dependencies:
# None
#
# Configuration:
# HUBOT_JIRA_URL
# HUBOT_JIRA_USER
# HUBOT_JIRA_PASSWORD
# Optional environment variables:
# HUBOT_JIRA_USE_V2 (defaults to "true", set to "false" for JIRA earlier than 5.0)
# HUBOT_JIRA_MAXLIST
# HUBOT_JIRA_ISSUEDELAY
# HUBOT_JIRA_IGNOREUSERS
#
# Commands:
# <Project Key>-<Issue ID> - Displays information about the JIRA ticket (if it exists)
# hubot show watchers for <Issue Key> - Shows watchers for the given JIRA issue
# hubot search for <JQL> - Search JIRA with JQL
# hubot save filter <JQL> as <name> - Save JIRA JQL query as filter in the brain
# hubot use filter <name> - Use a JIRA filter from the brain
# hubot show filter(s) - Show all JIRA filters
# hubot show filter <name> - Show a specific JIRA filter
#
# Author:
# codec

class IssueFilters
  constructor: (@robot) ->
    @cache = []

    @robot.brain.on 'loaded', =>
      jqls_from_brain = @robot.brain.data.jqls
      # only overwrite the cache from redis if data exists in redis
      if jqls_from_brain
        @cache = jqls_from_brain

  add: (filter) ->
    @cache.push filter
    @robot.brain.data.jqls = @cache

  delete: (name) ->
    result = []
    @cache.forEach (filter) ->
      if filter.name.toLowerCase() isnt name.toLowerCase()
        result.push filter

    @cache = result
    @robot.brain.data.jqls = @cache

  get: (name) ->
    result = null

    @cache.forEach (filter) ->
      if filter.name.toLowerCase() is name.toLowerCase()
        result = filter

    result
  all: ->
    return @cache

class IssueFilter
  constructor: (@name, @jql) ->
    return {name: @name, jql: @jql}


# keeps track of recently displayed issues, to prevent spamming
class RecentIssues
  constructor: (@maxage) ->
    @issues = []
  
  cleanup: ->
    for issue,time of @issues
      age = Math.round(((new Date()).getTime() - time) / 1000)
      if age > @maxage
        #console.log 'removing old issue', issue
        delete @issues[issue]
    0

  contains: (issue) ->
    @cleanup()
    @issues[issue]?

  add: (issue,time) ->
    time = time || (new Date()).getTime()
    @issues[issue] = time


module.exports = (robot) ->
  filters = new IssueFilters robot

  useV2 = process.env.HUBOT_JIRA_USE_V2 != "false"
  # max number of issues to list during a search
  maxlist = process.env.HUBOT_JIRA_MAXLIST || 10
  # how long (seconds) to wait between repeating the same JIRA issue link
  issuedelay = process.env.HUBOT_JIRA_ISSUEDELAY || 30
  # array of users that are ignored
  ignoredusers = (process.env.HUBOT_JIRA_IGNOREUSERS.split(',') if process.env.HUBOT_JIRA_IGNOREUSERS?) || []

  recentissues = new RecentIssues issuedelay

  get = (msg, where, cb) ->
    console.log(process.env.HUBOT_JIRA_URL + "/rest/api/latest/" + where)

    httprequest = msg.http(process.env.HUBOT_JIRA_URL + "/rest/api/latest/" + where)
    if (process.env.HUBOT_JIRA_USER)
      authdata = new Buffer(process.env.HUBOT_JIRA_USER+':'+process.env.HUBOT_JIRA_PASSWORD).toString('base64')
      httprequest = httprequest.header('Authorization', 'Basic ' + authdata)
    httprequest.get() (err, res, body) ->
        cb JSON.parse(body)

  watchers = (msg, issue, cb) ->
    get msg, "issue/#{issue}/watchers", (watchers) ->
      if watchers.errors?
        return

      cb watchers.watchers.map((watcher) -> return watcher.displayName).join(", ")

  info = (msg, issue, cb) ->
    get msg, "issue/#{issue}", (issues) ->
      if issues.errors?
        return

      if useV2
        issue =
          key: issues.key
          summary: issues.fields.summary
          assignee: ->
            if issues.fields.assignee != null
              issues.fields.assignee.displayName
            else
              "no assignee"
          status: issues.fields.status.name
          fixVersion: ->
            if issues.fields.fixVersions? and issues.fields.fixVersions.length > 0
              issues.fields.fixVersions.map((fixVersion) -> return fixVersion.name).join(", ")
            else
              "no fix version"
          url: process.env.HUBOT_JIRA_URL + '/browse/' + issues.key
      else
        issue =
          key: issues.key
          summary: issues.fields.summary.value
          assignee: ->
            if issues.fields.assignee.value != undefined
              issues.fields.assignee.value.displayName
            else
              "no assignee"
          status: issues.fields.status.value.name
          fixVersion: ->
            if issues.fields.fixVersions? and issues.fields.fixVersions.value != undefined
              issues.fields.fixVersions.value.map((fixVersion) -> return fixVersion.name).join(", ")
            else
              "no fix version"
          url: process.env.HUBOT_JIRA_URL + '/browse/' + issues.key

      cb "[#{issue.key}] #{issue.summary}. #{issue.assignee()} / #{issue.status}, #{issue.fixVersion()} #{issue.url}"
      
  search = (msg, jql, cb) ->
    get msg, "search/?jql=#{escape(jql)}", (result) ->
      if result.errors?
        return
      
      resultText = "I found #{result.total} issues for your search. #{process.env.HUBOT_JIRA_URL}/secure/IssueNavigator.jspa?reset=true&jqlQuery=#{escape(jql)}"
      if result.issues.length <= maxlist
        cb resultText
        result.issues.forEach (issue) ->
          info msg, issue.key, (info) ->
            cb info
      else
        cb resultText + " (too many to list)"

  robot.respond /(show )?watchers (for )?(\w+-[0-9]+)/i, (msg) ->
    if msg.message.user.id is robot.name
      return

    watchers msg, msg.match[3], (text) ->
      msg.send text
  
  robot.respond /search (for )?(.*)/i, (msg) ->
    if msg.message.user.id is robot.name
      return
      
    search msg, msg.match[2], (text) ->
      msg.reply text
  
  robot.respond /([^\w\-]|^)(\w+-[0-9]+)(?=[^\w]|$)/ig, (msg) ->
    if msg.message.user.id is robot.name
      return

    if (ignoredusers.some (user) -> user == msg.message.user.name)
      console.log 'ignoring user due to blacklist:', msg.message.user.name
      return
   
    for matched in msg.match
      ticket = (matched.match /(\w+-[0-9]+)/)[0]
      if !recentissues.contains msg.message.user.room+ticket
        info msg, ticket, (text) ->
          msg.send text
        recentissues.add msg.message.user.room+ticket

  robot.respond /save filter (.*) as (.*)/i, (msg) ->
    filter = filters.get msg.match[2]

    if filter
      filters.delete filter.name
      msg.reply "Updated filter #{filter.name} for you"

    filter = new IssueFilter msg.match[2], msg.match[1]
    filters.add filter

  robot.respond /delete filter (.*)/i, (msg) ->
    filters.delete msg.match[1]

  robot.respond /(use )?filter (.*)/i, (msg) ->
    name = msg.match[2]
    filter = filters.get name
    
    if not filter
      msg.reply "Sorry, could not find filter #{name}"
      return

    search msg, filter.jql, (text) ->
      msg.reply text

  robot.respond /(show )?filter(s)? ?(.*)?/i, (msg) ->
    if filters.all().length == 0
      msg.reply "Sorry, I don't remember any filters."
      return

    if msg.match[3] == undefined
      msg.reply "I remember #{filters.all().length} filters"
      filters.all().forEach (filter) ->
        msg.reply "#{filter.name}: #{filter.jql}"
    else
      filter = filters.get msg.match[3]
      msg.reply "#{filter.name}: #{filter.jql}"
Something went wrong with that request. Please try again.