22 Commits
0.3 ... 0.6.1

Author SHA1 Message Date
Egon Zemmer
8551a96ec8 Update README.md 2017-03-02 14:58:49 +01:00
Egon Zemmer
31b215ad01 Add placeholder to Rocket.Chat URL. 2017-03-02 14:53:46 +01:00
Egon Zemmer
cb271d3522 Initial import. Bump to version 0.6.1. 2017-03-02 14:24:37 +01:00
Igor Antonov
6755e9cb94 Fix more 2017-02-27 19:10:16 +03:00
Igor Antonov
61f4367b6f Fix 500 error on no channel specified 2017-02-27 18:52:40 +03:00
Igor Antonov
a414136510 Try fixing channel field 2017-02-27 18:48:25 +03:00
Igor Antonov
bdd9c9442e Readme link changes 2017-02-27 18:48:25 +03:00
Igor Antonov
cc6a036213 Allow project-level configuration of posting private issues and notes 2017-02-27 18:48:25 +03:00
Igor Antonov
bd88f92a66 Configure posting private issues and notes 2017-02-27 18:48:25 +03:00
Ron
24f9e716b9 ATTENTION: circumvent ssl cert... 2017-02-27 18:48:25 +03:00
Thanos Kyritsis
4881062c3d find proper value in case of version custom fields (refs #22) 2017-02-23 16:35:27 +02:00
Thanos Kyritsis
99fde7ebe0 Merge pull request #21 from tobenschmidt/master
Call split in channels_for_project only once
2017-02-21 12:41:14 +02:00
Toben Schmidt
bba71e1d06 Call split in channels_for_project only once 2017-02-15 20:08:37 +01:00
Thanos Kyritsis
e10b351b88 bump to version 0.6 2017-02-07 11:43:19 +02:00
Thanos Kyritsis
24268e2297 Prevent internal server error if empty custom field for channel (refs #17) 2017-01-26 13:47:21 +02:00
Thanos Kyritsis
5171908a19 use Rails.logger.warn to fix errors when connecting to Mattermost (sync with sciyoshi/redmine-slack commit b002b62553affceaf13e2ad12e108ffa76d76979) 2016-11-08 14:43:23 +02:00
Thanos Kyritsis
bc9f5df3fc Prevent internal server error if no description passed to API (sync with sciyoshi/redmine-slack commit 5e01d86a12448554ee7015a1ee9df3638bbd7610) 2016-11-08 14:42:21 +02:00
Thanos Kyritsis
c092d8bfcc Send to multiple rooms at a time (sync with sciyoshi/redmine-slack commit 7575feca8716acf52c669af7725db6054e897ed3) 2016-11-08 14:40:18 +02:00
Thanos Kyritsis
2a3e52f80f prevent private notes from being sent to mattermost (refs #14) 2016-10-18 17:56:52 +03:00
Thanos Kyritsis
f89232d657 bump to version 0.4, ensure redmine 3.3.x compatibility (refs #12) 2016-09-28 17:28:23 +03:00
Thanos Kyritsis
f467383922 Merge pull request #13 from jnbt/master
Improve SSL connection negotiation
2016-09-28 17:17:36 +03:00
Erwan Arzur
dea106ccde Fix Slack SSL connection (#83)
Make HTTPClient auto-negotiate SSL supported ciphers - forget about :SSLV23 which are refused by most servers now.
- log exceptions that could occur

* remove comment
2016-09-15 12:29:52 +02:00
8 changed files with 454 additions and 367 deletions

View File

@@ -1,3 +1,3 @@
source 'https://rubygems.org'
gem "httpclient"
gem 'httpclient'

View File

@@ -1,20 +1,20 @@
# Mattermost chat plugin for Redmine
# Rocket.Chat plugin for Redmine
This plugin posts updates to issues in your Redmine installation to a Mattermost
This plugin posts updates to issues in your Redmine installation to a Rocket.Chat
channel.
Redmine Supported versions: 2.0.x - 3.2.x.
Redmine Supported versions: 2.0.x - 3.3.x.
## Screenshot
![screenshot](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/screenshot.png)
![screenshot](https://raw.githubusercontent.com/phlegx/redmine_rocketchat/assets/screenshot.png)
## Installation
From your Redmine plugins directory, clone this repository as `redmine_mattermost` (note
From your Redmine plugins directory, clone this repository as `redmine_rocketchat` (note
the underscore!):
git clone https://github.com/altsol/redmine_mattermost.git redmine_mattermost
git clone https://github.com/phlegx/redmine_rocketchat.git redmine_rocketchat
You will also need the `httpclient` dependency, which can be installed by running
@@ -23,42 +23,46 @@ You will also need the `httpclient` dependency, which can be installed by runnin
from the plugin directory.
Restart Redmine, and you should see the plugin show up in the Plugins page.
Under the configuration options, set the Mattermost API URL to the URL for an
Incoming WebHook integration in your Mattermost account (see also the next two sections).
Under the configuration options, set the Rocket.Chat API URL to the URL for an
Incoming WebHook integration in your Rocket.Chat account and also set the Rocket.Chat
Channel to the channel's handle (be careful, this is not the channel's display name
visible to users, you can find each channel's handle by navigating inside the channel
and clicking the down-arrow and selecting view info). See also the next two sections
for advanced and custom routing options.
## Customized Routing
You can also route messages to different channels on a per-project basis. To
do this, create a project custom field (Administration > Custom fields > Project)
named `Mattermost Channel`. If no custom channel is defined for a project, the parent
named `Rocket.Chat Channel`. If no custom channel is defined for a project, the parent
project will be checked (or the default will be used). To prevent all notifications
from being sent for a project, set the custom channel to `-`.
For more information, see http://www.redmine.org/projects/redmine/wiki/Plugins (see also next section for an easy configuration demonstration).
For more information, see http://www.redmine.org/projects/redmine/wiki/Plugins (see also next section for an easy configuration demonstration).
## Screenshot Guided Configuration
Step 1: Create an Incoming Webhook in Mattermost (Account Settings > Integrations > Incoming Webhooks).
Step 1: Create an Incoming Webhook in Rocket.Chat (Administration > Integrations > Incoming WebHook).
![step1](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step1.png)
![step1](https://raw.githubusercontent.com/phlegx/redmine_rocketchat/assets/step1.png)
Step 2: Install this Redmine plugin for Mattermost.
Step 2: Install this Redmine plugin for Rocket.Chat.
![step2](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step2.png)
![step2](https://raw.githubusercontent.com/phlegx/redmine_rocketchat/assets/step2.png)
Step 3: Configure this Redmine plugin for Mattermost. For per-project customized routing, leave the `Mattermost Channel` field empty and follow the next steps, otherwise all Redmine projects will post to the same Mattermost channel. Be careful when filling the channel field, you need to input the channel's handle, not the display name visible to users. You can find each channel's handle by going inside the channel and click the down-arrow and selecting view info.
Step 3: Configure this Redmine plugin for Rocket.Chat. For per-project customized routing, leave the `Rocket.Chat Channel` field empty and follow the next steps, otherwise all Redmine projects will post to the same Rocket.Chat channel. Be careful when filling the channel field, you need to input the channel's handle, not the display name visible to users. You can find each channel's handle by going inside the channel and click the down-arrow and selecting view info.
![step3](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step3.png)
![step3](https://raw.githubusercontent.com/phlegx/redmine_rocketchat/assets/step3.png)
Step 4: For per-project customized routing, first create the project custom field (Administration > Custom fields > Project).
Step 4: For per-project customized routing, first create the project custom field (Administration > Custom fields > New custom field > Projects).
![step4a](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step4a.png)
![step4b](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step4b.png)
![step4a](https://raw.githubusercontent.com/phlegx/redmine_rocketchat/assets/step4a.png)
![step4b](https://raw.githubusercontent.com/phlegx/redmine_rocketchat/assets/step4b.png)
Step 5: For per-project customized routing, configure the Mattermost channel handle inside your Redmine project.
Step 5: For per-project customized routing, configure the Rocket.Chat channel handle inside your Redmine project.
![step5](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step5.png)
![step5](https://raw.githubusercontent.com/phlegx/redmine_rocketchat/assets/step5.png)
## Credits
The source code is forked from https://github.com/sciyoshi/redmine-slack. Special thanks to the original author and contributors for making this awesome hook for Redmine. This fork is just refactored to use Mattermost-namespaced configuration options in order to use both hooks (Mattermost and Slack) in a Redmine installation.
The source code is forked from https://github.com/altsol/redmine_mattermost. Special thanks to the original author and contributors for making this awesome hook for Redmine. This fork is just refactored to use Rocket.Chat-namespaced configuration options in order to use all hooks (Rocket.Chat, Mattermost and Slack) in a Redmine installation.

View File

@@ -1,46 +0,0 @@
<p>
<label for="settings_mattermost_url">Mattermost URL</label>
<input type="text" id="settings_mattermost_url" size=80 value="<%= settings['mattermost_url'] %>" name="settings[mattermost_url]" />
</p>
<p>
Generate an "Incoming WebHook" URL from the <a href="http://docs.mattermost.com/developer/webhooks-incoming.html">Integrations configuration page on Mattermost</a>. This URL can be changed on a per-project basis by creating a <a href="/custom_fields/new?type=ProjectCustomField">project custom field</a> named "Mattermost URL" (without quotes).
</p>
<p>
<label for="settings_channel">Mattermost Channel</label>
<input type="text" id="settings_channel" value="<%= settings['channel'] %>" name="settings[channel]" />
</p>
<p>
The channel can be changed on a per-project basis by creating a
<a href="/custom_fields/new?type=ProjectCustomField">project custom field</a> named "Mattermost Channel" (without quotes).
</p>
<p>
<label for="settings_icon">Mattermost Icon</label>
<input type="text" id="settings_icon" value="<%= settings['icon'] %>" name="settings[icon]" />
</p>
<p>
<label for="settings_username">Mattermost Username</label>
<input type="text" id="settings_username" value="<%= settings['username'] %>" name="settings[username]" />
</p>
<p>
<label for="settings_display_watchers">Display Watchers?</label>
<select id="settings_display_watchers" value="<%= settings['display_watchers'] %>" name="settings[display_watchers]">
<option value="yes">Yes</option>
<option value="no" <%= settings['display_watchers'] != 'yes' ? %q(selected="selected") : '' %>>No</option>
</select>
</p>
<p>
<label for="settings_post_updates">Post Issue Updates?</label>
<input type="checkbox" id="settings_post_updates" value="1" name="settings[post_updates]" <%= settings['post_updates'] == '1' ? 'checked="checked"' : '' %> />
</p>
<p>
<label for="settings_post_wiki_updates">Post Wiki Updates?</label>
<input type="checkbox" id="settings_post_wiki_updates" value="1" name="settings[post_wiki_updates]" <%= settings['post_wiki_updates'] == '1' ? 'checked="checked"' : '' %> />
</p>

View File

@@ -0,0 +1,71 @@
<p>
<label for="settings_rocketchat_url">Rocket.Chat URL</label>
<input type="text" id="settings_rocketchat_url" size=80 value="<%= settings['rocketchat_url'] %>" name="settings[rocketchat_url]" placeholder="https://rocket.chat/hooks/my_rocket_chat_token" />
</p>
<p>
Generate an "Incoming WebHook" URL from the <a href="https://rocket.chat/docs/administrator-guides/integrations/">Integrations configuration page on Rocket.Chat</a>.
This URL can be changed on a per-project basis by creating a <a href="/custom_fields/new?type=ProjectCustomField&custom_field[name]=Rocket.Chat Channel">project custom field</a>
named "Rocket.Chat URL" (without quotes).
</p>
<p>
<label for="settings_channel">Rocket.Chat Channel</label>
<input type="text" id="settings_channel" value="<%= settings['channel'] %>" name="settings[channel]" placeholder="redmine" />
</p>
<p>
The channel can be changed on a per-project basis by creating a
<a href="/custom_fields/new?type=ProjectCustomField&custom_field[name]=Rocket.Chat Channel">project custom field</a>
named "Rocket.Chat Channel" (without quotes).
</p>
<p>
<label for="settings_icon">Rocket.Chat Icon</label>
<input type="text" id="settings_icon" value="<%= settings['icon'] %>" name="settings[icon]" />
</p>
<p>
<label for="settings_username">Rocket.Chat Username</label>
<input type="text" id="settings_username" value="<%= settings['username'] %>" name="settings[username]" placeholder="redmine.bot" />
</p>
<p>
<label for="settings_auto_mentions">Display watchers?</label>
<input type="checkbox" id="settings_display_watchers" value="1" name="settings[display_watchers]" <%= settings['display_watchers'] == '1' ? 'checked="checked"' : '' %> />
</p>
<p>
<label for="settings_auto_mentions">Convert names to mentions?</label>
<input type="checkbox" id="settings_auto_mentions" value="1" name="settings[auto_mentions]" <%= settings['auto_mentions'] == '1' ? 'checked="checked"' : '' %> />
</p>
<p>
<label for="settings_post_updates">Post issue updates?</label>
<input type="checkbox" id="settings_post_updates" value="1" name="settings[post_updates]" <%= settings['post_updates'] == '1' ? 'checked="checked"' : '' %> />
</p>
<p>
<label for="settings_new_include_description">Description in new issue?</label>
<input type="checkbox" id="settings_new_include_description" value="1" name="settings[new_include_description]" <%= settings['new_include_description'] == '1' ? 'checked="checked"' : '' %> />
</p>
<p>
<label for="settings_updated_include_description">Description in update issue?</label>
<input type="checkbox" id="settings_updated_include_description" value="1" name="settings[updated_include_description]" <%= settings['updated_include_description'] == '1' ? 'checked="checked"' : '' %> />
</p>
<p>
<label for="settings_post_updates">Post updates for private issues?</label>
<input type="checkbox" id="settings_post_private_issues" value="1" name="settings[post_private_issues]" <%= settings['post_private_issues'] == '1' ? 'checked="checked"' : '' %> />
</p>
<p>
<label for="settings_post_updates">Post private notes?</label>
<input type="checkbox" id="settings_post_private_notes" value="1" name="settings[post_private_notes]" <%= settings['post_private_notes'] == '1' ? 'checked="checked"' : '' %> />
</p>
<p>
<label for="settings_post_wiki_updates">Post Wiki updates?</label>
<input type="checkbox" id="settings_post_wiki_updates" value="1" name="settings[post_wiki_updates]" <%= settings['post_wiki_updates'] == '1' ? 'checked="checked"' : '' %> />
</p>

51
init.rb
View File

@@ -1,31 +1,38 @@
require 'redmine'
require_dependency 'redmine_mattermost/listener'
require_dependency 'redmine_rocketchat/listener'
Redmine::Plugin.register :redmine_mattermost do
name 'Redmine Mattermost'
author 'AltSol'
url 'https://github.com/altsol/redmine_mattermost'
author_url 'http://altsol.gr'
description 'Mattermost chat integration'
version '0.3'
Redmine::Plugin.register :redmine_rocketchat do
name 'Redmine Rocket.Chat'
author 'Egon Zemmer'
url 'https://github.com/phlegx/redmine_rocketchat'
author_url 'https://phlegx.com'
description 'Rocket.Chat integration'
version '0.6.1'
requires_redmine :version_or_higher => '2.0.0'
requires_redmine :version_or_higher => '2.0.0'
settings \
:default => {
'callback_url' => 'http://example.com/callback/',
'channel' => nil,
'icon' => 'https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/icon.png',
'username' => 'redmine',
'display_watchers' => 'no'
},
:partial => 'settings/mattermost_settings'
settings \
:default => {
'callback_url' => 'https://rocket.chat/hooks/my_rocket_chat_token',
'channel' => 'redmine',
'icon' => 'https://raw.githubusercontent.com/phlegx/redmine_rocketchat/assets/icon.png',
'username' => 'redmine.bot',
'display_watchers' => '0',
'auto_mentions' => '1',
'post_updates' => '1',
'new_include_description' => '1',
'updated_include_description' => '1',
'post_private_issues' => '1',
'post_private_notes' => '1',
'post_wiki_updates' => '0'
},
:partial => 'settings/rocketchat_settings'
end
ActionDispatch::Callbacks.to_prepare do
require_dependency 'issue'
unless Issue.included_modules.include? RedmineMattermost::IssuePatch
Issue.send(:include, RedmineMattermost::IssuePatch)
end
require_dependency 'issue'
unless Issue.included_modules.include? RedmineRocketchat::IssuePatch
Issue.send(:include, RedmineRocketchat::IssuePatch)
end
end

View File

@@ -1,269 +0,0 @@
require 'httpclient'
class MattermostListener < Redmine::Hook::Listener
def redmine_mattermost_issues_new_after_save(context={})
issue = context[:issue]
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 = {}
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
}, {
:title => I18n.t("field_assigned_to"),
:value => escape(issue.assigned_to.to_s),
:short => true
}]
attachment[:fields] << {
:title => I18n.t("field_watcher"),
:value => escape(issue.watcher_users.join(', ')),
:short => true
} if Setting.plugin_redmine_mattermost[:display_watchers] == 'yes'
speak msg, channel, attachment, url
end
def redmine_mattermost_issues_edit_after_save(context={})
issue = context[:issue]
journal = context[:journal]
channel = channel_for_project issue.project
url = url_for_project issue.project
return unless channel and url and Setting.plugin_redmine_mattermost[:post_updates] == '1'
return if issue.is_private?
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 }
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
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
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 }
speak msg, channel, attachment, url
end
def controller_wiki_edit_after_save(context = { })
return unless Setting.plugin_redmine_mattermost[: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_mattermost[:mattermost_url] if not url
username = Setting.plugin_redmine_mattermost[:username]
icon = Setting.plugin_redmine_mattermost[: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 = "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("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
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("Mattermost URL")
return [
(proj.custom_value_for(cf).value rescue nil),
(url_for_project proj.parent),
Setting.plugin_redmine_mattermost[:mattermost_url],
].find{|v| v.present?}
end
def channel_for_project(proj)
return nil if proj.blank?
cf = ProjectCustomField.find_by_name("Mattermost Channel")
val = [
(proj.custom_value_for(cf).value rescue nil),
(channel_for_project proj.parent),
Setting.plugin_redmine_mattermost[: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", "")
title = I18n.t "field_#{key}"
end
short = true
value = escape detail.value.to_s
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.to_s
when "fixed_version"
version = Version.find(detail.value) rescue nil
value = escape version.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
end
def mentions text
return nil if text.nil?
names = extract_usernames text
names.present? ? "\nTo: " + names.join(', ') : nil
end
def extract_usernames text = ''
# mattermost 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

View File

@@ -1,4 +1,4 @@
module RedmineMattermost
module RedmineRocketchat
module IssuePatch
def self.included(base) # :nodoc:
base.extend(ClassMethods)
@@ -10,24 +10,24 @@ module RedmineMattermost
after_save :save_from_issue
end
end
module ClassMethods
end
module InstanceMethods
def create_from_issue
@create_already_fired = true
Redmine::Hook.call_hook(:redmine_mattermost_issues_new_after_save, { :issue => self})
Redmine::Hook.call_hook(:redmine_rocketchat_issues_new_after_save, { :issue => self})
return true
end
def save_from_issue
if not @create_already_fired
Redmine::Hook.call_hook(:redmine_mattermost_issues_edit_after_save, { :issue => self, :journal => self.current_journal}) unless self.current_journal.nil?
unless @create_already_fired
Redmine::Hook.call_hook(:redmine_rocketchat_issues_edit_after_save, {:issue => self, :journal => self.current_journal}) unless self.current_journal.nil?
end
return true
end
end
end
end
end

View File

@@ -0,0 +1,320 @@
require 'httpclient'
class RocketchatListener < Redmine::Hook::Listener
def redmine_rocketchat_issues_new_after_save(context={})
issue = context[:issue]
channels = channels_for_project issue.project
url = url_for_project issue.project
post_private_issues = post_private_issues_for_project(issue.project)
return unless channels.present? and url
return if issue.is_private? and post_private_issues != '1'
msg = "[#{escape issue.project}] #{escape issue.author} created <#{object_url issue}|#{escape issue}>#{mentions issue.description if Setting.plugin_redmine_rocketchat[:auto_mentions] == '1'}"
attachment = {}
attachment[:text] = escape issue.description if issue.description and Setting.plugin_redmine_rocketchat[:new_include_description] == '1'
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
}, {
:title => I18n.t(:field_assigned_to),
:value => escape(issue.assigned_to.to_s),
:short => true
}]
attachment[:fields] << {
:title => I18n.t(:field_watcher),
:value => escape(issue.watcher_users.join(', ')),
:short => true
} if Setting.plugin_redmine_rocketchat[:display_watchers] == '1'
speak msg, channels, attachment, url
end
def redmine_rocketchat_issues_edit_after_save(context={})
issue = context[:issue]
journal = context[:journal]
channels = channels_for_project issue.project
url = url_for_project issue.project
post_private_issues = post_private_issues_for_project(issue.project)
post_private_notes = post_private_notes_for_project(issue.project)
return unless channels.present? and url and Setting.plugin_redmine_rocketchat[:post_updates] == '1'
return if issue.is_private? and post_private_issues != '1'
return if journal.private_notes? and post_private_notes != '1'
msg = "[#{escape issue.project}] #{escape journal.user.to_s} updated <#{object_url issue}|#{escape issue}>#{mentions journal.notes if Setting.plugin_redmine_rocketchat[:auto_mentions] == '1'}"
attachment = {}
attachment[:text] = escape journal.notes if journal.notes and Setting.plugin_redmine_rocketchat[:updated_include_description] == '1'
attachment[:fields] = journal.details.map { |d| detail_to_field d }
speak msg, channels, 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]
channels = channels_for_project issue.project
url = url_for_project issue.project
post_private_issues = post_private_issues_for_project(issue.project)
return unless channels.present? and url and issue.save
return if issue.is_private? and post_private_issues != '1'
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 }
speak msg, channels, attachment, url
end
def controller_wiki_edit_after_save(context = { })
return unless Setting.plugin_redmine_rocketchat[: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}*"
channels = channels_for_project project
url = url_for_project project
return unless channels.present? and url
attachment = nil
unless page.content.comments.empty?
attachment = {}
attachment[:text] = "#{escape page.content.comments}"
end
speak comment, channels, attachment, url
end
def speak(msg, channels, attachment=nil, url=nil)
return if channels.blank?
url = Setting.plugin_redmine_rocketchat[:rocketchat_url] unless url
username = Setting.plugin_redmine_rocketchat[:username]
icon = Setting.plugin_redmine_rocketchat[:icon]
params = {
:text => msg,
:link_names => 1,
}
params[:username] = username if username
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
channels.each do |channel|
params[:channel] = channel
begin
client = HTTPClient.new
client.ssl_config.cert_store.set_default_paths
client.ssl_config.ssl_version = :auto
client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
client.post_async url, {:payload => params.to_json}
rescue Exception => e
Rails.logger.warn("cannot connect to #{url}")
Rails.logger.warn(e)
end
end
end
private
def escape(msg)
msg.to_s.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
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('Rocket.Chat URL')
[
(proj.custom_value_for(cf).value rescue nil),
(url_for_project proj.parent),
Setting.plugin_redmine_rocketchat[:rocketchat_url],
].flatten.find(&:present?)
end
def post_private_issues_for_project(proj)
return nil if proj.blank?
cf = ProjectCustomField.find_by_name('Rocket.Chat Post private issues')
[
(proj.custom_value_for(cf).value rescue nil),
(post_private_issues_for_project proj.parent),
Setting.plugin_redmine_rocketchat[:post_private_issues],
].flatten.find(&:present?)
end
def post_private_notes_for_project(proj)
return nil if proj.blank?
cf = ProjectCustomField.find_by_name('Rocket.Chat Post private notes')
[
(proj.custom_value_for(cf).value rescue nil),
(post_private_notes_for_project proj.parent),
Setting.plugin_redmine_rocketchat[:post_private_notes],
].flatten.find(&:present?)
end
def channels_for_project(proj)
return nil if proj.blank?
cf = ProjectCustomField.find_by_name('Rocket.Chat Channel')
val = [
(proj.custom_value_for(cf).value rescue nil),
(channels_for_project proj.parent),
Setting.plugin_redmine_rocketchat[:channel],
].flatten.find(&:present?)
# Channel name '-' or empty '' is reserved for NOT notifying
return [] if val.nil? or val.to_s == ''
return [] if val.to_s == '-'
return val.split(",") if val.is_a? String
val
end
def detail_to_field(detail)
field_format = nil
if detail.property == "cf"
key = CustomField.find(detail.prop_key).name rescue nil
title = key
field_format = CustomField.find(detail.prop_key).field_format rescue nil
elsif detail.property == 'attachment'
key = 'attachment'
title = I18n.t :label_attachment
else
key = detail.prop_key.to_s.sub('_id', '')
title = I18n.t "field_#{key}"
end
short = true
value = escape detail.value.to_s
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.to_s
when 'fixed_version'
version = Version.find(detail.value) rescue nil
value = escape version.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
case field_format
when 'version'
version = Version.find(detail.value) rescue nil
value = escape version.to_s
end
value = '-' if value.empty?
result = { :title => title, :value => value }
result[:short] = true if short
result
end
def mentions text
return nil if text.nil?
names = extract_usernames text
names.present? ? '\nTo: ' + names.join(', ') : nil
end
def extract_usernames text = ''
if text.nil?
text = ''
end
# rocketchat usernames may only contain lowercase letters, numbers,
# dashes, dots and underscores and must start with a letter or number.
text.scan(/@[a-z0-9][a-z0-9_\-.]*/).uniq
end
end