slack.rb 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
  2. class Transaction::Slack
  3. =begin
  4. backend = Transaction::Slack.new(
  5. object: 'Ticket',
  6. type: 'create',
  7. object_id: 1,
  8. )
  9. backend.perform
  10. {
  11. object: 'Ticket',
  12. type: 'update',
  13. object_id: 123,
  14. via_web: true,
  15. changes: {
  16. 'attribute1' => [before, now],
  17. 'attribute2' => [before, now],
  18. }
  19. },
  20. =end
  21. def initialize(item, params = {})
  22. @item = item
  23. @params = params
  24. end
  25. def perform
  26. # return if we run import mode
  27. return if Setting.get('import_mode')
  28. return if @item[:object] != 'Ticket'
  29. return if !Setting.get('slack_integration')
  30. config = Setting.get('slack_config')
  31. return if !config
  32. return if !config['items']
  33. ticket = Ticket.find(@item[:object_id])
  34. if @item[:article_id]
  35. article = Ticket::Article.find(@item[:article_id])
  36. end
  37. # ignore if no changes has been done
  38. changes = human_changes(ticket)
  39. return if @item[:type] == 'update' && !article && (!changes || changes.empty?)
  40. # get user based notification template
  41. # if create, send create message / block update messages
  42. template = nil
  43. sent_value = nil
  44. if @item[:type] == 'create'
  45. template = 'ticket_create'
  46. elsif @item[:type] == 'update'
  47. template = 'ticket_update'
  48. elsif @item[:type] == 'reminder_reached'
  49. template = 'ticket_reminder_reached'
  50. sent_value = ticket.pending_time
  51. elsif @item[:type] == 'escalation'
  52. template = 'ticket_escalation'
  53. sent_value = ticket.escalation_time
  54. elsif @item[:type] == 'escalation_warning'
  55. template = 'ticket_escalation_warning'
  56. sent_value = ticket.escalation_time
  57. else
  58. raise "unknown type for notification #{@item[:type]}"
  59. end
  60. user = User.find(1)
  61. result = NotificationFactory::Slack.template(
  62. template: template,
  63. locale: user[:preferences][:locale],
  64. objects: {
  65. ticket: ticket,
  66. article: article,
  67. changes: changes,
  68. },
  69. )
  70. # good, warning, danger
  71. color = '#000000'
  72. ticket_state_type = ticket.state.state_type.name
  73. if ticket.escalation_time && ticket.escalation_time < Time.zone.now
  74. color = '#f35912'
  75. elsif ticket_state_type == 'pending reminder'
  76. if ticket.pending_time && ticket.pending_time < Time.zone.now
  77. color = '#faab00'
  78. end
  79. elsif ticket_state_type =~ /^(new|open)$/
  80. color = '#faab00'
  81. elsif ticket_state_type == 'closed'
  82. color = '#38ad69'
  83. end
  84. config['items'].each {|local_config|
  85. next if local_config['webhook'].empty?
  86. # check if reminder_reached/escalation/escalation_warning is already sent today
  87. md5_webhook = Digest::MD5.hexdigest(local_config['webhook'])
  88. cache_key = "slack::backend::#{@item[:type]}::#{ticket.id}::#{md5_webhook}"
  89. if sent_value
  90. value = Cache.get(cache_key)
  91. if value == sent_value
  92. Rails.logger.debug "did not send webhook, already sent (#{@item[:type]}/#{ticket.id}/#{local_config['webhook']})"
  93. next
  94. end
  95. Cache.write(
  96. cache_key,
  97. sent_value,
  98. {
  99. expires_in: 24.hours
  100. },
  101. )
  102. end
  103. # check action
  104. if local_config['types'].class == Array
  105. hit = false
  106. local_config['types'].each {|type|
  107. next if type.to_s != @item[:type].to_s
  108. hit = true
  109. break
  110. }
  111. next if !hit
  112. elsif local_config['types']
  113. next if local_config['types'].to_s != @item[:type].to_s
  114. end
  115. # check group
  116. if local_config['group_ids'].class == Array
  117. hit = false
  118. local_config['group_ids'].each {|group_id|
  119. next if group_id.to_s != ticket.group_id.to_s
  120. hit = true
  121. break
  122. }
  123. next if !hit
  124. elsif local_config['group_ids']
  125. next if local_config['group_ids'].to_s != ticket.group_id.to_s
  126. end
  127. logo_url = 'https://zammad.com/assets/images/logo-200x200.png'
  128. if !local_config['logo_url'].empty?
  129. logo_url = local_config['logo_url']
  130. end
  131. Rails.logger.debug "sent webhook (#{@item[:type]}/#{ticket.id}/#{local_config['webhook']})"
  132. notifier = Slack::Notifier.new(
  133. local_config['webhook'],
  134. channel: local_config['channel'],
  135. username: local_config['username'],
  136. icon_url: logo_url,
  137. mrkdwn: true,
  138. http_client: Transaction::Slack::Client,
  139. )
  140. if local_config['expand']
  141. body = "#{result[:subject]}\n#{result[:body]}"
  142. result = notifier.ping body
  143. else
  144. attachment = {
  145. text: result[:body],
  146. mrkdwn_in: ['text'],
  147. color: color,
  148. }
  149. result = notifier.ping result[:subject],
  150. attachments: [attachment]
  151. end
  152. if !result.success?
  153. if sent_value
  154. Cache.delete(cache_key)
  155. end
  156. Rails.logger.error "Unable to post webhook: #{local_config['webhook']}: #{result.inspect}"
  157. next
  158. end
  159. Rails.logger.debug "sent webhook (#{@item[:type]}/#{ticket.id}/#{local_config['webhook']})"
  160. }
  161. end
  162. def human_changes(record)
  163. return {} if !@item[:changes]
  164. user = User.find(1)
  165. locale = user.preferences[:locale] || 'en-us'
  166. # only show allowed attributes
  167. attribute_list = ObjectManager::Attribute.by_object_as_hash('Ticket', user)
  168. #puts "AL #{attribute_list.inspect}"
  169. user_related_changes = {}
  170. @item[:changes].each {|key, value|
  171. # if no config exists, use all attributes
  172. if !attribute_list || attribute_list.empty?
  173. user_related_changes[key] = value
  174. # if config exists, just use existing attributes for user
  175. elsif attribute_list[key.to_s]
  176. user_related_changes[key] = value
  177. end
  178. }
  179. changes = {}
  180. user_related_changes.each {|key, value|
  181. # get attribute name
  182. attribute_name = key.to_s
  183. object_manager_attribute = attribute_list[attribute_name]
  184. if attribute_name[-3, 3] == '_id'
  185. attribute_name = attribute_name[ 0, attribute_name.length - 3 ].to_s
  186. end
  187. # add item to changes hash
  188. if key.to_s == attribute_name
  189. changes[attribute_name] = value
  190. end
  191. # if changed item is an _id field/reference, do an lookup for the realy values
  192. value_id = []
  193. value_str = [ value[0], value[1] ]
  194. if key.to_s[-3, 3] == '_id'
  195. value_id[0] = value[0]
  196. value_id[1] = value[1]
  197. if record.respond_to?(attribute_name) && record.send(attribute_name)
  198. relation_class = record.send(attribute_name).class
  199. if relation_class && value_id[0]
  200. relation_model = relation_class.lookup(id: value_id[0])
  201. if relation_model
  202. if relation_model['name']
  203. value_str[0] = relation_model['name']
  204. elsif relation_model.respond_to?('fullname')
  205. value_str[0] = relation_model.send('fullname')
  206. end
  207. end
  208. end
  209. if relation_class && value_id[1]
  210. relation_model = relation_class.lookup(id: value_id[1])
  211. if relation_model
  212. if relation_model['name']
  213. value_str[1] = relation_model['name']
  214. elsif relation_model.respond_to?('fullname')
  215. value_str[1] = relation_model.send('fullname')
  216. end
  217. end
  218. end
  219. end
  220. end
  221. # check if we have an dedcated display name for it
  222. display = attribute_name
  223. if object_manager_attribute && object_manager_attribute[:display]
  224. # delete old key
  225. changes.delete(display)
  226. # set new key
  227. display = object_manager_attribute[:display].to_s
  228. end
  229. changes[display] = if object_manager_attribute && object_manager_attribute[:translate]
  230. from = Translation.translate(locale, value_str[0])
  231. to = Translation.translate(locale, value_str[1])
  232. [from, to]
  233. else
  234. [value_str[0].to_s, value_str[1].to_s]
  235. end
  236. }
  237. changes
  238. end
  239. class Transaction::Slack::Client
  240. def self.post(uri, params = {})
  241. UserAgent.post(
  242. uri.to_s,
  243. params,
  244. {
  245. open_timeout: 4,
  246. read_timeout: 10,
  247. total_timeout: 20,
  248. log: {
  249. facility: 'slack_webhook',
  250. }
  251. },
  252. )
  253. end
  254. end
  255. end