1 | _ = require "underscore"
|
2 |
|
3 | Config = require "../config"
|
4 | Jira = require "../jira"
|
5 | Utils = require "../utils"
|
6 | GenericAdapter = require "./generic"
|
7 |
|
8 | class Slack extends GenericAdapter
|
9 | constructor: (@robot) ->
|
10 | super @robot
|
11 | @queue = {}
|
12 |
|
13 | @robot.router.post "/hubot/slack-events", (req, res) =>
|
14 | try
|
15 | payload = JSON.parse req.body.payload
|
16 | return unless payload.token is Config.slack.verification.token
|
17 | return @robot.emit "SlackEvents", payload, res unless @shouldJiraBotHandle payload
|
18 | catch e
|
19 | @robot.logger.debug e
|
20 | Utils.Stats.increment "jirabot.webhook.failed"
|
21 | return
|
22 |
|
23 | @onButtonActions(payload).then ->
|
24 | res.json payload.original_message
|
25 | .catch (error) ->
|
26 | @robot.logger.error error
|
27 |
|
28 | getRoom: (context) ->
|
29 | context = @normalizeContext context
|
30 | room = @robot.adapter.client.rtm.dataStore.getChannelOrGroupByName context.message.room
|
31 | room = @robot.adapter.client.rtm.dataStore.getChannelGroupOrDMById context.message.room unless room
|
32 | room
|
33 |
|
34 | getUsers: ->
|
35 | @robot.adapter.client.rtm.dataStore.users
|
36 |
|
37 | send: (context, message) ->
|
38 | payload = text: ""
|
39 | room = @getRoom context
|
40 | return unless room
|
41 |
|
42 | if _(message).isString()
|
43 | payload.text = message
|
44 | else
|
45 | payload = _(payload).chain().extend(message).pick("text", "attachments").value()
|
46 |
|
47 | if Config.slack.verification.token and payload.attachments?.length > 0
|
48 | attachments = []
|
49 | for a in payload.attachments
|
50 | attachments.push a
|
51 | attachments.push @buttonAttachmentsForState "mention", a if a and a.type is "JiraTicketAttachment"
|
52 | payload.attachments = attachments
|
53 |
|
54 | payload.text = " " if payload.attachments?.length > 0 and payload.text.length is 0
|
55 | if payload.text.length > 0
|
56 | @robot.adapter.send
|
57 | room: room.id
|
58 | message: thread_ts: context.message.thread_ts
|
59 | , payload
|
60 |
|
61 | shouldJiraBotHandle: (context) ->
|
62 | id = context.callback_id
|
63 | matches = id.match Config.ticket.regexGlobal
|
64 |
|
65 | if matches and matches[0]
|
66 | return yes
|
67 | else if ~id.indexOf "JiraBotDuplicate"
|
68 | return yes
|
69 | else
|
70 | return no
|
71 |
|
72 | onButtonActions: (payload) ->
|
73 | Promise.all payload.actions.map (action) => @handleButtonAction payload, action
|
74 |
|
75 | handleDuplicateReponse: (payload, action) ->
|
76 | id = payload.callback_id.split(":")[1]
|
77 | msg = payload.original_message
|
78 | msg.attachments.pop()
|
79 | if item = @queue[id]
|
80 | clearTimeout item.timer
|
81 | delete @queue[id]
|
82 |
|
83 | if action.name is "create" and action.value is "yes"
|
84 | msg.attachments.push text: "Creating ticket..."
|
85 | item.action()
|
86 |
|
87 | if action.name is "create" and action.value is "no"
|
88 | msg.attachments.push text: "Ticket creation has been cancelled"
|
89 | Utils.Stats.increment "jirabot.slack.button.duplicate.#{action.name}.#{action.value}"
|
90 |
|
91 | return Promise.resolve()
|
92 |
|
93 | handleButtonAction: (payload, action) ->
|
94 | key = payload.callback_id
|
95 | return @handleDuplicateReponse payload, action if ~key.indexOf "JiraBotDuplicate"
|
96 |
|
97 | return new Promise (resolve, reject) =>
|
98 | key = payload.callback_id
|
99 | user = payload.user
|
100 | msg = payload.original_message
|
101 | envelope = message: user: user
|
102 |
|
103 | switch action.name
|
104 | when "rank"
|
105 | Jira.Rank.forTicketKeyByDirection key, "up", envelope, no, no
|
106 | msg.attachments.push
|
107 | text: "<@#{user.id}> ranked this ticket to the top"
|
108 | resolve()
|
109 | when "watch"
|
110 | Jira.Create.fromKey(key)
|
111 | .then (ticket) =>
|
112 | watchers = Utils.lookupChatUsersWithJira ticket.watchers
|
113 | if _(watchers).findWhere(id: user.id)
|
114 | msg.attachments.push
|
115 | text: "<@#{user.id}> has stopped watching this ticket"
|
116 | Jira.Watch.forTicketKeyRemovePerson key, null, envelope, no, no
|
117 | else
|
118 | msg.attachments.push
|
119 | text: "<@#{user.id}> is now watching this ticket"
|
120 | Jira.Watch.forTicketKeyForPerson key, user.name, envelope, no, no, no
|
121 | resolve()
|
122 | when "assign"
|
123 | Jira.Create.fromKey(key)
|
124 | .then (ticket) =>
|
125 | assignee = Utils.lookupChatUserWithJira ticket.fields.assignee
|
126 | if assignee and assignee.id is user.id
|
127 | Jira.Assign.forTicketKeyToUnassigned key, envelope, no, no
|
128 | msg.attachments.push
|
129 | text: "<@#{user.id}> has unassigned themself"
|
130 | else
|
131 | Jira.Assign.forTicketKeyToPerson key, user.name, envelope, no, no
|
132 | msg.attachments.push
|
133 | text: "<@#{user.id}> is now assigned to this ticket"
|
134 | resolve()
|
135 | else
|
136 | result = Utils.fuzzyFind action.value, Config.maps.transitions, ['jira']
|
137 | if result
|
138 | msg.attachments.push @buttonAttachmentsForState action.name,
|
139 | key: key
|
140 | text: "<@#{user.id}> transitioned this ticket to #{result.jira}"
|
141 | Jira.Transition.forTicketKeyToState key, result.name, envelope, no, no
|
142 | else
|
143 | msg.attachments.push
|
144 | text: "Unable to to process #{action.name}"
|
145 | resolve()
|
146 | Utils.Stats.increment "jirabot.slack.button.#{action.name}"
|
147 |
|
148 | getPermalink: (context) ->
|
149 | team = _(context.robot.adapter.client.rtm.dataStore.teams).pairs()
|
150 | if domain = team[0]?[1]?.domain
|
151 | "https://#{domain}.slack.com/archives/#{context.message.room}/p#{context.message.id.replace '.', ''}"
|
152 | else
|
153 | ""
|
154 |
|
155 | buttonAttachmentsForState: (state="mention", details) ->
|
156 | key = details.author_name or details.key
|
157 | return {} unless key and key.length > 0
|
158 | project = key.split("-")[0]
|
159 | return {} unless project
|
160 | buttons = Config.slack.buttons
|
161 | return {} unless buttons
|
162 | map = Config.slack.project.button.state.map[project] or Config.slack.project.button.state.map.default
|
163 | return {} unless map
|
164 | actions = []
|
165 | actions.push buttons[button] for button in map[state] if map[state]
|
166 |
|
167 | fallback: "Unable to display quick action buttons"
|
168 | attachment_type: "default"
|
169 | callback_id: key
|
170 | color: details.color
|
171 | actions: actions
|
172 | text: details.text
|
173 |
|
174 | detectForDuplicates: (project, type, summary, context) ->
|
175 | original = summary
|
176 | create = -> Jira.Create.with project, type, original, context
|
177 | { summary } = Utils.extract.all summary
|
178 |
|
179 | Jira.Search.withQueryForProject(summary, project, context, 20)
|
180 | .then (results) =>
|
181 | if duplicate = Utils.detectPossibleDuplicate summary, results.tickets
|
182 | now = Date.now()
|
183 | @queue[now] =
|
184 | timer: setTimeout =>
|
185 | create()
|
186 | delete @queue[now]
|
187 | , Config.duplicates.timeout
|
188 | action: create
|
189 |
|
190 | attachments = [ duplicate.toAttachment no ]
|
191 | attachments.push
|
192 | fallback: "Unable to display quick action buttons"
|
193 | attachment_type: "default"
|
194 | callback_id: "JiraBotDuplicate:#{now}"
|
195 | text: """
|
196 | There are potential duplicates of this issue.
|
197 | If you do not respond, the ticket will be created in #{Config.duplicates.timeout/1000} seconds
|
198 |
|
199 | What would you like to do?
|
200 | """
|
201 | actions: [
|
202 | name: "create"
|
203 | text: "Create anyways"
|
204 | style: "primary"
|
205 | type: "button"
|
206 | value: "yes"
|
207 | ,
|
208 | name: "create"
|
209 | text: "Do not create"
|
210 | style: "danger"
|
211 | type: "button"
|
212 | value: "no"
|
213 | ]
|
214 |
|
215 | @send context,
|
216 | text: results.text
|
217 | attachments: attachments
|
218 | , no
|
219 | else
|
220 | create()
|
221 | .catch ->
|
222 | create()
|
223 |
|
224 | module.exports = Slack
|