From db47ddf5e8888ec04c80205a416b32a171d0787c Mon Sep 17 00:00:00 2001
From: eastuni
Date: Thu, 7 Sep 2017 18:10:55 +0900
Subject: [PATCH 1/6] merged
---
app/views/settings/_slack_settings.html.erb | 12 +++
lib/redmine_slack/listener.rb | 105 ++++++++++++++++++--
2 files changed, 107 insertions(+), 10 deletions(-)
diff --git a/app/views/settings/_slack_settings.html.erb b/app/views/settings/_slack_settings.html.erb
index ae281b9..133769f 100644
--- a/app/views/settings/_slack_settings.html.erb
+++ b/app/views/settings/_slack_settings.html.erb
@@ -44,3 +44,15 @@
/>
+
+
+ />
+
+
+
+
+
+
diff --git a/lib/redmine_slack/listener.rb b/lib/redmine_slack/listener.rb
index 043fe9b..1ae2194 100644
--- a/lib/redmine_slack/listener.rb
+++ b/lib/redmine_slack/listener.rb
@@ -7,9 +7,6 @@ def controller_issues_new_after_save(context={})
channel = channel_for_project issue.project
url = url_for_project issue.project
- return unless channel and url
- return if issue.is_private?
-
msg = "[#{escape issue.project}] #{escape issue.author} created <#{object_url issue}|#{escape issue}>#{mentions issue.description}"
attachment = {}
@@ -34,6 +31,11 @@ def controller_issues_new_after_save(context={})
:short => true
} if Setting.plugin_redmine_slack['display_watchers'] == 'yes'
+ directSpeak issue, msg, attachment, url if Setting.plugin_redmine_slack[:direct_speak] == '1'
+
+ return unless channel and url
+ return if issue.is_private?
+
speak msg, channel, attachment, url
end
@@ -44,15 +46,28 @@ def controller_issues_edit_after_save(context={})
channel = channel_for_project issue.project
url = url_for_project issue.project
- return unless channel and url and Setting.plugin_redmine_slack['post_updates'] == '1'
- return if issue.is_private?
- return if journal.private_notes?
-
msg = "[#{escape issue.project}] #{escape journal.user.to_s} updated <#{object_url issue}|#{escape issue}>#{mentions journal.notes}"
attachment = {}
attachment[:text] = escape journal.notes if journal.notes
attachment[:fields] = journal.details.map { |d| detail_to_field d }
+
+ directSpeak issue, msg, attachment, url if Setting.plugin_redmine_slack[:direct_speak] == '1'
+
+ # send msg to old user that he was aware of
+ old_user_obj = "nil"
+ journal.details.map { |d| old_user_obj = d if d.prop_key == "assigned_to_id" }
+ if not old_user_obj == "nil"
+ olduser = User.find(old_user_obj.old_value) rescue nil
+ if olduser != nil
+ issue.assigned_to = olduser
+ directSpeak issue, msg, attachment, url, true if Setting.plugin_redmine_slack[:direct_speak] == '1'
+ end
+ end
+
+ return unless channel and url and Setting.plugin_redmine_slack['post_updates'] == '1'
+ return if issue.is_private?
+ return if journal.private_notes?
speak msg, channel, attachment, url
end
@@ -65,9 +80,6 @@ def model_changeset_scan_commit_for_issue_ids_pre_issue_update(context={})
channel = channel_for_project issue.project
url = url_for_project issue.project
- return unless channel and url and issue.save
- return if issue.is_private?
-
msg = "[#{escape issue.project}] #{escape journal.user.to_s} updated <#{object_url issue}|#{escape issue}>"
repository = changeset.repository
@@ -100,6 +112,22 @@ def model_changeset_scan_commit_for_issue_ids_pre_issue_update(context={})
attachment = {}
attachment[:text] = ll(Setting.default_language, :text_status_changed_by_changeset, "<#{revision_url}|#{escape changeset.comments}>")
attachment[:fields] = journal.details.map { |d| detail_to_field d }
+
+ directSpeak issue, msg, attachment, url if Setting.plugin_redmine_slack[:direct_speak] == '1'
+
+ # send msg to old user that he was aware of
+ old_user_obj = "nil"
+ journal.details.map { |d| old_user_obj = d if d.prop_key == "assigned_to_id" }
+ if not old_user_obj == "nil"
+ olduser = User.find(old_user_obj.old_value) rescue nil
+ if olduser != nil
+ issue.assigned_to = olduser
+ directSpeak issue, msg, attachment, url, true if Setting.plugin_redmine_slack[:direct_speak] == '1'
+ end
+ end
+
+ return unless channel and url and issue.save
+ return if issue.is_private?
speak msg, channel, attachment, url
end
@@ -160,6 +188,55 @@ def speak(msg, channel, attachment=nil, url=nil)
Rails.logger.warn(e)
end
end
+
+ def directSpeak(issue, msg, attachment=nil, url=nil, full=false)
+
+ # Filter1. Send direct post if issue was modified not by assignee user
+ if issue.current_journal #if issue is edited
+ (return if issue.assigned_to and issue.current_journal.user.login == issue.assigned_to.login) if Setting.plugin_redmine_slack[:direct_speak_rule] == 'DirectPost_IgnoreMyActions'
+ end
+
+ url = Setting.plugin_redmine_slack[:slack_url] if not url
+ icon = Setting.plugin_redmine_slack[:icon]
+
+ params = {
+ :text => msg,
+ :link_names => 1,
+ }
+
+ params[:username] = "#{issue.author}"
+ if issue.assigned_to
+ params[:channel] = "@#{issue.assigned_to.login}"
+ else
+ params[:channel] = "@slackbot"
+ end
+
+ if attachment
+ # duplicate 'attachment' to 'localAttache' without 'Assignee' field for direct message
+ localAttache = attachment.dup
+ localAttache[:fields] = []
+ attachment[:fields].each {|x| localAttache[:fields] << x if full or not x.has_value?(I18n.t("field_assigned_to"))}
+
+ params[:attachments] = [localAttache]
+ end
+
+ if icon and not icon.empty?
+ if icon.start_with? ':'
+ params[:icon_emoji] = icon
+ else
+ params[:icon_url] = icon
+ end
+ end
+
+ begin
+ client = HTTPClient.new
+ client.ssl_config.cert_store.set_default_paths
+ client.ssl_config.ssl_version = "SSLv23"
+ client.post_async url, {:payload => params.to_json}
+ rescue
+ # Bury exception if connection error
+ end
+ end
private
def escape(msg)
@@ -229,6 +306,7 @@ def detail_to_field(detail)
short = true
value = escape detail.value.to_s
+ old_value = "nil"
case key
when "title", "subject", "description"
@@ -251,9 +329,15 @@ def detail_to_field(detail)
when "assigned_to"
user = User.find(detail.value) rescue nil
value = escape user.to_s
+
+ olduser = User.find(detail.old_value) rescue nil
+ old_value = escape olduser.to_s
when "fixed_version"
version = Version.find(detail.value) rescue nil
value = escape version.to_s
+
+ oldversion = Version.find(detail.old_value) rescue nil
+ old_value = escape oldversion.to_s
when "attachment"
attachment = Attachment.find(detail.prop_key) rescue nil
value = "<#{object_url attachment}|#{escape attachment.filename}>" if attachment
@@ -266,6 +350,7 @@ def detail_to_field(detail)
result = { :title => title, :value => value }
result[:short] = true if short
+ result[:old_value] = old_value
result
end
From 87dcbcdad5421aa7e367099e721821ee16f19f8a Mon Sep 17 00:00:00 2001
From: eastuni
Date: Tue, 7 Aug 2018 16:12:49 +0900
Subject: [PATCH 2/6] my
---
lib/redmine_slack/listener.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/redmine_slack/listener.rb b/lib/redmine_slack/listener.rb
index 1ae2194..2d4bba0 100644
--- a/lib/redmine_slack/listener.rb
+++ b/lib/redmine_slack/listener.rb
@@ -21,7 +21,7 @@ def controller_issues_new_after_save(context={})
:short => true
}, {
:title => I18n.t("field_assigned_to"),
- :value => escape(issue.assigned_to.to_s),
+ :value => escape("@"+issue.assigned_to.login),
:short => true
}]
@@ -328,7 +328,7 @@ def detail_to_field(detail)
value = escape category.to_s
when "assigned_to"
user = User.find(detail.value) rescue nil
- value = escape user.to_s
+ value = escape user.login
olduser = User.find(detail.old_value) rescue nil
old_value = escape olduser.to_s
From 0efc061671a42db0f7cb620d63c140f870352eb1 Mon Sep 17 00:00:00 2001
From: donghyun
Date: Tue, 7 Aug 2018 16:16:14 +0900
Subject: [PATCH 3/6] test
---
lib/redmine_slack/listener.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/redmine_slack/listener.rb b/lib/redmine_slack/listener.rb
index 2d4bba0..a48b227 100644
--- a/lib/redmine_slack/listener.rb
+++ b/lib/redmine_slack/listener.rb
@@ -21,7 +21,7 @@ def controller_issues_new_after_save(context={})
:short => true
}, {
:title => I18n.t("field_assigned_to"),
- :value => escape("@"+issue.assigned_to.login),
+ :value => escape("@"+(issue.assigned_to.nil?"":issue.assigned_to.login)),
:short => true
}]
From 598a82ce0cbb12f54c0db1b1ec449973fc6bb4ca Mon Sep 17 00:00:00 2001
From: donghyun
Date: Tue, 7 Aug 2018 18:08:42 +0900
Subject: [PATCH 4/6] test
---
lib/redmine_slack/listener.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/redmine_slack/listener.rb b/lib/redmine_slack/listener.rb
index a48b227..5f690e2 100644
--- a/lib/redmine_slack/listener.rb
+++ b/lib/redmine_slack/listener.rb
@@ -21,7 +21,7 @@ def controller_issues_new_after_save(context={})
:short => true
}, {
:title => I18n.t("field_assigned_to"),
- :value => escape("@"+(issue.assigned_to.nil?"":issue.assigned_to.login)),
+ :value => escape("@"+issue.assigned_to.login) if issue.assigned_to,
:short => true
}]
From 815031eaf542b8c1223059c7f1ec3c45dbe885f2 Mon Sep 17 00:00:00 2001
From: eastuni
Date: Tue, 7 Aug 2018 18:21:20 +0900
Subject: [PATCH 5/6] test
---
listener.rb | 372 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 372 insertions(+)
create mode 100644 listener.rb
diff --git a/listener.rb b/listener.rb
new file mode 100644
index 0000000..410ce00
--- /dev/null
+++ b/listener.rb
@@ -0,0 +1,372 @@
+require 'httpclient'
+
+class SlackListener < Redmine::Hook::Listener
+ def controller_issues_new_after_save(context={})
+ issue = context[:issue]
+
+ channel = channel_for_project issue.project
+ url = url_for_project issue.project
+
+ msg = "[#{escape issue.project}] #{escape issue.author} created <#{object_url issue}|#{escape issue}>#{mentions issue.description}"
+
+ attachment = {}
+ attachment[:text] = escape issue.description if issue.description
+ attachment[:fields] = [{
+ :title => I18n.t("field_status"),
+ :value => escape(issue.status.to_s),
+ :short => true
+ }, {
+ :title => I18n.t("field_priority"),
+ :value => escape(issue.priority.to_s),
+ :short => true
+ }]
+ attachment[:fields] << {
+ :title => I18n.t("field_assigned_to"),
+ :value => escape("@"+issue.assigned_to.login),
+ :short => true
+ } if issue.assigned_to
+
+ attachment[:fields] << {
+ :title => I18n.t("field_watcher"),
+ :value => escape(issue.watcher_users.join(', ')),
+ :short => true
+ } if Setting.plugin_redmine_slack['display_watchers'] == 'yes'
+
+ directSpeak issue, msg, attachment, url if Setting.plugin_redmine_slack[:direct_speak] == '1'
+
+ return unless channel and url
+ return if issue.is_private?
+
+ speak msg, channel, attachment, url
+ end
+
+ def controller_issues_edit_after_save(context={})
+ issue = context[:issue]
+ journal = context[:journal]
+
+ channel = channel_for_project issue.project
+ url = url_for_project issue.project
+
+ msg = "[#{escape issue.project}] #{escape journal.user.to_s} updated <#{object_url issue}|#{escape issue}>#{mentions journal.notes}"
+
+ attachment = {}
+ attachment[:text] = escape journal.notes if journal.notes
+ attachment[:fields] = journal.details.map { |d| detail_to_field d }
+
+ directSpeak issue, msg, attachment, url if Setting.plugin_redmine_slack[:direct_speak] == '1'
+
+ # send msg to old user that he was aware of
+ old_user_obj = "nil"
+ journal.details.map { |d| old_user_obj = d if d.prop_key == "assigned_to_id" }
+ if not old_user_obj == "nil"
+ olduser = User.find(old_user_obj.old_value) rescue nil
+ if olduser != nil
+ issue.assigned_to = olduser
+ directSpeak issue, msg, attachment, url, true if Setting.plugin_redmine_slack[:direct_speak] == '1'
+ end
+ end
+
+ return unless channel and url and Setting.plugin_redmine_slack['post_updates'] == '1'
+ return if issue.is_private?
+ return if journal.private_notes?
+
+ speak msg, channel, attachment, url
+ end
+
+ def model_changeset_scan_commit_for_issue_ids_pre_issue_update(context={})
+ issue = context[:issue]
+ journal = issue.current_journal
+ changeset = context[:changeset]
+
+ channel = channel_for_project issue.project
+ url = url_for_project issue.project
+
+ msg = "[#{escape issue.project}] #{escape journal.user.to_s} updated <#{object_url issue}|#{escape issue}>"
+
+ repository = changeset.repository
+
+ if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
+ host, port, prefix = $2, $4, $5
+ revision_url = Rails.application.routes.url_for(
+ :controller => 'repositories',
+ :action => 'revision',
+ :id => repository.project,
+ :repository_id => repository.identifier_param,
+ :rev => changeset.revision,
+ :host => host,
+ :protocol => Setting.protocol,
+ :port => port,
+ :script_name => prefix
+ )
+ else
+ revision_url = Rails.application.routes.url_for(
+ :controller => 'repositories',
+ :action => 'revision',
+ :id => repository.project,
+ :repository_id => repository.identifier_param,
+ :rev => changeset.revision,
+ :host => Setting.host_name,
+ :protocol => Setting.protocol
+ )
+ end
+
+ attachment = {}
+ attachment[:text] = ll(Setting.default_language, :text_status_changed_by_changeset, "<#{revision_url}|#{escape changeset.comments}>")
+ attachment[:fields] = journal.details.map { |d| detail_to_field d }
+
+ directSpeak issue, msg, attachment, url if Setting.plugin_redmine_slack[:direct_speak] == '1'
+
+ # send msg to old user that he was aware of
+ old_user_obj = "nil"
+ journal.details.map { |d| old_user_obj = d if d.prop_key == "assigned_to_id" }
+ if not old_user_obj == "nil"
+ olduser = User.find(old_user_obj.old_value) rescue nil
+ if olduser != nil
+ issue.assigned_to = olduser
+ directSpeak issue, msg, attachment, url, true if Setting.plugin_redmine_slack[:direct_speak] == '1'
+ end
+ end
+
+ return unless channel and url and issue.save
+ return if issue.is_private?
+
+ speak msg, channel, attachment, url
+ end
+
+ def controller_wiki_edit_after_save(context = { })
+ return unless Setting.plugin_redmine_slack['post_wiki_updates'] == '1'
+
+ project = context[:project]
+ page = context[:page]
+
+ user = page.content.author
+ project_url = "<#{object_url project}|#{escape project}>"
+ page_url = "<#{object_url page}|#{page.title}>"
+ comment = "[#{project_url}] #{page_url} updated by *#{user}*"
+
+ channel = channel_for_project project
+ url = url_for_project project
+
+ attachment = nil
+ if not page.content.comments.empty?
+ attachment = {}
+ attachment[:text] = "#{escape page.content.comments}"
+ end
+
+ speak comment, channel, attachment, url
+ end
+
+ def speak(msg, channel, attachment=nil, url=nil)
+ url = Setting.plugin_redmine_slack['slack_url'] if not url
+ username = Setting.plugin_redmine_slack['username']
+ icon = Setting.plugin_redmine_slack['icon']
+
+ params = {
+ :text => msg,
+ :link_names => 1,
+ }
+
+ params[:username] = username if username
+ params[:channel] = channel if channel
+
+ params[:attachments] = [attachment] if attachment
+
+ if icon and not icon.empty?
+ if icon.start_with? ':'
+ params[:icon_emoji] = icon
+ else
+ params[:icon_url] = icon
+ end
+ end
+
+ begin
+ client = HTTPClient.new
+ client.ssl_config.cert_store.set_default_paths
+ client.ssl_config.ssl_version = :auto
+ client.post_async url, {:payload => params.to_json}
+ rescue Exception => e
+ Rails.logger.warn("cannot connect to #{url}")
+ Rails.logger.warn(e)
+ end
+ end
+
+ def directSpeak(issue, msg, attachment=nil, url=nil, full=false)
+
+ # Filter1. Send direct post if issue was modified not by assignee user
+ if issue.current_journal #if issue is edited
+ (return if issue.assigned_to and issue.current_journal.user.login == issue.assigned_to.login) if Setting.plugin_redmine_slack[:direct_speak_rule] == 'DirectPost_IgnoreMyActions'
+ end
+
+ url = Setting.plugin_redmine_slack[:slack_url] if not url
+ icon = Setting.plugin_redmine_slack[:icon]
+
+ params = {
+ :text => msg,
+ :link_names => 1,
+ }
+
+ params[:username] = "#{issue.author}"
+ if issue.assigned_to
+ params[:channel] = "@#{issue.assigned_to.login}"
+ else
+ params[:channel] = "@slackbot"
+ end
+
+ if attachment
+ # duplicate 'attachment' to 'localAttache' without 'Assignee' field for direct message
+ localAttache = attachment.dup
+ localAttache[:fields] = []
+ attachment[:fields].each {|x| localAttache[:fields] << x if full or not x.has_value?(I18n.t("field_assigned_to"))}
+
+ params[:attachments] = [localAttache]
+ end
+
+ if icon and not icon.empty?
+ if icon.start_with? ':'
+ params[:icon_emoji] = icon
+ else
+ params[:icon_url] = icon
+ end
+ end
+
+ begin
+ client = HTTPClient.new
+ client.ssl_config.cert_store.set_default_paths
+ client.ssl_config.ssl_version = "SSLv23"
+ client.post_async url, {:payload => params.to_json}
+ rescue
+ # Bury exception if connection error
+ end
+ end
+
+private
+ def escape(msg)
+ msg.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">")
+ end
+
+ def object_url(obj)
+ if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
+ host, port, prefix = $2, $4, $5
+ Rails.application.routes.url_for(obj.event_url({
+ :host => host,
+ :protocol => Setting.protocol,
+ :port => port,
+ :script_name => prefix
+ }))
+ else
+ Rails.application.routes.url_for(obj.event_url({
+ :host => Setting.host_name,
+ :protocol => Setting.protocol
+ }))
+ end
+ end
+
+ def url_for_project(proj)
+ return nil if proj.blank?
+
+ cf = ProjectCustomField.find_by_name("Slack URL")
+
+ return [
+ (proj.custom_value_for(cf).value rescue nil),
+ (url_for_project proj.parent),
+ Setting.plugin_redmine_slack['slack_url'],
+ ].find{|v| v.present?}
+ end
+
+ def channel_for_project(proj)
+ return nil if proj.blank?
+
+ cf = ProjectCustomField.find_by_name("Slack Channel")
+
+ val = [
+ (proj.custom_value_for(cf).value rescue nil),
+ (channel_for_project proj.parent),
+ Setting.plugin_redmine_slack['channel'],
+ ].find{|v| v.present?}
+
+ # Channel name '-' is reserved for NOT notifying
+ return nil if val.to_s == '-'
+ val
+ end
+
+ def detail_to_field(detail)
+ if detail.property == "cf"
+ key = CustomField.find(detail.prop_key).name rescue nil
+ title = key
+ elsif detail.property == "attachment"
+ key = "attachment"
+ title = I18n.t :label_attachment
+ else
+ key = detail.prop_key.to_s.sub("_id", "")
+ if key == "parent"
+ title = I18n.t "field_#{key}_issue"
+ else
+ title = I18n.t "field_#{key}"
+ end
+ end
+
+ short = true
+ value = escape detail.value.to_s
+ old_value = "nil"
+
+ case key
+ when "title", "subject", "description"
+ short = false
+ when "tracker"
+ tracker = Tracker.find(detail.value) rescue nil
+ value = escape tracker.to_s
+ when "project"
+ project = Project.find(detail.value) rescue nil
+ value = escape project.to_s
+ when "status"
+ status = IssueStatus.find(detail.value) rescue nil
+ value = escape status.to_s
+ when "priority"
+ priority = IssuePriority.find(detail.value) rescue nil
+ value = escape priority.to_s
+ when "category"
+ category = IssueCategory.find(detail.value) rescue nil
+ value = escape category.to_s
+ when "assigned_to"
+ user = User.find(detail.value) rescue nil
+ value = escape user.login
+
+ olduser = User.find(detail.old_value) rescue nil
+ old_value = escape olduser.to_s
+ when "fixed_version"
+ version = Version.find(detail.value) rescue nil
+ value = escape version.to_s
+
+ oldversion = Version.find(detail.old_value) rescue nil
+ old_value = escape oldversion.to_s
+ when "attachment"
+ attachment = Attachment.find(detail.prop_key) rescue nil
+ value = "<#{object_url attachment}|#{escape attachment.filename}>" if attachment
+ when "parent"
+ issue = Issue.find(detail.value) rescue nil
+ value = "<#{object_url issue}|#{escape issue}>" if issue
+ end
+
+ value = "-" if value.empty?
+
+ result = { :title => title, :value => value }
+ result[:short] = true if short
+ result[:old_value] = old_value
+ result
+ end
+
+ def mentions text
+ names = extract_usernames text
+ names.present? ? "\nTo: " + names.join(', ') : nil
+ end
+
+ def extract_usernames text = ''
+ if text.nil?
+ text = ''
+ end
+
+ # slack usernames may only contain lowercase letters, numbers,
+ # dashes and underscores and must start with a letter or number.
+ text.scan(/@[a-z0-9][a-z0-9_\-]*/).uniq
+ end
+end
From 0168c6d3041344510008a5680ab1cc6c054749c7 Mon Sep 17 00:00:00 2001
From: eastuni
Date: Tue, 7 Aug 2018 18:22:56 +0900
Subject: [PATCH 6/6] test
---
lib/redmine_slack/listener.rb | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lib/redmine_slack/listener.rb b/lib/redmine_slack/listener.rb
index 5f690e2..410ce00 100644
--- a/lib/redmine_slack/listener.rb
+++ b/lib/redmine_slack/listener.rb
@@ -19,11 +19,12 @@ def controller_issues_new_after_save(context={})
:title => I18n.t("field_priority"),
:value => escape(issue.priority.to_s),
:short => true
- }, {
+ }]
+ attachment[:fields] << {
:title => I18n.t("field_assigned_to"),
- :value => escape("@"+issue.assigned_to.login) if issue.assigned_to,
+ :value => escape("@"+issue.assigned_to.login),
:short => true
- }]
+ } if issue.assigned_to
attachment[:fields] << {
:title => I18n.t("field_watcher"),