From a10205750638f56bccb20d77d66c085014ed32f0 Mon Sep 17 00:00:00 2001
From: Martin Edenhofer
Date: Fri, 15 Apr 2016 23:56:10 +0200
Subject: [PATCH] Added slack integration.
---
Gemfile | 3 +
Gemfile.lock | 2 +
.../controllers/_integration/icinga.coffee | 40 ++-
.../_integration/mattermost.coffee | 12 -
.../controllers/_integration/nagios.coffee | 40 ++-
.../app/controllers/_integration/slack.coffee | 73 ++++++
.../_profile/linked_accounts.coffee | 22 +-
.../app/controllers/_settings/area.coffee | 94 +++++++-
.../app/controllers/integrations.coffee | 45 +++-
.../javascripts/app/controllers/manage.coffee | 1 +
.../app/controllers/package.coffee | 2 +-
.../javascripts/app/models/setting.coffee | 10 +
.../app/views/integration/base.jst.eco | 17 ++
.../app/views/integration/index.jst.eco | 29 +++
.../app/views/settings/form.jst.eco | 21 ++
.../app/views/settings/item.jst.eco | 6 +-
app/assets/stylesheets/zammad.scss | 1 +
app/models/observer/ticket/notification.rb | 180 --------------
app/models/observer/transaction.rb | 181 +++++++++++++-
app/models/ticket.rb | 18 +-
app/models/transaction.rb | 3 +
app/models/transaction/background_job.rb | 36 +++
.../notification.rb} | 69 +++---
app/models/transaction/slack.rb | 227 ++++++++++++++++++
app/views/mailer/ticket_create/de.html.erb | 2 +-
app/views/mailer/ticket_create/en.html.erb | 2 +-
.../mailer/ticket_escalation/de.html.erb | 2 +-
.../mailer/ticket_escalation/en.html.erb | 2 +-
.../ticket_escalation_warning/de.html.erb | 2 +-
.../ticket_escalation_warning/en.html.erb | 6 +-
.../ticket_reminder_reached/de.html.erb | 2 +-
.../ticket_reminder_reached/en.html.erb | 4 +-
app/views/mailer/ticket_update/de.html.erb | 2 +-
app/views/mailer/ticket_update/en.html.erb | 2 +-
app/views/slack/application.md.erb | 1 +
app/views/slack/ticket_create/en.md.erb | 9 +
app/views/slack/ticket_escalation/en.md.erb | 7 +
.../slack/ticket_escalation_warning/en.md.erb | 7 +
.../slack/ticket_reminder_reached/en.md.erb | 7 +
app/views/slack/ticket_update/en.md.erb | 11 +
config/application.rb | 4 +-
.../20160415000001_add_slack_integration.rb | 144 +++++++++++
db/seeds.rb | 59 ++++-
lib/notification_factory.rb | 24 +-
lib/notification_factory/slack.rb | 54 +++++
lib/notification_factory/template.rb | 23 +-
test/integration/slack_test.rb | 192 +++++++++++++++
test/unit/ticket_notification_test.rb | 12 +-
48 files changed, 1403 insertions(+), 309 deletions(-)
delete mode 100644 app/assets/javascripts/app/controllers/_integration/mattermost.coffee
create mode 100644 app/assets/javascripts/app/controllers/_integration/slack.coffee
create mode 100644 app/assets/javascripts/app/views/integration/base.jst.eco
create mode 100644 app/assets/javascripts/app/views/integration/index.jst.eco
create mode 100644 app/assets/javascripts/app/views/settings/form.jst.eco
delete mode 100644 app/models/observer/ticket/notification.rb
create mode 100644 app/models/transaction.rb
create mode 100644 app/models/transaction/background_job.rb
rename app/models/{observer/ticket/notification/background_job.rb => transaction/notification.rb} (82%)
create mode 100644 app/models/transaction/slack.rb
create mode 100644 app/views/slack/application.md.erb
create mode 100644 app/views/slack/ticket_create/en.md.erb
create mode 100644 app/views/slack/ticket_escalation/en.md.erb
create mode 100644 app/views/slack/ticket_escalation_warning/en.md.erb
create mode 100644 app/views/slack/ticket_reminder_reached/en.md.erb
create mode 100644 app/views/slack/ticket_update/en.md.erb
create mode 100644 db/migrate/20160415000001_add_slack_integration.rb
create mode 100644 lib/notification_factory/slack.rb
create mode 100644 test/integration/slack_test.rb
diff --git a/Gemfile b/Gemfile
index ecc9a8afd..a188559e6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -88,6 +88,9 @@ gem 'writeexcel'
gem 'icalendar'
gem 'browser'
+# integrations
+gem 'slack-notifier'
+
# event machine
gem 'eventmachine'
gem 'em-websocket'
diff --git a/Gemfile.lock b/Gemfile.lock
index c0deb2051..f568c61ef 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -267,6 +267,7 @@ GEM
simplecov-html (0.10.0)
simplecov-rcov (0.2.3)
simplecov (>= 0.4.1)
+ slack-notifier (1.5.1)
slop (3.6.0)
spring (1.6.4)
sprockets (3.5.2)
@@ -361,6 +362,7 @@ DEPENDENCIES
simple-rss
simplecov
simplecov-rcov
+ slack-notifier
spring
sprockets
sqlite3
diff --git a/app/assets/javascripts/app/controllers/_integration/icinga.coffee b/app/assets/javascripts/app/controllers/_integration/icinga.coffee
index a5ad04487..3c486a06b 100644
--- a/app/assets/javascripts/app/controllers/_integration/icinga.coffee
+++ b/app/assets/javascripts/app/controllers/_integration/icinga.coffee
@@ -1,12 +1,30 @@
-class Icinga extends App.ControllerTabs
- header: 'Icinga'
- constructor: ->
- super
- return if !@authenticate(false, 'Admin')
- @title 'Icinga', true
- @tabs = [
- { name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Integration::Icinga' } }
- ]
- @render()
+class Index extends App.ControllerIntegrationBase
+ featureIntegration: 'icinga_integration'
+ featureName: 'Icinga'
+ featureConfig: 'icinga_config'
+ description: [
+ ['This service receives emails from %s and creates tickets with host and service.', 'Icinga']
+ ['If the host and service is recovered again, the ticket will be closed automatically.']
+ ]
-App.Config.set('IntegrationIcinga', { prio: 1100, parent: '#integration', name: 'Icinga', target: '#integration/icinga', controller: Icinga, role: ['Admin'] }, 'NavBarIntegration')
+ form: (localeEl) ->
+ new App.SettingsForm(
+ area: 'Integration::Icinga'
+ el: localeEl.find('.js-form')
+ )
+
+class State
+ @current: ->
+ App.Setting.get('icinga_integration')
+
+App.Config.set(
+ 'IntegrationIcinga'
+ {
+ name: 'Icinga'
+ target: '#system/integration/icinga'
+ description: 'A open source monitoring tool.'
+ controller: Index
+ state: State
+ }
+ 'NavBarIntegrations'
+)
diff --git a/app/assets/javascripts/app/controllers/_integration/mattermost.coffee b/app/assets/javascripts/app/controllers/_integration/mattermost.coffee
deleted file mode 100644
index ce340da0d..000000000
--- a/app/assets/javascripts/app/controllers/_integration/mattermost.coffee
+++ /dev/null
@@ -1,12 +0,0 @@
-class Mattermost extends App.ControllerTabs
- header: 'Mattermost'
- constructor: ->
- super
- return if !@authenticate(false, 'Admin')
- @title 'Mattermost', true
- @tabs = [
- { name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Integration::Mattermost' } }
- ]
- @render()
-
-App.Config.set('IntegrationMattermost', { prio: 1000, parent: '#integration', name: 'Mattermost', target: '#integration/mattermost', controller: Mattermost, role: ['Admin'] }, 'NavBarIntegration')
diff --git a/app/assets/javascripts/app/controllers/_integration/nagios.coffee b/app/assets/javascripts/app/controllers/_integration/nagios.coffee
index 471fa8516..ab8bf6394 100644
--- a/app/assets/javascripts/app/controllers/_integration/nagios.coffee
+++ b/app/assets/javascripts/app/controllers/_integration/nagios.coffee
@@ -1,12 +1,30 @@
-class Nagios extends App.ControllerTabs
- header: 'Nagios'
- constructor: ->
- super
- return if !@authenticate(false, 'Admin')
- @title 'Nagios', true
- @tabs = [
- { name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Integration::Nagios' } }
- ]
- @render()
+class Index extends App.ControllerIntegrationBase
+ featureIntegration: 'nagios_integration'
+ featureName: 'Nagios'
+ featureConfig: 'nagios_config'
+ description: [
+ ['This service receives emails from %s and creates tickets with host and service.', 'Nagios']
+ ['If the host and service is recovered again, the ticket will be closed automatically.']
+ ]
-App.Config.set('IntegrationNagios', { prio: 1200, parent: '#integration', name: 'Nagios', target: '#integration/nagios', controller: Nagios, role: ['Admin'] }, 'NavBarIntegration')
+ form: (localeEl) ->
+ new App.SettingsForm(
+ area: 'Integration::Nagios'
+ el: localeEl.find('.js-form')
+ )
+
+class State
+ @current: ->
+ App.Setting.get('nagios_integration')
+
+App.Config.set(
+ 'IntegrationNagios'
+ {
+ name: 'Nagios'
+ target: '#system/integration/nagios'
+ description: 'A open source monitoring tool.'
+ controller: Index
+ state: State
+ }
+ 'NavBarIntegrations'
+)
diff --git a/app/assets/javascripts/app/controllers/_integration/slack.coffee b/app/assets/javascripts/app/controllers/_integration/slack.coffee
new file mode 100644
index 000000000..de3169477
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_integration/slack.coffee
@@ -0,0 +1,73 @@
+class Index extends App.ControllerIntegrationBase
+ featureIntegration: 'slack_integration'
+ featureName: 'Slack'
+ featureConfig: 'slack_config'
+ description: [
+ ['This service sends notifications to your %s channel.', 'Slack']
+ ['To setup this Service you need to create a new |"Incoming webhook"| in your %s integration panel, and enter the Webhook URL below.', 'Slack']
+ ]
+
+ form: (localEl) =>
+
+ params = App.Setting.get(@featureConfig)
+ if params && params.items
+ params = params.items[0] || {}
+
+ options =
+ create: '1. Ticket Create'
+ update: '2. Ticket Update'
+ reminder_reached: '3. Ticket Reminder Reached'
+ escalation: '4. Ticket Escalation'
+ escalation_warning: '5. Ticket Escalation Warning'
+
+ configureAttributes = [
+ { name: 'types', display: 'Trigger', tag: 'checkbox', options: options, 'null': false, class: 'vertical', note: 'Where notification is sent.' },
+ { name: 'group_id', display: 'Group', tag: 'select', relation: 'Group', multiple: true, 'null': false, note: 'Only for this groups.' },
+ { name: 'webhook', display: 'Webhook', tag: 'input', type: 'text', limit: 200, 'null': false, placeholder: 'https://hooks.slack.com/services/...' },
+ { name: 'username', display: 'username', tag: 'input', type: 'text', limit: 100, 'null': false, placeholder: 'username' },
+ { name: 'channel', display: 'channel', tag: 'input', type: 'text', limit: 100, 'null': true, placeholder: '#channel' },
+ ]
+ console.log('p', params)
+ settings = []
+ for item in configureAttributes
+ setting =
+ options:
+ form: [item]
+ name: item.name
+ description: item.note || ''
+ title: item.display
+ settings.push setting
+
+ formEl = $( App.view('settings/form')(
+ settings: settings
+ ))
+
+ for setting in settings
+ configure_attribute = setting.options['form']
+ configure_attribute[0].display = ''
+ value = params[setting.name]
+ localParams = {}
+ localParams[setting.name] = value
+ new App.ControllerForm(
+ el: formEl.find("[data-name=#{setting.name}]")
+ model: { configure_attributes: configure_attribute, className: '' }
+ params: localParams
+ )
+
+ localEl.find('.js-form').html(formEl)
+
+class State
+ @current: ->
+ App.Setting.get('slack_integration')
+
+App.Config.set(
+ 'IntegrationSlack'
+ {
+ name: 'Slack'
+ target: '#system/integration/slack'
+ description: 'A team communication tool for the 21st century.'
+ controller: Index
+ state: State
+ }
+ 'NavBarIntegrations'
+)
diff --git a/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee b/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee
index 893498d51..587b2132e 100644
--- a/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee
+++ b/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee
@@ -37,7 +37,7 @@ class Index extends App.Controller
}
auth_providers = []
for key, provider of auth_provider_all
- if @Config.get( provider.config ) is true || @Config.get( provider.config ) is 'true'
+ if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true'
auth_providers.push provider
@html App.view('profile/linked_accounts')(
@@ -52,19 +52,19 @@ class Index extends App.Controller
# get data
@ajax(
- id: 'account'
- type: 'DELETE'
- url: @apiPath + '/users/account'
- data: JSON.stringify({ provider: provider, uid: uid })
+ id: 'account'
+ type: 'DELETE'
+ url: @apiPath + '/users/account'
+ data: JSON.stringify(provider: provider, uid: uid)
processData: true
- success: @success
- error: @error
+ success: @success
+ error: @error
)
success: (data, status, xhr) =>
@notify(
type: 'success'
- msg: App.i18n.translateContent( 'Successfully!' )
+ msg: App.i18n.translateContent('Successfully!')
)
update = =>
@render()
@@ -72,10 +72,10 @@ class Index extends App.Controller
error: (xhr, status, error) =>
@render()
- data = JSON.parse( xhr.responseText )
+ data = JSON.parse(xhr.responseText)
@notify(
type: 'error'
- msg: App.i18n.translateContent( data.message )
+ msg: App.i18n.translateContent(data.message)
)
-App.Config.set( 'LinkedAccounts', { prio: 4000, name: 'Linked Accounts', parent: '#profile', target: '#profile/linked', controller: Index }, 'NavBarProfile' )
+App.Config.set('LinkedAccounts', { prio: 4000, name: 'Linked Accounts', parent: '#profile', target: '#profile/linked', controller: Index }, 'NavBarProfile')
diff --git a/app/assets/javascripts/app/controllers/_settings/area.coffee b/app/assets/javascripts/app/controllers/_settings/area.coffee
index 36a718730..f6bdc1674 100644
--- a/app/assets/javascripts/app/controllers/_settings/area.coffee
+++ b/app/assets/javascripts/app/controllers/_settings/area.coffee
@@ -1,3 +1,93 @@
+class App.SettingsForm extends App.Controller
+ events:
+ 'submit form': 'update'
+
+ constructor: ->
+ super
+
+ # check authentication
+ return if !@authenticate()
+
+ @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
+
+ render: =>
+
+ # serach area settings
+ settings = App.Setting.search(
+ filter:
+ area: @area
+ )
+
+ # filter online service settings
+ if App.Config.get('system_online_service')
+ settings = _.filter(settings, (setting) ->
+ return if setting.online_service
+ return if setting.preferences && setting.preferences.online_service_disable
+ setting
+ )
+ return if _.isEmpty(settings)
+
+ # sort by prio
+ settings = _.sortBy( settings, (setting) ->
+ return if !setting.preferences
+ setting.preferences.prio
+ )
+
+ localEl = $( App.view('settings/form')(
+ settings: settings
+ ))
+
+ for setting in settings
+ configure_attributes = setting.options['form']
+ value = App.Setting.get(setting.name)
+ params = {}
+ params[setting.name] = value
+ new App.ControllerForm(
+ el: localEl.find("[data-name=#{setting.name}]")
+ model: { configure_attributes: configure_attributes, className: '' }
+ params: params
+ )
+ @html localEl
+
+ update: (e) =>
+ e.preventDefault()
+ @formDisable(e)
+ params = @formParam(e.target)
+
+ ui = @
+ count = 0
+ for name, value of params
+ if App.Setting.findByAttribute('name', name)
+ count += 1
+ App.Setting.set(
+ name,
+ value,
+ done: ->
+ ui.formEnable(e)
+ count -= 1
+ if count == 0
+ App.Event.trigger 'notify', {
+ type: 'success'
+ msg: App.i18n.translateContent('Update successful!')
+ timeout: 2000
+ }
+
+ # rerender ui || get new collections and session data
+ if @preferences
+ if @preferences.render
+ App.Event.trigger( 'ui:rerender' )
+
+ if @preferences.session_check
+ App.Auth.loginCheck()
+
+ fail: (settings, details) ->
+ App.Event.trigger 'notify', {
+ type: 'error'
+ msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
+ timeout: 2000
+ }
+ )
+
class App.SettingsArea extends App.Controller
constructor: ->
super
@@ -115,11 +205,11 @@ class App.SettingsAreaItem extends App.Controller
if @setting.preferences.session_check
App.Auth.loginCheck()
- fail: ->
+ fail: (settings, details) ->
ui.formEnable(e)
App.Event.trigger 'notify', {
type: 'error'
- msg: App.i18n.translateContent('Can\'t update item!')
+ msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
timeout: 2000
}
)
diff --git a/app/assets/javascripts/app/controllers/integrations.coffee b/app/assets/javascripts/app/controllers/integrations.coffee
index 5de52cf83..57570bf1c 100644
--- a/app/assets/javascripts/app/controllers/integrations.coffee
+++ b/app/assets/javascripts/app/controllers/integrations.coffee
@@ -1,8 +1,41 @@
-class IndexRouter extends App.ControllerNavSidbar
- authenticateRequired: true
- configKey: 'NavBarIntegration'
+class Index extends App.ControllerContent
+ constructor: ->
+ super
-App.Config.set('integration', IndexRouter, 'Routes')
-App.Config.set('integration/:target', IndexRouter, 'Routes')
+ # check authentication
+ return if !@authenticate(false, 'Admin')
-App.Config.set('Integration', { prio: 1000, name: 'Integration', target: '#integration', role: ['Admin'] }, 'NavBarIntegration')
+ @title 'Integrations', true
+
+ @integrationItems = App.Config.get('NavBarIntegrations')
+
+ if !@integration
+ @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
+ return
+
+ for key, value of @integrationItems
+ if value.target is "#system/#{@target}/#{@integration}"
+ config = value
+ break
+
+ new config.controller(
+ el: @el.closest('.main')
+ )
+
+ render: =>
+ integrations = []
+ for key, value of @integrationItems
+ value.key = key
+ integrations.push value
+ integrations = _.sortBy(integrations, (item) -> return item.name)
+
+ @html App.view('integration/index')(
+ head: 'Integrations'
+ integrations: integrations
+ )
+
+ release: =>
+ if @subscribeId
+ App.Setting.unsubscribe(@subscribeId)
+
+App.Config.set('Integration', { prio: 1000, name: 'Integrations', parent: '#system', target: '#system/integration', controller: Index, role: ['Admin'] }, 'NavBarAdmin')
diff --git a/app/assets/javascripts/app/controllers/manage.coffee b/app/assets/javascripts/app/controllers/manage.coffee
index f59d7bb72..5ab127c7d 100644
--- a/app/assets/javascripts/app/controllers/manage.coffee
+++ b/app/assets/javascripts/app/controllers/manage.coffee
@@ -8,6 +8,7 @@ App.Config.set('settings/:target', IndexRouter, 'Routes')
App.Config.set('channels/:target', IndexRouter, 'Routes')
App.Config.set('channels/:target/:channel_id', IndexRouter, 'Routes')
App.Config.set('system/:target', IndexRouter, 'Routes')
+App.Config.set('system/:target/:integration', IndexRouter, 'Routes')
App.Config.set('Manage', { prio: 1000, name: 'Manage', target: '#manage', role: ['Admin'] }, 'NavBarAdmin')
App.Config.set('Channels', { prio: 2500, name: 'Channels', target: '#channels', role: ['Admin'] }, 'NavBarAdmin')
diff --git a/app/assets/javascripts/app/controllers/package.coffee b/app/assets/javascripts/app/controllers/package.coffee
index e2c53ae30..a110789cd 100644
--- a/app/assets/javascripts/app/controllers/package.coffee
+++ b/app/assets/javascripts/app/controllers/package.coffee
@@ -58,4 +58,4 @@ class Index extends App.ControllerContent
@load()
)
-App.Config.set( 'Packages', { prio: 1000, name: 'Packages', parent: '#system', target: '#system/package', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
+App.Config.set('Packages', { prio: 1000, name: 'Packages', parent: '#system', target: '#system/package', controller: Index, role: ['Admin'] }, 'NavBarAdmin')
diff --git a/app/assets/javascripts/app/models/setting.coffee b/app/assets/javascripts/app/models/setting.coffee
index 1879df407..baedf47f0 100644
--- a/app/assets/javascripts/app/models/setting.coffee
+++ b/app/assets/javascripts/app/models/setting.coffee
@@ -2,3 +2,13 @@ class App.Setting extends App.Model
@configure 'Setting', 'name', 'state_current'
@extend Spine.Model.Ajax
@url: @apiPath + '/settings'
+
+ @get: (name) ->
+ setting = App.Setting.findByAttribute('name', name)
+ setting.state_current.value
+
+ @set: (name, value, options = {}) ->
+ setting = App.Setting.findByAttribute('name', name)
+ setting.state_current.value = value
+ setting.save(options)
+ App.Config.set(name, value)
diff --git a/app/assets/javascripts/app/views/integration/base.jst.eco b/app/assets/javascripts/app/views/integration/base.jst.eco
new file mode 100644
index 000000000..d941bd965
--- /dev/null
+++ b/app/assets/javascripts/app/views/integration/base.jst.eco
@@ -0,0 +1,17 @@
+
+
+ <% if @description: %>
+ <% for item in @description: %>
+
<%- @T(item[0], item[1], item[2]) %>
+ <% end %>
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/integration/index.jst.eco b/app/assets/javascripts/app/views/integration/index.jst.eco
new file mode 100644
index 000000000..feddea317
--- /dev/null
+++ b/app/assets/javascripts/app/views/integration/index.jst.eco
@@ -0,0 +1,29 @@
+
+
+
+
+
+ |
+ <%- @T('Service') %> |
+ <%- @T('Description') %> |
+
+
+
+ <% for integration in @integrations: %>
+
+
+ <% if !integration.state.current(): %>
+ <%- @Icon('status', 'inactive inline') %>
+ <% else: %>
+ <%- @Icon('status', 'ok inline') %>
+ <% end %>
+ |
+ <%= integration.name %> |
+ <%= integration.description %> |
+
+ <% end %>
+
+
+
diff --git a/app/assets/javascripts/app/views/settings/form.jst.eco b/app/assets/javascripts/app/views/settings/form.jst.eco
new file mode 100644
index 000000000..8596311c9
--- /dev/null
+++ b/app/assets/javascripts/app/views/settings/form.jst.eco
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/settings/item.jst.eco b/app/assets/javascripts/app/views/settings/item.jst.eco
index b4b72d923..88a89dc98 100644
--- a/app/assets/javascripts/app/views/settings/item.jst.eco
+++ b/app/assets/javascripts/app/views/settings/item.jst.eco
@@ -1,8 +1,8 @@
\ No newline at end of file
diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss
index bbe837494..4c7c0eb01 100644
--- a/app/assets/stylesheets/zammad.scss
+++ b/app/assets/stylesheets/zammad.scss
@@ -6962,6 +6962,7 @@ output {
}
th, td {
+ vertical-align: top;
padding: 10px;
border: 1px solid hsl(198,18%,86%);
}
diff --git a/app/models/observer/ticket/notification.rb b/app/models/observer/ticket/notification.rb
deleted file mode 100644
index 35ce83c91..000000000
--- a/app/models/observer/ticket/notification.rb
+++ /dev/null
@@ -1,180 +0,0 @@
-# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
-
-require 'event_buffer'
-require 'notification_factory'
-
-class Observer::Ticket::Notification < ActiveRecord::Observer
- observe :ticket, 'ticket::_article'
-
- def self.transaction(params)
-
- return if params[:disable_notification]
-
- # return if we run import mode
- return if Setting.get('import_mode')
-
- # get buffer
- list = EventBuffer.list('notification')
-
- # reset buffer
- EventBuffer.reset('notification')
-
- via_web = false
- if ENV['RACK_ENV'] || Rails.configuration.webserver_is_active
- via_web = true
- end
-
- # get uniq objects
- list_objects = get_uniq_changes(list)
- list_objects.each {|_ticket_id, item|
-
- # send background job
- Delayed::Job.enqueue(Observer::Ticket::Notification::BackgroundJob.new(item, via_web))
- }
- end
-
-=begin
-
- result = get_uniq_changes(events)
-
- result = {
- 1 => {
- type: 'create',
- ticket_id: 123,
- article_id: 123,
- },
- 9 => {
- type: 'update',
- ticket_id: 123,
- changes: {
- attribute1: [before, now],
- attribute2: [before, now],
- }
- },
- }
-
- result = {
- 9 => {
- type: 'update',
- ticket_id: 123,
- article_id: 123,
- changes: {
- attribute1: [before, now],
- attribute2: [before, now],
- }
- },
- }
-
-=end
-
- def self.get_uniq_changes(events)
- list_objects = {}
- events.each { |event|
-
- # get current state of objects
- if event[:name] == 'Ticket::Article'
- article = Ticket::Article.lookup(id: event[:id])
-
- # next if article is already deleted
- next if !article
-
- ticket = article.ticket
- if !list_objects[ticket.id]
- list_objects[ticket.id] = {}
- end
- list_objects[ticket.id][:article_id] = article.id
- list_objects[ticket.id][:ticket_id] = ticket.id
-
- if !list_objects[ticket.id][:type]
- list_objects[ticket.id][:type] = 'update'
- end
-
- elsif event[:name] == 'Ticket'
- ticket = Ticket.lookup(id: event[:id])
-
- # next if ticket is already deleted
- next if !ticket
-
- if !list_objects[ticket.id]
- list_objects[ticket.id] = {}
- end
- list_objects[ticket.id][:ticket_id] = ticket.id
-
- if !list_objects[ticket.id][:type] || list_objects[ticket.id][:type] == 'update'
- list_objects[ticket.id][:type] = event[:type]
- end
-
- # merge changes
- if event[:changes]
- if !list_objects[ticket.id][:changes]
- list_objects[ticket.id][:changes] = event[:changes]
- else
- event[:changes].each {|key, value|
- if !list_objects[ticket.id][:changes][key]
- list_objects[ticket.id][:changes][key] = value
- else
- list_objects[ticket.id][:changes][key][1] = value[1]
- end
- }
- end
- end
- else
- raise "unknown object for notification #{event[:name]}"
- end
- }
- list_objects
- end
-
- def after_create(record)
-
- # return if we run import mode
- return if Setting.get('import_mode')
-
- # Rails.logger.info 'CREATED!!!!'
- # Rails.logger.info record.inspect
- e = {
- name: record.class.name,
- type: 'create',
- data: record,
- id: record.id,
- }
- EventBuffer.add('notification', e)
- end
-
- def before_update(record)
-
- # return if we run import mode
- return if Setting.get('import_mode')
-
- # ignore updates on articles / we just want send notifications on ticket updates
- return if record.class.name == 'Ticket::Article'
-
- # ignore certain attributes
- real_changes = {}
- record.changes.each {|key, value|
- next if key == 'updated_at'
- next if key == 'first_response'
- next if key == 'close_time'
- next if key == 'last_contact_agent'
- next if key == 'last_contact_customer'
- next if key == 'last_contact'
- next if key == 'article_count'
- next if key == 'create_article_type_id'
- next if key == 'create_article_sender_id'
- real_changes[key] = value
- }
-
- # do not send anything if nothing has changed
- return if real_changes.empty?
-
- e = {
- name: record.class.name,
- type: 'update',
- data: record,
- changes: real_changes,
- id: record.id,
- }
- EventBuffer.add('notification', e)
- end
-
-end
diff --git a/app/models/observer/transaction.rb b/app/models/observer/transaction.rb
index c3a813c68..5644a23c8 100644
--- a/app/models/observer/transaction.rb
+++ b/app/models/observer/transaction.rb
@@ -1,10 +1,185 @@
-class Observer::Transaction
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class Observer::Transaction < ActiveRecord::Observer
+ observe :ticket, 'ticket::_article'
def self.commit(params = {})
- # execute ticket transactions
- Observer::Ticket::Notification.transaction(params)
+ # add attribute if execution is via web
+ params[:via_web] = false
+ if ENV['RACK_ENV'] || Rails.configuration.webserver_is_active
+ params[:via_web] = true
+ end
+ # execute object transactions
+ Observer::Transaction.perform(params)
+ end
+
+ def self.perform(params)
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
+ # get buffer
+ list = EventBuffer.list('transaction')
+
+ # reset buffer
+ EventBuffer.reset('transaction')
+
+ # get uniq objects
+ list_objects = get_uniq_changes(list)
+ list_objects.each {|_id, item|
+
+ # send background job
+ Delayed::Job.enqueue(Transaction::BackgroundJob.new(item, params))
+ }
+ end
+
+=begin
+
+ result = get_uniq_changes(events)
+
+ result = {
+ 1 => {
+ object: 'Ticket',
+ type: 'create',
+ ticket_id: 123,
+ article_id: 123,
+ },
+ 9 => {
+ object: 'Ticket',
+ type: 'update',
+ ticket_id: 123,
+ changes: {
+ attribute1: [before, now],
+ attribute2: [before, now],
+ }
+ },
+ }
+
+ result = {
+ 9 => {
+ object: 'Ticket',
+ type: 'update',
+ ticket_id: 123,
+ article_id: 123,
+ changes: {
+ attribute1: [before, now],
+ attribute2: [before, now],
+ }
+ },
+ }
+
+=end
+
+ def self.get_uniq_changes(events)
+ list_objects = {}
+ events.each { |event|
+
+ # get current state of objects
+ if event[:name] == 'Ticket::Article'
+ article = Ticket::Article.lookup(id: event[:id])
+
+ # next if article is already deleted
+ next if !article
+
+ ticket = article.ticket
+ if !list_objects[ticket.id]
+ list_objects[ticket.id] = {}
+ end
+ list_objects[ticket.id][:object] = 'Ticket'
+ list_objects[ticket.id][:article_id] = article.id
+ list_objects[ticket.id][:ticket_id] = ticket.id
+
+ if !list_objects[ticket.id][:type]
+ list_objects[ticket.id][:type] = 'update'
+ end
+
+ elsif event[:name] == 'Ticket'
+ ticket = Ticket.lookup(id: event[:id])
+
+ # next if ticket is already deleted
+ next if !ticket
+
+ if !list_objects[ticket.id]
+ list_objects[ticket.id] = {}
+ end
+ list_objects[ticket.id][:object] = 'Ticket'
+ list_objects[ticket.id][:ticket_id] = ticket.id
+
+ if !list_objects[ticket.id][:type] || list_objects[ticket.id][:type] == 'update'
+ list_objects[ticket.id][:type] = event[:type]
+ end
+
+ # merge changes
+ if event[:changes]
+ if !list_objects[ticket.id][:changes]
+ list_objects[ticket.id][:changes] = event[:changes]
+ else
+ event[:changes].each {|key, value|
+ if !list_objects[ticket.id][:changes][key]
+ list_objects[ticket.id][:changes][key] = value
+ else
+ list_objects[ticket.id][:changes][key][1] = value[1]
+ end
+ }
+ end
+ end
+ else
+ raise "unknown object for integration #{event[:name]}"
+ end
+ }
+ list_objects
+ end
+
+ def after_create(record)
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
+ e = {
+ name: record.class.name,
+ type: 'create',
+ data: record,
+ id: record.id,
+ }
+ EventBuffer.add('transaction', e)
+ end
+
+ def before_update(record)
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
+ # ignore updates on articles / we just want send integrations on ticket updates
+ return if record.class.name == 'Ticket::Article'
+
+ # ignore certain attributes
+ real_changes = {}
+ record.changes.each {|key, value|
+ next if key == 'updated_at'
+ next if key == 'first_response'
+ next if key == 'close_time'
+ next if key == 'last_contact_agent'
+ next if key == 'last_contact_customer'
+ next if key == 'last_contact'
+ next if key == 'article_count'
+ next if key == 'create_article_type_id'
+ next if key == 'create_article_sender_id'
+ real_changes[key] = value
+ }
+
+ # do not send anything if nothing has changed
+ return if real_changes.empty?
+
+ e = {
+ name: record.class.name,
+ type: 'update',
+ data: record,
+ changes: real_changes,
+ id: record.id,
+ }
+ EventBuffer.add('transaction', e)
end
end
diff --git a/app/models/ticket.rb b/app/models/ticket.rb
index 830be69b5..05530bdf8 100644
--- a/app/models/ticket.rb
+++ b/app/models/ticket.rb
@@ -180,12 +180,12 @@ returns
tickets.each { |ticket|
# send notification
- bg = Observer::Ticket::Notification::BackgroundJob.new(
+ Transaction::BackgroundJob.run(
+ object: 'Ticket',
+ type: 'reminder_reached',
ticket_id: ticket.id,
article_id: ticket.articles.last.id,
- type: 'reminder_reached',
)
- bg.perform
result.push ticket
}
@@ -220,23 +220,23 @@ returns
# send escalation
if ticket.escalation_time < Time.zone.now
- bg = Observer::Ticket::Notification::BackgroundJob.new(
+ Transaction::BackgroundJob.run(
+ object: 'Ticket',
+ type: 'escalation',
ticket_id: ticket.id,
article_id: ticket.articles.last.id,
- type: 'escalation',
)
- bg.perform
result.push ticket
next
end
# check if warning need to be sent
- bg = Observer::Ticket::Notification::BackgroundJob.new(
+ Transaction::BackgroundJob.run(
+ object: 'Ticket',
+ type: 'escalation_warning',
ticket_id: ticket.id,
article_id: ticket.articles.last.id,
- type: 'escalation_warning',
)
- bg.perform
result.push ticket
}
result
diff --git a/app/models/transaction.rb b/app/models/transaction.rb
new file mode 100644
index 000000000..9dc9b0b8b
--- /dev/null
+++ b/app/models/transaction.rb
@@ -0,0 +1,3 @@
+class Transaction
+
+end
diff --git a/app/models/transaction/background_job.rb b/app/models/transaction/background_job.rb
new file mode 100644
index 000000000..263a269a3
--- /dev/null
+++ b/app/models/transaction/background_job.rb
@@ -0,0 +1,36 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class Transaction::BackgroundJob
+ def initialize(item, params = {})
+
+=begin
+ {
+ object: 'Ticket',
+ type: 'update',
+ ticket_id: 123,
+ via_web: true,
+ changes: {
+ 'attribute1' => [before,now],
+ 'attribute2' => [before,now],
+ }
+ },
+=end
+
+ @item = item
+ @params = params
+ end
+
+ def perform
+ Setting.where(area: 'Transaction::Backend').order(:name).each {|setting|
+ backend = Setting.get(setting.name)
+ integration = Kernel.const_get(backend).new(@item, @params)
+ integration.perform
+ }
+ end
+
+ def self.run(item, params = {})
+ generic = new(item, params)
+ generic.perform
+ end
+
+end
diff --git a/app/models/observer/ticket/notification/background_job.rb b/app/models/transaction/notification.rb
similarity index 82%
rename from app/models/observer/ticket/notification/background_job.rb
rename to app/models/transaction/notification.rb
index 72b0e197c..1d2780faa 100644
--- a/app/models/observer/ticket/notification/background_job.rb
+++ b/app/models/transaction/notification.rb
@@ -1,24 +1,29 @@
-# encoding: utf-8
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
-class Observer::Ticket::Notification::BackgroundJob
- def initialize(params, via_web = false)
+class Transaction::Notification
=begin
+ {
+ object: 'Ticket',
type: 'update',
ticket_id: 123,
+ via_web: true,
changes: {
- 'attribute1' => [before,now],
- 'attribute2' => [before,now],
+ 'attribute1' => [before, now],
+ 'attribute2' => [before, now],
}
+ },
=end
- @p = params
- @via_web = via_web
+
+ def initialize(item, params = {})
+ @item = item
+ @params = params
end
def perform
- ticket = Ticket.find(@p[:ticket_id])
- if @p[:article_id]
- article = Ticket::Article.find(@p[:article_id])
+ ticket = Ticket.find(@item[:ticket_id])
+ if @item[:article_id]
+ article = Ticket::Article.find(@item[:article_id])
end
# find recipients
@@ -59,7 +64,7 @@ class Observer::Ticket::Notification::BackgroundJob
end
already_checked_recipient_ids = {}
possible_recipients.each {|user|
- result = NotificationFactory::Mailer.notification_settings(user, ticket, @p[:type])
+ result = NotificationFactory::Mailer.notification_settings(user, ticket, @item[:type])
next if !result
next if already_checked_recipient_ids[result[:user].id]
already_checked_recipient_ids[result[:user].id] = true
@@ -73,7 +78,7 @@ class Observer::Ticket::Notification::BackgroundJob
channels = item[:channels]
# ignore user who changed it by him self via web
- if @via_web
+ if @params[:via_web]
next if article && article.updated_by_id == user.id
next if !article && ticket.updated_by_id == user.id
end
@@ -83,10 +88,10 @@ class Observer::Ticket::Notification::BackgroundJob
# ignore if no changes has been done
changes = human_changes(user, ticket)
- next if @p[:type] == 'update' && !article && (!changes || changes.empty?)
+ next if @item[:type] == 'update' && !article && (!changes || changes.empty?)
# check if today already notified
- if @p[:type] == 'reminder_reached' || @p[:type] == 'escalation' || @p[:type] == 'escalation_warning'
+ if @item[:type] == 'reminder_reached' || @item[:type] == 'escalation' || @item[:type] == 'escalation_warning'
identifier = user.email
if !identifier || identifier == ''
identifier = user.login
@@ -94,7 +99,7 @@ class Observer::Ticket::Notification::BackgroundJob
already_notified = false
History.list('Ticket', ticket.id).each {|history|
next if history['type'] != 'notification'
- next if history['value_to'] !~ /\(#{Regexp.escape(@p[:type])}:/
+ next if history['value_to'] !~ /\(#{Regexp.escape(@item[:type])}:/
next if history['value_to'] !~ /#{Regexp.escape(identifier)}\(/
next if !history['created_at'].today?
already_notified = true
@@ -110,59 +115,59 @@ class Observer::Ticket::Notification::BackgroundJob
created_by_id = ticket.updated_by_id || 1
# delete old notifications
- if @p[:type] == 'reminder_reached'
+ if @item[:type] == 'reminder_reached'
seen = false
created_by_id = 1
- OnlineNotification.remove_by_type('Ticket', ticket.id, @p[:type], user)
+ OnlineNotification.remove_by_type('Ticket', ticket.id, @item[:type], user)
- elsif @p[:type] == 'escalation' || @p[:type] == 'escalation_warning'
+ elsif @item[:type] == 'escalation' || @item[:type] == 'escalation_warning'
seen = false
created_by_id = 1
OnlineNotification.remove_by_type('Ticket', ticket.id, 'escalation', user)
OnlineNotification.remove_by_type('Ticket', ticket.id, 'escalation_warning', user)
# on updates without state changes create unseen messages
- elsif @p[:type] != 'create' && (!@p[:changes] || @p[:changes].empty? || !@p[:changes]['state_id'])
+ elsif @item[:type] != 'create' && (!@item[:changes] || @item[:changes].empty? || !@item[:changes]['state_id'])
seen = false
else
seen = ticket.online_notification_seen_state(user.id)
end
OnlineNotification.add(
- type: @p[:type],
+ type: @item[:type],
object: 'Ticket',
o_id: ticket.id,
seen: seen,
created_by_id: created_by_id,
user_id: user.id,
)
- Rails.logger.debug "sent ticket online notifiaction to agent (#{@p[:type]}/#{ticket.id}/#{user.email})"
+ Rails.logger.debug "sent ticket online notifiaction to agent (#{@item[:type]}/#{ticket.id}/#{user.email})"
end
# ignore email channel notificaiton and empty emails
if !channels['email'] || !user.email || user.email == ''
- add_recipient_list(ticket, user, used_channels, @p[:type])
+ add_recipient_list(ticket, user, used_channels, @item[:type])
next
end
used_channels.push 'email'
- add_recipient_list(ticket, user, used_channels, @p[:type])
+ add_recipient_list(ticket, user, used_channels, @item[:type])
# get user based notification template
# if create, send create message / block update messages
template = nil
- if @p[:type] == 'create'
+ if @item[:type] == 'create'
template = 'ticket_create'
- elsif @p[:type] == 'update'
+ elsif @item[:type] == 'update'
template = 'ticket_update'
- elsif @p[:type] == 'reminder_reached'
+ elsif @item[:type] == 'reminder_reached'
template = 'ticket_reminder_reached'
- elsif @p[:type] == 'escalation'
+ elsif @item[:type] == 'escalation'
template = 'ticket_escalation'
- elsif @p[:type] == 'escalation_warning'
+ elsif @item[:type] == 'escalation_warning'
template = 'ticket_escalation_warning'
else
- raise "unknown type for notification #{@p[:type]}"
+ raise "unknown type for notification #{@item[:type]}"
end
NotificationFactory::Mailer.notification(
@@ -177,7 +182,7 @@ class Observer::Ticket::Notification::BackgroundJob
references: ticket.get_references,
main_object: ticket,
)
- Rails.logger.debug "sent ticket email notifiaction to agent (#{@p[:type]}/#{ticket.id}/#{user.email})"
+ Rails.logger.debug "sent ticket email notifiaction to agent (#{@item[:type]}/#{ticket.id}/#{user.email})"
end
end
@@ -200,14 +205,14 @@ class Observer::Ticket::Notification::BackgroundJob
def human_changes(user, record)
- return {} if !@p[:changes]
+ return {} if !@item[:changes]
locale = user.preferences[:locale] || 'en-us'
# only show allowed attributes
attribute_list = ObjectManager::Attribute.by_object_as_hash('Ticket', user)
#puts "AL #{attribute_list.inspect}"
user_related_changes = {}
- @p[:changes].each {|key, value|
+ @item[:changes].each {|key, value|
# if no config exists, use all attributes
if !attribute_list || attribute_list.empty?
diff --git a/app/models/transaction/slack.rb b/app/models/transaction/slack.rb
new file mode 100644
index 000000000..2499f7a18
--- /dev/null
+++ b/app/models/transaction/slack.rb
@@ -0,0 +1,227 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class Transaction::Slack
+=begin
+ {
+ object: 'Ticket',
+ type: 'update',
+ ticket_id: 123,
+ via_web: true,
+ changes: {
+ 'attribute1' => [before, now],
+ 'attribute2' => [before, now],
+ }
+ },
+=end
+ def initialize(item, params = {})
+ @item = item
+ @params = params
+ end
+
+ def perform
+ return if @item[:object] != 'Ticket'
+ return if !Setting.get('slack_integration')
+ logo_url = "#{Setting.get('http_type')}://#{Setting.get('fqdn')}/assets/images/#{Setting.get('product_logo')}"
+
+ config = Setting.get('slack_config')
+ return if !config
+ return if !config['items']
+
+ ticket = Ticket.find(@item[:ticket_id])
+ if @item[:article_id]
+ article = Ticket::Article.find(@item[:article_id])
+ end
+
+ # ignore if no changes has been done
+ changes = human_changes(ticket)
+ return if @item[:type] == 'update' && !article && (!changes || changes.empty?)
+
+ # get user based notification template
+ # if create, send create message / block update messages
+ template = nil
+ if @item[:type] == 'create'
+ template = 'ticket_create'
+ elsif @item[:type] == 'update'
+ template = 'ticket_update'
+ elsif @item[:type] == 'reminder_reached'
+ template = 'ticket_reminder_reached'
+ elsif @item[:type] == 'escalation'
+ template = 'ticket_escalation'
+ elsif @item[:type] == 'escalation_warning'
+ template = 'ticket_escalation_warning'
+ else
+ raise "unknown type for notification #{@item[:type]}"
+ end
+
+ user = User.find(1)
+ result = NotificationFactory::Slack.template(
+ template: template,
+ locale: user[:preferences][:locale],
+ objects: {
+ ticket: ticket,
+ article: article,
+ changes: changes,
+ },
+ )
+
+ # good, warning, danger
+ color = '#000000'
+ ticket_state_type = ticket.state.state_type.name
+ if ticket.escalation_time && ticket.escalation_time > Time.zone.now
+ color = '#f35912'
+ elsif ticket_state_type == 'pending reminder'
+ if ticket.pending_time && ticket.pending_time < Time.zone.now
+ color = '#faab00'
+ end
+ elsif ticket_state_type =~ /^(new|open)$/
+ color = '#faab00'
+ elsif ticket_state_type == 'closed'
+ color = '#38ad69'
+ end
+
+ config['items'].each {|item|
+
+ # check action
+ if item['types']
+ hit = false
+ item['types'].each {|type|
+ next if type.to_s != @item[:type].to_s
+ hit = true
+ break
+ }
+ next if !hit
+ end
+
+ # check group
+ if item['group_ids']
+ hit = false
+ item['group_ids'].each {|group_id|
+ next if group_id.to_s != ticket.group_id.to_s
+ hit = true
+ break
+ }
+ next if !hit
+ end
+
+ Rails.logger.debug "sent webhook (#{@item[:type]}/#{ticket.id}/#{item['webhook']})"
+ notifier = Slack::Notifier.new(
+ item['webhook'],
+ channel: item['channel'],
+ username: item['username'],
+ icon_url: logo_url,
+ mrkdwn: true,
+ )
+ if item['expand']
+ body = "#{result[:subject]}\n#{result[:body]}"
+ result = notifier.ping body
+ else
+ attachment = {
+ text: result[:body],
+ mrkdwn_in: ['text'],
+ color: color,
+ }
+ result = notifier.ping result[:subject],
+ attachments: [attachment]
+ end
+ if !result
+ Rails.logger.error "Unable to post webhook: #{item['webhook']}"
+ end
+ if result.code.to_s != '200' && result.code.to_s != '201'
+ Rails.logger.error "Unable to post webhook: #{item['webhook']}: #{result.inspect}"
+ end
+ Rails.logger.debug "sent webhook (#{@item[:type]}/#{ticket.id}/#{item['webhook']})"
+ }
+
+ end
+
+ def human_changes(record)
+
+ return {} if !@item[:changes]
+ user = User.find(1)
+ locale = user.preferences[:locale] || 'en-us'
+
+ # only show allowed attributes
+ attribute_list = ObjectManager::Attribute.by_object_as_hash('Ticket', user)
+ #puts "AL #{attribute_list.inspect}"
+ user_related_changes = {}
+ @item[:changes].each {|key, value|
+
+ # if no config exists, use all attributes
+ if !attribute_list || attribute_list.empty?
+ user_related_changes[key] = value
+
+ # if config exists, just use existing attributes for user
+ elsif attribute_list[key.to_s]
+ user_related_changes[key] = value
+ end
+ }
+
+ changes = {}
+ user_related_changes.each {|key, value|
+
+ # get attribute name
+ attribute_name = key.to_s
+ object_manager_attribute = attribute_list[attribute_name]
+ if attribute_name[-3, 3] == '_id'
+ attribute_name = attribute_name[ 0, attribute_name.length - 3 ].to_s
+ end
+
+ # add item to changes hash
+ if key.to_s == attribute_name
+ changes[attribute_name] = value
+ end
+
+ # if changed item is an _id field/reference, do an lookup for the realy values
+ value_id = []
+ value_str = [ value[0], value[1] ]
+ if key.to_s[-3, 3] == '_id'
+ value_id[0] = value[0]
+ value_id[1] = value[1]
+
+ if record.respond_to?(attribute_name) && record.send(attribute_name)
+ relation_class = record.send(attribute_name).class
+ if relation_class && value_id[0]
+ relation_model = relation_class.lookup(id: value_id[0])
+ if relation_model
+ if relation_model['name']
+ value_str[0] = relation_model['name']
+ elsif relation_model.respond_to?('fullname')
+ value_str[0] = relation_model.send('fullname')
+ end
+ end
+ end
+ if relation_class && value_id[1]
+ relation_model = relation_class.lookup(id: value_id[1])
+ if relation_model
+ if relation_model['name']
+ value_str[1] = relation_model['name']
+ elsif relation_model.respond_to?('fullname')
+ value_str[1] = relation_model.send('fullname')
+ end
+ end
+ end
+ end
+ end
+
+ # check if we have an dedcated display name for it
+ display = attribute_name
+ if object_manager_attribute && object_manager_attribute[:display]
+
+ # delete old key
+ changes.delete(display)
+
+ # set new key
+ display = object_manager_attribute[:display].to_s
+ end
+ changes[display] = if object_manager_attribute && object_manager_attribute[:translate]
+ from = Translation.translate(locale, value_str[0])
+ to = Translation.translate(locale, value_str[1])
+ [from, to]
+ else
+ [value_str[0].to_s, value_str[1].to_s]
+ end
+ }
+ changes
+ end
+
+end
diff --git a/app/views/mailer/ticket_create/de.html.erb b/app/views/mailer/ticket_create/de.html.erb
index d79f442b5..8806b0875 100644
--- a/app/views/mailer/ticket_create/de.html.erb
+++ b/app/views/mailer/ticket_create/de.html.erb
@@ -14,7 +14,7 @@ Neues Ticket (<%= d 'ticket.title' %>)
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_create/en.html.erb b/app/views/mailer/ticket_create/en.html.erb
index 06cb4bb28..7a71d711d 100644
--- a/app/views/mailer/ticket_create/en.html.erb
+++ b/app/views/mailer/ticket_create/en.html.erb
@@ -14,7 +14,7 @@ New Ticket (<%= d 'ticket.title' %>)
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_escalation/de.html.erb b/app/views/mailer/ticket_escalation/de.html.erb
index 1b33dcddf..c32ac9300 100644
--- a/app/views/mailer/ticket_escalation/de.html.erb
+++ b/app/views/mailer/ticket_escalation/de.html.erb
@@ -8,7 +8,7 @@ Ticket ist eskaliert (<%= d 'ticket.title' %>)
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_escalation/en.html.erb b/app/views/mailer/ticket_escalation/en.html.erb
index 017472a7e..4bda1e9c2 100644
--- a/app/views/mailer/ticket_escalation/en.html.erb
+++ b/app/views/mailer/ticket_escalation/en.html.erb
@@ -8,7 +8,7 @@ Ticket is escalated (<%= d 'ticket.title' %>)
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_escalation_warning/de.html.erb b/app/views/mailer/ticket_escalation_warning/de.html.erb
index 4ee2e9671..b257a2142 100644
--- a/app/views/mailer/ticket_escalation_warning/de.html.erb
+++ b/app/views/mailer/ticket_escalation_warning/de.html.erb
@@ -8,7 +8,7 @@ Ticket wird eskalieren (<%= d 'ticket.title' %>)
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_escalation_warning/en.html.erb b/app/views/mailer/ticket_escalation_warning/en.html.erb
index d376e0c20..341663a1b 100644
--- a/app/views/mailer/ticket_escalation_warning/en.html.erb
+++ b/app/views/mailer/ticket_escalation_warning/en.html.erb
@@ -1,14 +1,14 @@
-Ticket will escalated (<%= d 'ticket.title' %>)
+Ticket will escalate (<%= d 'ticket.title' %>)
Hi <%= d 'recipient.firstname' %>,
-a ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" will escalate at "<%= d 'ticket.escalation_time' %>"!
+A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" will escalate at "<%= d 'ticket.escalation_time' %>"!
<% if @objects[:article] %>
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_reminder_reached/de.html.erb b/app/views/mailer/ticket_reminder_reached/de.html.erb
index 5e0c98911..e919e7dfa 100644
--- a/app/views/mailer/ticket_reminder_reached/de.html.erb
+++ b/app/views/mailer/ticket_reminder_reached/de.html.erb
@@ -8,7 +8,7 @@ Warten auf Erinnerung erreicht! (<%= d 'ticket.title' %>)
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_reminder_reached/en.html.erb b/app/views/mailer/ticket_reminder_reached/en.html.erb
index 91830d753..255614252 100644
--- a/app/views/mailer/ticket_reminder_reached/en.html.erb
+++ b/app/views/mailer/ticket_reminder_reached/en.html.erb
@@ -2,13 +2,13 @@ Reminder reached (<%= d 'ticket.title' %>)
Hi <%= d 'recipient.firstname' %>,
-Ticket needs attention, reminder reached for ticket (<%= d 'ticket.title' %>) with customer "<%= d 'ticket.customer.longname' %>".
+A ticket needs attention, reminder reached for (<%= d 'ticket.title' %>) with customer "<%= d 'ticket.customer.longname' %>".
<% if @objects[:article] %>
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_update/de.html.erb b/app/views/mailer/ticket_update/de.html.erb
index 8f47080e3..576be3f21 100644
--- a/app/views/mailer/ticket_update/de.html.erb
+++ b/app/views/mailer/ticket_update/de.html.erb
@@ -19,7 +19,7 @@ Ticket (<%= d 'ticket.title' %>) wurde von "<%= d 'ticket.updated_by.longname
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/mailer/ticket_update/en.html.erb b/app/views/mailer/ticket_update/en.html.erb
index 000701a03..df29d9ac0 100644
--- a/app/views/mailer/ticket_update/en.html.erb
+++ b/app/views/mailer/ticket_update/en.html.erb
@@ -19,7 +19,7 @@ Ticket (<%= d 'ticket.title' %>) has been updated by "<%= d 'ticket.updated_b
<%= t 'Information' %>:
- <%= a 'article' %>
+ <%= a_html 'article' %>
<% end %>
diff --git a/app/views/slack/application.md.erb b/app/views/slack/application.md.erb
new file mode 100644
index 000000000..3fec9ad1d
--- /dev/null
+++ b/app/views/slack/application.md.erb
@@ -0,0 +1 @@
+<%= d 'message', false %>
\ No newline at end of file
diff --git a/app/views/slack/ticket_create/en.md.erb b/app/views/slack/ticket_create/en.md.erb
new file mode 100644
index 000000000..594fac928
--- /dev/null
+++ b/app/views/slack/ticket_create/en.md.erb
@@ -0,0 +1,9 @@
+# <%= d 'ticket.title' %>
+_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Created by <%= d 'ticket.updated_by.longname' %> at <%= d 'ticket.updated_at' %>_
+* <%= t 'Group' %>: <%= d 'ticket.group.name' %>
+* <%= t 'Owner' %>: <%= d 'ticket.owner.fullname' %>
+* <%= t 'State' %>: <%= t d 'ticket.state.name' %>
+
+<% if @objects[:article] %>
+<%= a_text 'article' %>
+<% end %>
diff --git a/app/views/slack/ticket_escalation/en.md.erb b/app/views/slack/ticket_escalation/en.md.erb
new file mode 100644
index 000000000..330998489
--- /dev/null
+++ b/app/views/slack/ticket_escalation/en.md.erb
@@ -0,0 +1,7 @@
+# <%= d 'ticket.title' %>
+_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Escalated at <%= d 'ticket.escalation_time' %>_
+A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" is escalated since "<%= d 'ticket.escalation_time' %>"!
+
+<% if @objects[:article] %>
+<%= a_text 'article' %>
+<% end %>
diff --git a/app/views/slack/ticket_escalation_warning/en.md.erb b/app/views/slack/ticket_escalation_warning/en.md.erb
new file mode 100644
index 000000000..15f48c0a3
--- /dev/null
+++ b/app/views/slack/ticket_escalation_warning/en.md.erb
@@ -0,0 +1,7 @@
+# <%= d 'ticket.title' %>
+_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Will escalate at <%= d 'ticket.escalation_time' %>_
+A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" will escalate at "<%= d 'ticket.escalation_time' %>"!
+
+<% if @objects[:article] %>
+<%= a_text 'article' %>
+<% end %>
diff --git a/app/views/slack/ticket_reminder_reached/en.md.erb b/app/views/slack/ticket_reminder_reached/en.md.erb
new file mode 100644
index 000000000..8ae47fc71
--- /dev/null
+++ b/app/views/slack/ticket_reminder_reached/en.md.erb
@@ -0,0 +1,7 @@
+# <%= d 'ticket.title' %>
+_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Reminder reached!_
+A ticket needs attention, reminder reached for (<%= d 'ticket.title' %>) with customer "*<%= d 'ticket.customer.longname' %>*".
+
+<% if @objects[:article] %>
+<%= a_text 'article' %>
+<% end %>
diff --git a/app/views/slack/ticket_update/en.md.erb b/app/views/slack/ticket_update/en.md.erb
new file mode 100644
index 000000000..e391ad115
--- /dev/null
+++ b/app/views/slack/ticket_update/en.md.erb
@@ -0,0 +1,11 @@
+# <%= d 'ticket.title' %>
+_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Updated by <%= d 'ticket.updated_by.longname' %> at <%= d 'ticket.updated_at' %>_
+<% if @objects[:changes] && !@objects[:changes].empty? %>
+ <% @objects[:changes].each do |key, value| %>
+ * <%= t key %>: <%= h value[0] %> -> <%= h value[1] %>
+ <% end %>
+<% end %>
+
+<% if @objects[:article] %>
+<%= a_text 'article' %>
+<% end %>
diff --git a/config/application.rb b/config/application.rb
index 59a17ebcf..4228be027 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -31,7 +31,6 @@ module Zammad
'observer::_ticket::_article::_communicate_facebook',
'observer::_ticket::_article::_communicate_twitter',
'observer::_ticket::_article::_signature_detection',
- 'observer::_ticket::_notification',
'observer::_ticket::_reset_new_state',
'observer::_ticket::_escalation_calculation',
'observer::_ticket::_ref_object_touch',
@@ -42,7 +41,8 @@ module Zammad
'observer::_user::_ticket_organization',
'observer::_user::_geo',
'observer::_organization::_ref_object_touch',
- 'observer::_sla::_ticket_rebuild_escalation'
+ 'observer::_sla::_ticket_rebuild_escalation',
+ 'observer::_transaction'
# REST api path
config.api_path = '/api/v1'
diff --git a/db/migrate/20160415000001_add_slack_integration.rb b/db/migrate/20160415000001_add_slack_integration.rb
new file mode 100644
index 000000000..c9fefc581
--- /dev/null
+++ b/db/migrate/20160415000001_add_slack_integration.rb
@@ -0,0 +1,144 @@
+class AddSlackIntegration < ActiveRecord::Migration
+ def up
+ Setting.create_or_update(
+ title: 'Icinga integration',
+ name: 'icinga_integration',
+ area: 'Integration::Switch',
+ description: 'Define if Icinga (http://www.icinga.org) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'icinga_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: false,
+ preferences: { prio: 1 },
+ frontend: false
+ )
+ Setting.create_or_update(
+ title: 'Sender',
+ name: 'icinga_sender',
+ area: 'Integration::Icinga',
+ description: 'Define the sender email address of Icinga emails.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: false,
+ name: 'icinga_sender',
+ tag: 'input',
+ placeholder: 'icinga@monitoring.example.com',
+ },
+ ],
+ },
+ state: 'icinga@monitoring.example.com',
+ frontend: false,
+ preferences: { prio: 2 },
+ )
+ Setting.create_or_update(
+ title: 'Nagios integration',
+ name: 'nagios_integration',
+ area: 'Integration::Switch',
+ description: 'Define if Nagios (http://www.nagios.org) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'nagios_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: false,
+ preferences: { prio: 1 },
+ frontend: false
+ )
+ Setting.create_or_update(
+ title: 'Sender',
+ name: 'nagios_sender',
+ area: 'Integration::Nagios',
+ description: 'Define the sender email address of Nagios emails.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: false,
+ name: 'nagios_sender',
+ tag: 'input',
+ placeholder: 'nagios@monitoring.example.com',
+ },
+ ],
+ },
+ state: 'nagios@monitoring.example.com',
+ frontend: false,
+ preferences: { prio: 2 },
+ )
+
+ Setting.create_or_update(
+ title: 'Define transaction backend.',
+ name: '0100_notification',
+ area: 'Transaction::Backend',
+ description: 'Define the transaction backend to send agent notifications.',
+ options: {},
+ state: 'Transaction::Notification',
+ frontend: false
+ )
+ Setting.create_or_update(
+ title: 'Define transaction backend.',
+ name: '6000_slack_webhook',
+ area: 'Transaction::Backend',
+ description: 'Define the transaction backend which posts messages to (http://www.slack.com).',
+ options: {},
+ state: 'Transaction::Slack',
+ frontend: false
+ )
+ Setting.create_if_not_exists(
+ title: 'Slack integration',
+ name: 'slack_integration',
+ area: 'Integration::Slack',
+ description: 'Define if Slack (http://www.slack.org) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'slack_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: true,
+ preferences: { prio: 1 },
+ frontend: false
+ )
+ Setting.create_or_update(
+ title: 'Slack config',
+ name: 'slack_config',
+ area: 'Integration::Slack',
+ description: 'Define the slack config.',
+ options: {},
+ state: {
+ items: []
+ },
+ frontend: false,
+ preferences: { prio: 2 },
+ )
+ end
+end
diff --git a/db/seeds.rb b/db/seeds.rb
index 1d87da6a3..f13859168 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1600,7 +1600,7 @@ Setting.create_if_not_exists(
Setting.create_if_not_exists(
title: 'Icinga integration',
name: 'icinga_integration',
- area: 'Integration::Icinga',
+ area: 'Integration::Switch',
description: 'Define if Icinga (http://www.icinga.org) is enabled or not.',
options: {
form: [
@@ -1632,6 +1632,7 @@ Setting.create_if_not_exists(
null: false,
name: 'icinga_sender',
tag: 'input',
+ placeholder: 'icinga@monitoring.example.com',
},
],
},
@@ -1685,7 +1686,7 @@ Setting.create_if_not_exists(
Setting.create_if_not_exists(
title: 'Nagios integration',
name: 'nagios_integration',
- area: 'Integration::Nagios',
+ area: 'Integration::Switch',
description: 'Define if Nagios (http://www.nagios.org) is enabled or not.',
options: {
form: [
@@ -1717,6 +1718,7 @@ Setting.create_if_not_exists(
null: false,
name: 'nagios_sender',
tag: 'input',
+ placeholder: 'nagios@monitoring.example.com',
},
],
},
@@ -1767,6 +1769,59 @@ Setting.create_if_not_exists(
preferences: { prio: 4 },
frontend: false
)
+Setting.create_if_not_exists(
+ title: 'Define transaction backend.',
+ name: '0100_notification',
+ area: 'Transaction::Backend',
+ description: 'Define the transaction backend to send agent notifications.',
+ options: {},
+ state: 'Transaction::Notification',
+ frontend: false
+)
+Setting.create_if_not_exists(
+ title: 'Define transaction backend.',
+ name: '6000_slack_webhook',
+ area: 'Transaction::Backend',
+ description: 'Define the transaction backend which posts messages to (http://www.slack.com).',
+ options: {},
+ state: 'Transaction::Slack',
+ frontend: false
+)
+Setting.create_if_not_exists(
+ title: 'Slack integration',
+ name: 'slack_integration',
+ area: 'Integration::Switch',
+ description: 'Define if Slack (http://www.slack.org) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'slack_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: true,
+ preferences: { prio: 1 },
+ frontend: false
+)
+Setting.create_if_not_exists(
+ title: 'Slack config',
+ name: 'slack_config',
+ area: 'Integration::Slack',
+ description: 'Define the slack config.',
+ options: {},
+ state: {
+ items: []
+ },
+ frontend: false,
+ preferences: { prio: 2 },
+)
signature = Signature.create_if_not_exists(
id: 1,
diff --git a/lib/notification_factory.rb b/lib/notification_factory.rb
index 0311720f5..26de217e3 100644
--- a/lib/notification_factory.rb
+++ b/lib/notification_factory.rb
@@ -5,8 +5,17 @@ module NotificationFactory
result = NotificationFactory.template_read(
template: 'password_reset',
locale: 'en-us',
- format: 'html', # md
- type: 'mailer', # slack
+ format: 'html',
+ type: 'mailer',
+ )
+
+or
+
+ result = NotificationFactory.template_read(
+ template: 'ticket_update',
+ locale: 'en-us',
+ format: 'md',
+ type: 'slack',
)
returns
@@ -56,8 +65,15 @@ returns
=begin
string = NotificationFactory.application_template_read(
- format: 'html', # md
- type: 'mailer', # slack
+ format: 'html',
+ type: 'mailer',
+ )
+
+or
+
+ string = NotificationFactory.application_template_read(
+ format: 'md',
+ type: 'slack',
)
returns
diff --git a/lib/notification_factory/slack.rb b/lib/notification_factory/slack.rb
new file mode 100644
index 000000000..895b2ff05
--- /dev/null
+++ b/lib/notification_factory/slack.rb
@@ -0,0 +1,54 @@
+class NotificationFactory::Slack
+
+=begin
+
+ result = NotificationFactory::Slack.template(
+ template: 'ticket_update',
+ locale: 'en-us',
+ objects: {
+ recipient: User.find(2),
+ ticket: Ticket.find(1)
+ },
+ )
+
+returns
+
+ {
+ subject: 'some subject',
+ body: 'some body',
+ }
+
+=end
+
+ def self.template(data)
+
+ if data[:templateInline]
+ return NotificationFactory::Template.new(data[:objects], data[:locale], data[:templateInline]).render
+ end
+
+ template = NotificationFactory.template_read(
+ locale: data[:locale] || 'en',
+ template: data[:template],
+ format: 'md',
+ type: 'slack',
+ )
+
+ message_subject = NotificationFactory::Template.new(data[:objects], data[:locale], template[:subject]).render
+ message_body = NotificationFactory::Template.new(data[:objects], data[:locale], template[:body]).render
+
+ if !data[:raw]
+ application_template = NotificationFactory.application_template_read(
+ format: 'md',
+ type: 'slack',
+ )
+ data[:objects][:message] = message_body
+ data[:objects][:standalone] = data[:standalone]
+ message_body = NotificationFactory::Template.new(data[:objects], data[:locale], application_template).render
+ end
+ {
+ subject: message_subject.strip!,
+ body: message_body.strip!,
+ }
+ end
+
+end
diff --git a/lib/notification_factory/template.rb b/lib/notification_factory/template.rb
index e953d1e27..a0244a6ce 100644
--- a/lib/notification_factory/template.rb
+++ b/lib/notification_factory/template.rb
@@ -11,6 +11,8 @@ class NotificationFactory::Template
ERB.new(@template).result(binding)
end
+ # d - data of object
+ # d('user.firstname', htmlEscape)
def d(key, escape = nil)
# do validaton, ignore some methodes
@@ -45,19 +47,25 @@ class NotificationFactory::Template
h placeholder
end
+ # c - config
+ # c('fqdn', htmlEscape)
def c(key, escape = nil)
config = Setting.get(key)
return config if escape == false || (escape.nil? && !@escape)
h config
end
+ # t - translation
+ # t('yes', htmlEscape)
def t(key, escape = nil)
translation = Translation.translate(@locale, key)
return translation if escape == false || (escape.nil? && !@escape)
h translation
end
- def a(article)
+ # a_html - article body in html
+ # a_html(article)
+ def a_html(article)
content_type = d "#{article}.content_type", false
if content_type =~ /html/
return d "#{article}.body", false
@@ -65,6 +73,19 @@ class NotificationFactory::Template
d("#{article}.body", false).text2html
end
+ # a_text - article body in text
+ # a_text(article)
+ def a_text(article)
+ content_type = d "#{article}.content_type", false
+ body = d "#{article}.body", false
+ if content_type =~ /html/
+ body = body.html2text
+ end
+ (body.strip + "\n").gsub(/^(.*?)$/, '> \\1')
+ end
+
+ # h - htmlEscape
+ # h('fqdn', htmlEscape)
def h(key)
return key if !key
CGI.escapeHTML(key.to_s)
diff --git a/test/integration/slack_test.rb b/test/integration/slack_test.rb
new file mode 100644
index 000000000..33320dfdb
--- /dev/null
+++ b/test/integration/slack_test.rb
@@ -0,0 +1,192 @@
+# encoding: utf-8
+require 'integration_test_helper'
+require 'slack'
+
+class SlackTest < ActiveSupport::TestCase
+
+ # needed to check correct behavior
+ slack_group = Group.create_if_not_exists(
+ name: 'Slack',
+ updated_by_id: 1,
+ created_by_id: 1
+ )
+
+ # check
+ test 'base' do
+
+ if !ENV['SLACK_CI_CHANNEL']
+ raise "ERROR: Need SLACK_CI_CHANNEL - hint SLACK_CI_CHANNEL='ci-zammad'"
+ end
+ if !ENV['SLACK_CI_WEBHOOK']
+ raise "ERROR: Need SLACK_CI_WEBHOOK - hint SLACK_CI_WEBHOOK='https://hooks.slack.com/services/...'"
+ end
+ if !ENV['SLACK_CI_CHECKER_TOKEN']
+ raise "ERROR: Need SLACK_CI_CHECKER_TOKEN - hint SLACK_CI_CHECKER_TOKEN='...'"
+ end
+
+ channel = ENV['SLACK_CI_CHANNEL']
+ webhook = ENV['SLACK_CI_WEBHOOK']
+
+ # set system mode to done / to activate
+ Setting.set('system_init_done', true)
+ Setting.set('slack_integration', true)
+
+ items = [
+ {
+ group_ids: [slack_group.id],
+ types: %w(create update),
+ webhook: webhook,
+ channel: channel,
+ username: 'zammad bot',
+ expand: false,
+ }
+ ]
+ Setting.set('slack_config', { items: items })
+
+ # case 1
+ customer = User.find(2)
+ hash = hash_gen
+ text = "#{rand_word}... #{hash}"
+
+ default_group = Group.first
+ ticket1 = Ticket.create(
+ title: text,
+ customer_id: customer.id,
+ group_id: default_group.id,
+ state: Ticket::State.find_by(name: 'new'),
+ priority: Ticket::Priority.find_by(name: '2 normal'),
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ article1 = Ticket::Article.create(
+ ticket_id: ticket1.id,
+ body: text,
+ type: Ticket::Article::Type.find_by(name: 'note'),
+ sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+ internal: false,
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ # check if message exists
+ assert_not(slack_check(channel, hash))
+
+ ticket1.state = Ticket::State.find_by(name: 'open')
+ ticket1.save
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ # check if message exists
+ assert_not(slack_check(channel, hash))
+
+ # case 2
+ hash = hash_gen
+ text = "#{rand_word}... #{hash}"
+
+ ticket2 = Ticket.create(
+ title: text,
+ customer_id: customer.id,
+ group_id: slack_group.id,
+ state: Ticket::State.find_by(name: 'new'),
+ priority: Ticket::Priority.find_by(name: '2 normal'),
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ article2 = Ticket::Article.create(
+ ticket_id: ticket2.id,
+ body: text,
+ type: Ticket::Article::Type.find_by(name: 'note'),
+ sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+ internal: false,
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ # check if message exists
+ assert(slack_check(channel, hash))
+
+ hash = hash_gen
+ text = "#{rand_word}... #{hash}"
+
+ ticket2.title = text
+ ticket2.save
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ # check if message exists
+ assert(slack_check(channel, hash))
+
+ end
+
+ def hash_gen
+ (0...10).map { ('a'..'z').to_a[rand(26)] }.join
+ end
+
+ def rand_word
+ words = [
+ 'dog',
+ 'cat',
+ 'house',
+ 'home',
+ 'yesterday',
+ 'tomorrow',
+ 'new york',
+ 'berlin',
+ 'coffee script',
+ 'java script',
+ 'bob smith',
+ 'be open',
+ 'really nice',
+ 'stay tuned',
+ 'be a good boy',
+ 'invent new things',
+ ]
+ words[rand(words.length)]
+ end
+
+ def slack_check(channel_name, search_for)
+
+ Slack.configure do |config|
+ config.token = ENV['SLACK_CI_CHECKER_TOKEN']
+ end
+
+ Slack.auth_test
+
+ client = Slack::Client.new
+ channels = client.channels_list['channels']
+ channel_id = nil
+ channels.each {|channel|
+ next if channel['name'] != channel_name
+ channel_id = channel['id']
+ }
+ if !channel_id
+ raise "ERROR: No such channel '#{channel_name}'"
+ end
+
+ channel_history = client.channels_history(channel: channel_id)
+ if !channel_history
+ raise "ERROR: No history for channel #{channel_name}/#{channel_id}"
+ end
+ if !channel_history['messages']
+ raise "ERROR: No history messages for channel #{channel_name}/#{channel_id}"
+ end
+ channel_history['messages'].each {|message|
+ next if !message['text']
+ if message['text'] =~ /#{search_for}/i
+ p "SUCCESS: message with #{search_for} found!"
+ return true
+ end
+ }
+ #raise "ERROR: No such message containing #{search_for} in history of channel #{channel_name}/#{channel_id}"
+ false
+ end
+
+end
diff --git a/test/unit/ticket_notification_test.rb b/test/unit/ticket_notification_test.rb
index b9cca9088..2193d50ab 100644
--- a/test/unit/ticket_notification_test.rb
+++ b/test/unit/ticket_notification_test.rb
@@ -864,8 +864,8 @@ class TicketNotificationTest < ActiveSupport::TestCase
ticket1.priority = Ticket::Priority.lookup(name: '3 high')
ticket1.save
- list = EventBuffer.list('notification')
- list_objects = Observer::Ticket::Notification.get_uniq_changes(list)
+ list = EventBuffer.list('transaction')
+ list_objects = Observer::Transaction.get_uniq_changes(list)
assert_equal('some notification event test 1', list_objects[ticket1.id][:changes]['title'][0])
assert_equal('some notification event test 1 - #2', list_objects[ticket1.id][:changes]['title'][1])
@@ -878,8 +878,8 @@ class TicketNotificationTest < ActiveSupport::TestCase
ticket1.priority = Ticket::Priority.lookup(name: '1 low')
ticket1.save
- list = EventBuffer.list('notification')
- list_objects = Observer::Ticket::Notification.get_uniq_changes(list)
+ list = EventBuffer.list('transaction')
+ list_objects = Observer::Transaction.get_uniq_changes(list)
assert_equal('some notification event test 1', list_objects[ticket1.id][:changes]['title'][0])
assert_equal('some notification event test 1 - #2 - #3', list_objects[ticket1.id][:changes]['title'][1])
@@ -916,7 +916,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
)
assert(ticket1, 'ticket created - ticket notification template')
- bg = Observer::Ticket::Notification::BackgroundJob.new(
+ bg = Transaction::Notification.new(
ticket_id: ticket1.id,
article_id: article.id,
type: 'update',
@@ -992,7 +992,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_no_match(/pending_till/, result[:body])
assert_no_match(/i18n/, result[:body])
- bg = Observer::Ticket::Notification::BackgroundJob.new(
+ bg = Transaction::Notification.new(
ticket_id: ticket1.id,
article_id: article.id,
type: 'update',