redmine-mattermost 0.3 → 0.4.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +15 -0
- data/LICENSE +1 -0
- data/README.md +30 -17
- data/Rakefile +9 -0
- data/app/views/settings/_mattermost_settings.html.erb +60 -20
- data/lib/redmine-mattermost.rb +9 -1
- data/lib/redmine_mattermost/bridge.rb +63 -0
- data/lib/redmine_mattermost/client.rb +28 -0
- data/lib/redmine_mattermost/extractors.rb +88 -0
- data/lib/redmine_mattermost/extractors/applied_changeset.rb +55 -0
- data/lib/redmine_mattermost/extractors/changed_wiki_page.rb +39 -0
- data/lib/redmine_mattermost/extractors/new_issue.rb +44 -0
- data/lib/redmine_mattermost/extractors/updated_issue.rb +42 -0
- data/lib/redmine_mattermost/infos.rb +1 -2
- data/lib/redmine_mattermost/issue_patch.rb +14 -14
- data/lib/redmine_mattermost/listener.rb +38 -268
- data/lib/redmine_mattermost/message_builder.rb +61 -0
- data/lib/redmine_mattermost/redmine_plugin.rb +3 -4
- data/lib/redmine_mattermost/utils.rb +3 -0
- data/lib/redmine_mattermost/utils/journal_mapper.rb +75 -0
- data/lib/redmine_mattermost/utils/text.rb +37 -0
- data/lib/redmine_mattermost/utils/urls.rb +20 -0
- data/lib/redmine_mattermost/version.rb +1 -1
- data/redmine-mattermost.gemspec +4 -1
- data/spec/client_spec.rb +29 -0
- data/spec/extractors/applied_changeset_spec.rb +82 -0
- data/spec/extractors/changed_wiki_page_spec.rb +63 -0
- data/spec/extractors/new_issue_spec.rb +209 -0
- data/spec/extractors/updated_issue_spec.rb +169 -0
- data/spec/message_builder_spec.rb +71 -0
- data/spec/redmine_mattermost_spec.rb +9 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/mock_support.rb +113 -0
- data/spec/utils/text_spec.rb +142 -0
- data/spec/utils/text_spec_alt.rb +142 -0
- metadata +87 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccd8592a22be9dae331562b803f93b1515ed9022
|
4
|
+
data.tar.gz: 0243de9f725c2751744d4106dcc7f4446a30b44b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec2dcbbbde62fa4e40070f7ac1691487a029463bdcae62bc3c050e904ee752b8f12e162d6f2f05808685f0f86808a36298853ede6e684e10b33552f8488da0e9
|
7
|
+
data.tar.gz: 68e1c9640f500e3fc491d6c90deeec7a196e9f46aa18cbfb5a8fc4ee2a46127b18f9a4955f448cadd496ddd9c4eae14b5cd69590df90a3480481e85c844a19e1
|
data/.travis.yml
ADDED
data/LICENSE
CHANGED
@@ -2,6 +2,7 @@ Released under the MIT license.
|
|
2
2
|
|
3
3
|
Project redmine-slack is Copyright (c) 2014-2016 by Samuel Cormier-Iijima (https://github.com/sciyoshi/redmine-slack).
|
4
4
|
Fork project redmine_mattermost is Copyright (c) 2015-2016 by AltSol (https://github.com/altsol/redmine_mattermost).
|
5
|
+
Fork project redmine-mattermost is Copyright (c) 2016 by Jonas Thiel (https://github.com/neopoly/redmine-mattermost).
|
5
6
|
|
6
7
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
7
8
|
|
data/README.md
CHANGED
@@ -1,23 +1,38 @@
|
|
1
|
-
|
1
|
+
[github]: https://github.com/neopoly/redmine-mattermost
|
2
|
+
[doc]: http://rubydoc.info/github/neopoly/redmine-mattermost/master/file/README.md
|
3
|
+
[gem]: https://rubygems.org/gems/redmine-mattermost
|
4
|
+
[travis]: https://travis-ci.org/neopoly/redmine-mattermost
|
5
|
+
[codeclimate]: https://codeclimate.com/github/neopoly/redmine-mattermost
|
6
|
+
|
2
7
|
|
3
8
|
# Mattermost chat plugin for Redmine
|
4
9
|
|
5
|
-
This plugin posts updates to issues
|
6
|
-
|
10
|
+
This plugin posts updates to issues and wiki pages from your Redmine installation to Mattermost.
|
11
|
+
|
12
|
+
[![Travis](https://img.shields.io/travis/neopoly/redmine-mattermost.svg?branch=master)][travis]
|
13
|
+
[![Gem Version](https://img.shields.io/gem/v/redmine-mattermost.svg)][gem]
|
14
|
+
[![Code Climate](https://img.shields.io/codeclimate/github/neopoly/redmine-mattermost.svg)][codeclimate]
|
15
|
+
[![Test Coverage](https://codeclimate.com/github/neopoly/redmine-mattermost/badges/coverage.svg)][codeclimate]
|
16
|
+
|
17
|
+
[Gem][gem] |
|
18
|
+
[Source][github] |
|
19
|
+
[Documentation][doc]
|
7
20
|
|
8
|
-
Redmine Supported versions: 2.0.
|
21
|
+
Redmine Supported versions: >= 2.0.0
|
22
|
+
|
23
|
+
> **NOTE:** This plugin started as a fork of https://github.com/altsol/redmine_mattermost
|
9
24
|
|
10
25
|
## Screenshot
|
11
26
|
|
12
|
-
![screenshot](https://raw.githubusercontent.com/
|
27
|
+
![screenshot](https://raw.githubusercontent.com/neopoly/redmine-mattermost/assets/screenshot.png)
|
13
28
|
|
14
29
|
## Installation
|
15
30
|
|
16
31
|
1. Ensure you have a `Gemfile.local` file in your Redmine directory. You may just create an empty file.
|
17
32
|
2. Register `redmine-mattermost` as a dependency as you would do for any other Ruby project:
|
18
33
|
|
19
|
-
gem "redmine-mattermost", "~> 0.
|
20
|
-
|
34
|
+
gem "redmine-mattermost", "~> 0.4"
|
35
|
+
|
21
36
|
3. Run bundler:
|
22
37
|
|
23
38
|
bundle install
|
@@ -26,35 +41,33 @@ Restart Redmine, and you should see the plugin show up in the Plugins page.
|
|
26
41
|
Under the configuration options, set the Mattermost API URL to the URL for an
|
27
42
|
Incoming WebHook integration in your Mattermost account (see also the next two sections).
|
28
43
|
|
29
|
-
## Customized
|
44
|
+
## Customized channels
|
30
45
|
|
31
|
-
You can also
|
46
|
+
You can also post messages to different channels on a per-project basis. To
|
32
47
|
do this, create a project custom field (Administration > Custom fields > Project)
|
33
48
|
named `Mattermost Channel`. If no custom channel is defined for a project, the parent
|
34
49
|
project will be checked (or the default will be used). To prevent all notifications
|
35
50
|
from being sent for a project, set the custom channel to `-`.
|
36
51
|
|
37
|
-
For more information, see http://www.redmine.org/projects/redmine/wiki/Plugins (see also next section for an easy configuration demonstration).
|
38
|
-
|
39
52
|
## Screenshot Guided Configuration
|
40
53
|
|
41
54
|
Step 1: Create an Incoming Webhook in Mattermost (Account Settings > Integrations > Incoming Webhooks).
|
42
55
|
|
43
|
-
![step1](https://raw.githubusercontent.com/
|
56
|
+
![step1](https://raw.githubusercontent.com/neopoly/redmine-mattermost/assets/step1.png)
|
44
57
|
|
45
58
|
Step 2: 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.
|
46
59
|
|
47
|
-
![step3](https://raw.githubusercontent.com/
|
60
|
+
![step3](https://raw.githubusercontent.com/neopoly/redmine-mattermost/assets/step3.png)
|
48
61
|
|
49
62
|
Step 3: For per-project customized routing, first create the project custom field (Administration > Custom fields > Project).
|
50
63
|
|
51
|
-
![step4a](https://raw.githubusercontent.com/
|
52
|
-
![step4b](https://raw.githubusercontent.com/
|
64
|
+
![step4a](https://raw.githubusercontent.com/neopoly/redmine-mattermost/assets/step4a.png)
|
65
|
+
![step4b](https://raw.githubusercontent.com/neopoly/redmine-mattermost/assets/step4b.png)
|
53
66
|
|
54
67
|
Step 4: For per-project customized routing, configure the Mattermost channel handle inside your Redmine project.
|
55
68
|
|
56
|
-
![step5](https://raw.githubusercontent.com/
|
69
|
+
![step5](https://raw.githubusercontent.com/neopoly/redmine-mattermost/assets/step5.png)
|
57
70
|
|
58
71
|
## Credits
|
59
72
|
|
60
|
-
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.
|
73
|
+
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.
|
data/Rakefile
CHANGED
@@ -1,46 +1,86 @@
|
|
1
|
+
<div class="box">
|
2
|
+
<i class="icon icon-help"></i> You can find detailed setup instructions in the
|
3
|
+
<a href="https://github.com/neopoly/redmine-mattermost/blob/master/README.md" target="_blank">README</a>.
|
4
|
+
</div>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<label for="settings_mattermost_url">Mattermost URL</label>
|
8
|
+
<input type="text" id="settings_mattermost_url" size=80 value="<%= settings['mattermost_url'] %>" name="settings[mattermost_url]" />
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
Generate an "Incoming WebHook" URL from the
|
13
|
+
<a href="http://docs.mattermost.com/developer/webhooks-incoming.html">Integrations configuration page on Mattermost</a>.
|
14
|
+
<br>
|
15
|
+
This URL can be overwritten on a per-project basis by creating a
|
16
|
+
<a href="/custom_fields/new?type=ProjectCustomField">project custom field</a> named "<code>Mattermost URL</code>".
|
17
|
+
</p>
|
18
|
+
|
19
|
+
<p>
|
20
|
+
<label for="settings_channel">Mattermost Channel</label>
|
21
|
+
<input type="text" id="settings_channel" value="<%= settings['channel'] %>" name="settings[channel]" size=80/>
|
22
|
+
</p>
|
23
|
+
|
24
|
+
<p>
|
25
|
+
The channel can be overwritten on a per-project basis by creating a
|
26
|
+
<a href="/custom_fields/new?type=ProjectCustomField">project custom field</a> named "<code>Mattermost Channel</code>".
|
27
|
+
Mattermost will use the configured channel for the used WebHook in case no channel is given.
|
28
|
+
<br>
|
29
|
+
Using "<code>-</code>" as the channel will prevent any messages to Mattermost.
|
30
|
+
</p>
|
31
|
+
|
32
|
+
<p>
|
33
|
+
<label for="settings_icon">Mattermost Icon</label>
|
34
|
+
<input type="text" id="settings_icon" value="<%= settings['icon'] %>" name="settings[icon]" size=80/>
|
35
|
+
</p>
|
36
|
+
|
1
37
|
<p>
|
2
|
-
|
3
|
-
|
38
|
+
The icon can be overwritten on a per-project basis by creating a
|
39
|
+
<a href="/custom_fields/new?type=ProjectCustomField">project custom field</a> named "<code>Mattermost Icon</code>".
|
4
40
|
</p>
|
5
41
|
|
6
42
|
<p>
|
7
|
-
|
43
|
+
<label for="settings_username">Mattermost Username</label>
|
44
|
+
<input type="text" id="settings_username" value="<%= settings['username'] %>" name="settings[username]" size=80/>
|
8
45
|
</p>
|
9
46
|
|
10
47
|
<p>
|
11
|
-
|
12
|
-
|
48
|
+
The username can be overwritten on a per-project basis by creating a
|
49
|
+
<a href="/custom_fields/new?type=ProjectCustomField">project custom field</a> named "<code>Mattermost Username</code>".
|
13
50
|
</p>
|
14
51
|
|
15
52
|
<p>
|
16
|
-
|
17
|
-
|
53
|
+
<label for="settings_display_watchers">Display Watchers?</label>
|
54
|
+
<%
|
55
|
+
display_watchers_available = Setting.text_formatting != "textile"
|
56
|
+
display_watchers = settings['display_watchers']
|
57
|
+
%>
|
58
|
+
<input type="checkbox" id="settings_display_watchers" value="1" name="settings[display_watchers]" <%= display_watchers ? 'checked="checked"' : '' %> <%= display_watchers_available ? '' : 'disabled="disabled"' %>/>
|
18
59
|
</p>
|
19
60
|
|
20
61
|
<p>
|
21
|
-
|
22
|
-
|
62
|
+
Enabling this option will scan the content for mentions (e.g. <code>@joe</code>) and list them
|
63
|
+
in the message title.
|
64
|
+
<br>
|
65
|
+
<em>This option is not available if you're using "Textile" as the main "Text formatting" engine in Redmine as the <code>@</code>-char has a special usage.</em>
|
23
66
|
</p>
|
24
67
|
|
25
68
|
<p>
|
26
|
-
|
27
|
-
|
69
|
+
<label for="settings_post_updates">Post Issue Updates?</label>
|
70
|
+
<input type="checkbox" id="settings_post_updates" value="1" name="settings[post_updates]" <%= settings['post_updates'] == '1' ? 'checked="checked"' : '' %> />
|
28
71
|
</p>
|
29
72
|
|
30
73
|
<p>
|
31
|
-
|
32
|
-
|
33
|
-
<option value="yes">Yes</option>
|
34
|
-
<option value="no" <%= settings['display_watchers'] != 'yes' ? %q(selected="selected") : '' %>>No</option>
|
35
|
-
</select>
|
74
|
+
Per default only new issue are reported to Mattermost. Enable this option if you want every issue
|
75
|
+
update to be posted to Mattermost.
|
36
76
|
</p>
|
37
77
|
|
38
78
|
<p>
|
39
|
-
|
40
|
-
|
79
|
+
<label for="settings_post_wiki_updates">Post Wiki Changes?</label>
|
80
|
+
<input type="checkbox" id="settings_post_wiki_updates" value="1" name="settings[post_wiki_updates]" <%= settings['post_wiki_updates'] == '1' ? 'checked="checked"' : '' %> />
|
41
81
|
</p>
|
42
82
|
|
43
83
|
<p>
|
44
|
-
|
45
|
-
|
84
|
+
Per default changes to Wiki pages (new pages or updates) are not posted to Mattermost. Enable this option
|
85
|
+
if you want every change to a Wiki page posted to Mattermost.
|
46
86
|
</p>
|
data/lib/redmine-mattermost.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
require "redmine_mattermost/version"
|
2
2
|
require "redmine_mattermost/infos"
|
3
|
-
require "redmine_mattermost/
|
3
|
+
require "redmine_mattermost/client"
|
4
|
+
require "redmine_mattermost/message_builder"
|
5
|
+
require "redmine_mattermost/bridge"
|
6
|
+
require "redmine_mattermost/utils"
|
7
|
+
require "redmine_mattermost/extractors"
|
8
|
+
|
9
|
+
if defined?(Rails)
|
10
|
+
require "redmine_mattermost/engine"
|
11
|
+
end
|
4
12
|
|
5
13
|
# Mattermost chat integration
|
6
14
|
module RedmineMattermost
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RedmineMattermost
|
2
|
+
class Bridge
|
3
|
+
def settings
|
4
|
+
::Setting
|
5
|
+
end
|
6
|
+
|
7
|
+
def custom_field_by_name(name)
|
8
|
+
::ProjectCustomField.find_by_name(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def url_for(target)
|
12
|
+
::Rails.application.routes.url_for(target)
|
13
|
+
end
|
14
|
+
|
15
|
+
def translate(*args)
|
16
|
+
::I18n.t(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_html(formatting, text)
|
20
|
+
Redmine::WikiFormatting.to_html(formatting, text)
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_custom_field(id)
|
24
|
+
::CustomField.find_by_id(id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_tracker(id)
|
28
|
+
::Tracker.find_by_id(id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_project(id)
|
32
|
+
::Project.find_by_id(id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_issue_status(id)
|
36
|
+
::IssueStatus.find_by_id(id)
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_issue_priority(id)
|
40
|
+
::IssuePriority.find_by_id(id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_issue_category(id)
|
44
|
+
::IssueCategory.find_by_id(id)
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_user(id)
|
48
|
+
::User.find(id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_version(id)
|
52
|
+
::Version.find_by_id(id)
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_attachement(id)
|
56
|
+
::Attachment.find_by_id(id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_issue(id)
|
60
|
+
::Issue.find_by_id(id)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module RedmineMattermost
|
5
|
+
class Client
|
6
|
+
SSL_VERSION = "SSLv23".freeze
|
7
|
+
|
8
|
+
attr_reader :uri
|
9
|
+
|
10
|
+
def initialize(url)
|
11
|
+
@uri = URI(url)
|
12
|
+
end
|
13
|
+
|
14
|
+
def speak(message)
|
15
|
+
post_async(payload: message.to_json)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def post_async(params)
|
21
|
+
Thread.new { post(params) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def post(params)
|
25
|
+
Net::HTTP.post_form(uri, params)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module RedmineMattermost
|
2
|
+
module Extractors
|
3
|
+
class Base
|
4
|
+
include Utils::Text
|
5
|
+
include Utils::Urls
|
6
|
+
|
7
|
+
CUSTOM_FIELD_URL = "Mattermost URL"
|
8
|
+
CUSTOM_FIELD_CHANNEL = "Mattermost Channel"
|
9
|
+
CUSTOM_FIELD_ICON = "Mattermost Icon"
|
10
|
+
CUSTOM_FIELD_USERNAME = "Mattermost Username"
|
11
|
+
BLACK_HOLE_CHANNEL = "-"
|
12
|
+
MENTIONS = "\nTo: %{usernames}"
|
13
|
+
|
14
|
+
attr_reader :bridge
|
15
|
+
|
16
|
+
def initialize(bridge)
|
17
|
+
@bridge = bridge
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def determine_channel(project)
|
23
|
+
channel = determine_configurable_field(project, :channel, CUSTOM_FIELD_CHANNEL)
|
24
|
+
return channel if channel != BLACK_HOLE_CHANNEL
|
25
|
+
end
|
26
|
+
|
27
|
+
def determine_url(project)
|
28
|
+
determine_configurable_field(project, :mattermost_url, CUSTOM_FIELD_URL)
|
29
|
+
end
|
30
|
+
|
31
|
+
def determine_icon(project)
|
32
|
+
determine_configurable_field(project, :icon, CUSTOM_FIELD_ICON)
|
33
|
+
end
|
34
|
+
|
35
|
+
def determine_username(project)
|
36
|
+
determine_configurable_field(project, :username, CUSTOM_FIELD_USERNAME)
|
37
|
+
end
|
38
|
+
|
39
|
+
def extract_mentions(text)
|
40
|
+
return unless text
|
41
|
+
return if uses_textile?
|
42
|
+
usernames = extract_usernames(text)
|
43
|
+
return if usernames.empty?
|
44
|
+
MENTIONS % { usernames: usernames.join(", ") }
|
45
|
+
end
|
46
|
+
|
47
|
+
def extract_usernames(text)
|
48
|
+
return [] unless text
|
49
|
+
return [] if uses_textile?
|
50
|
+
text.scan(/@[a-z0-9][a-z0-9_\-]*/).uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
def uses_textile?
|
54
|
+
settings.text_formatting == "textile"
|
55
|
+
end
|
56
|
+
|
57
|
+
def show_watchers?
|
58
|
+
settings.plugin_redmine_mattermost[:display_watchers] == "1"
|
59
|
+
end
|
60
|
+
|
61
|
+
def settings
|
62
|
+
bridge.settings
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def custom_field(name)
|
68
|
+
bridge.custom_field_by_name(name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def determine_configurable_field(project, settings_key, field_key)
|
72
|
+
return if project.nil? || project.blank?
|
73
|
+
cf = custom_field(field_key)
|
74
|
+
|
75
|
+
[
|
76
|
+
(project.custom_value_for(cf).value rescue nil),
|
77
|
+
determine_configurable_field(project.parent, settings_key, field_key),
|
78
|
+
settings.plugin_redmine_mattermost[settings_key],
|
79
|
+
].find { |v| !v.nil? && !v.empty? }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
require "redmine_mattermost/extractors/applied_changeset"
|
86
|
+
require "redmine_mattermost/extractors/changed_wiki_page"
|
87
|
+
require "redmine_mattermost/extractors/new_issue"
|
88
|
+
require "redmine_mattermost/extractors/updated_issue"
|