Skip to content
This repository
file 198 lines (179 sloc) 7.322 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
# Description:
# List stories and other items in Sprint.ly and interact with them
#
# Dependencies:
# None
#
# Configuration:
# None
#
# Commands:
# hubot sprintly [product_id] [status] [limit] - list items in status (default status is in-progress, other values: backlog, completed, accepted; default limit is 20)
# hubot sprintly [product_id] mine - list items assigned to me
# hubot sprintly [product_id] #42 - show item #42
# hubot sprintly [product_id] #42 tasks - list unfinished subtasks of story #42
# hubot sprintly [product_id] <action> #42 - carry out action on item #42 (available actions: start, stop, finish, accept, reject, delete)
# hubot sprintly [product_id] [environment] deploy 4,8,15,16,23,42 - mark items as deployed to an environment
# hubot sprintly token <email:apitoken> - set/update credentials for user (required for other commands to work)
# hubot sprintly default 1234 - set default product_id
# hubot sprintly default_env production - set default environment (used for deploy)
#
# Author:
# lackac

qs = require 'querystring'

module.exports = (robot) ->

  robot.respond /sprintly +token +(.*)/i, (msg) ->
    sprintly(msg, msg.match[1])
      .scope('products.json')
      .get() (err, res, body) ->
        if res.statusCode < 400
          sprintlyUser(msg).auth = msg.match[1]
          msg.send "API token set"
        else
          msg.send "Unable to verify API token: #{body}"

  robot.respond /sprintly +default +(\d+) *$/i, (msg) ->
    robot.brain.data.sprintly ?= {}
    robot.brain.data.sprintly.product_id = msg.match[1]
    msg.send "Default Product ID set to #{msg.match[1]}"

  robot.respond /sprintly +default_env +(.*)/i, (msg) ->
    robot.brain.data.sprintly ?= {}
    robot.brain.data.sprintly.env = msg.match[1]
    msg.send "Default environment set to #{msg.match[1]}"

  robot.respond /sprintly *(?: +(\d+))?(?: +(backlog|in-progress|completed|accepted))?(?: +(\d+))? *$/i, (msg) ->
    query = status: msg.match[2] ? 'in-progress'
    query.limit = msg.match[3] if msg.match[3]
    sprintly(msg).product()
      .scope('items.json')
      .query(query)
      .get()(formatItems(msg))

  robot.respond /sprintly +(?:(\d+) +)?mine *$/i, (msg) ->
    withUserId msg, (user_id) ->
      sprintly(msg).product()
        .scope('items.json')
        .query(assigned_to: user_id)
        .get()(formatItems(msg))

  robot.respond /sprintly +(?:(\d+) +)?#(\d+) *$/i, (msg) ->
    sprintly(msg).product()
      .scope("items/#{msg.match[2]}.json")
      .get() (err, res, body) ->
        if res.statusCode == 200
          item = JSON.parse(body)
          msg.send itemSummary(item)
          msg.send item.description if item.description
          meta = [
            "status: #{item.status}"
            "assigned_to: #{if u = item.assigned_to then "#{u.first_name} #{u.last_name}" else "nobody"}"
            "created by: #{item.created_by.first_name} #{item.created_by.last_name}"
          ]
          meta.push "tags: #{item.tags.join(", ")}" if item.tags and item.tags.length > 0
          msg.send meta.join(", ")
        else
          msg.send "Something came up: #{body}"

  robot.respond /sprintly +(?:(\d+) +)?#(\d+) +tasks *$/i, (msg) ->
    sprintly(msg).product()
      .scope("items/#{msg.match[2]}/children.json")
      .get()(formatItems(msg, true))

  robot.respond /sprintly +(?:(\d+) +)?(start|stop|finish|accept|reject|delete) +#?(\d+) *$/i, (msg) ->
    withUserId msg, (user_id) ->
      query = {}
      method = 'post'
      switch msg.match[2]
        when 'start'
          query.status = 'in-progress'
          query.assigned_to = user_id
        when 'stop'
          query.status = 'backlog'
        when 'finish'
          query.status = 'completed'
        when 'accept'
          query.status = 'accepted'
        when 'reject'
          query.status = 'in-progress'
        when 'delete'
          method = 'delete'
      sprintly(msg).product()
        .scope("items/#{msg.match[3]}.json")[method](qs.stringify(query)) (err, res, body) ->
          if res.statusCode < 400
            item = JSON.parse(body)
            if msg.match[2] is 'delete'
              msg.send "##{item.number} has been archived"
            else
              msg.send "##{item.number} status: #{item.status}"
          else
            msg.send "Something came up: #{body}"

  robot.respond /sprintly *(?: +(\d+))?(?: +(.*))?deploy +([\d]+(,[\d]+)*)/i, (msg) ->
    query =
      environment: msg.match[2] ? msg.robot.brain.data.sprintly?.env
      numbers: msg.match[3]

    if query.environment?
      sprintly(msg).product()
        .scope('deploys.json')
        .post(qs.stringify(query)) (err, res, body) ->
          if res.statusCode < 400
            apiRes = JSON.parse body
            msg.send "Successfully marked #{apiRes.items.length} items as deployed"
          else
            msg.send "Something came up: #{body}"
    else
      msg.send "No environment has been specified, you can set a default with 'sprintly default_env production'"

DummyClient = ->
self = -> this
for method in ['scope', 'query', 'product']
  DummyClient::[method] = self
for method in ['get', 'post', 'put', 'delete']
  DummyClient::[method] = -> self

sprintlyUser = (msg) ->
  sp = msg.robot.brain.data.sprintly ?= {}
  sp[msg.message.user.id] ?= {}

sprintly = (msg, auth) ->
  if auth ?= sprintlyUser(msg).auth
    client = msg.robot.http('https://sprint.ly')
      .header('accept', 'application/json')
      .header('authorization', "Basic #{new Buffer(auth).toString('base64')}")
      .path('/api')
    client.product = ->
      if product_id = msg.match[1] ? msg.robot.brain.data.sprintly?.product_id
        @path("/api/products/#{product_id}")
      else
        msg.send "No Product Id has been specified, you can set a default with 'sprintly default 1234'"
        new DummyClient()
    client
  else
    msg.send "API token not found, set it with 'sprintly token <email:apitoken>'"
    new DummyClient()

itemSummary = (item, subtask=false) ->
  parts = ["##{item.number}"]
  parts.push "(#{item.score})" unless subtask
  parts.push "#{item.type}:" if (not subtask and item.type isnt 'story') or (subtask and item.type isnt 'task')
  parts.push item.title
  parts.push "https://sprint.ly/#!/product/#{item.product.id}/item/#{item.number}"
  parts.join(" ")

formatItems = (msg, subtasks=false) ->
  no_items_msg = if subtasks then "No subtasks" else "No items"
  (err, res, body) ->
    if res.statusCode == 200
      payload = JSON.parse(body)
      if payload.length > 0
        for item in payload when not subtasks or item.status in ['backlog', 'in-progress']
          msg.send itemSummary(item, subtasks)
      else
        msg.send no_items_msg
    else
      msg.send "Something came up: #{body}"

withUserId = (msg, callback) ->
  user = sprintlyUser(msg)
  if user.user_id
    callback(user.user_id)
  else
    sprintly(msg).product()
      .scope('people.json')
      .get() (err, res, body) ->
        if res.statusCode == 200
          payload = JSON.parse(body)
          user_email = user.auth.split(':')[0]
          for {id, email} in payload when email == user_email
            user.user_id = id
            callback(id)
            break
        else
          msg.send "Something came up: #{body}"
Something went wrong with that request. Please try again.