slack.rb 8.8 KB

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