redmine-mattermost 0.3 → 0.4.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +15 -0
  3. data/LICENSE +1 -0
  4. data/README.md +30 -17
  5. data/Rakefile +9 -0
  6. data/app/views/settings/_mattermost_settings.html.erb +60 -20
  7. data/lib/redmine-mattermost.rb +9 -1
  8. data/lib/redmine_mattermost/bridge.rb +63 -0
  9. data/lib/redmine_mattermost/client.rb +28 -0
  10. data/lib/redmine_mattermost/extractors.rb +88 -0
  11. data/lib/redmine_mattermost/extractors/applied_changeset.rb +55 -0
  12. data/lib/redmine_mattermost/extractors/changed_wiki_page.rb +39 -0
  13. data/lib/redmine_mattermost/extractors/new_issue.rb +44 -0
  14. data/lib/redmine_mattermost/extractors/updated_issue.rb +42 -0
  15. data/lib/redmine_mattermost/infos.rb +1 -2
  16. data/lib/redmine_mattermost/issue_patch.rb +14 -14
  17. data/lib/redmine_mattermost/listener.rb +38 -268
  18. data/lib/redmine_mattermost/message_builder.rb +61 -0
  19. data/lib/redmine_mattermost/redmine_plugin.rb +3 -4
  20. data/lib/redmine_mattermost/utils.rb +3 -0
  21. data/lib/redmine_mattermost/utils/journal_mapper.rb +75 -0
  22. data/lib/redmine_mattermost/utils/text.rb +37 -0
  23. data/lib/redmine_mattermost/utils/urls.rb +20 -0
  24. data/lib/redmine_mattermost/version.rb +1 -1
  25. data/redmine-mattermost.gemspec +4 -1
  26. data/spec/client_spec.rb +29 -0
  27. data/spec/extractors/applied_changeset_spec.rb +82 -0
  28. data/spec/extractors/changed_wiki_page_spec.rb +63 -0
  29. data/spec/extractors/new_issue_spec.rb +209 -0
  30. data/spec/extractors/updated_issue_spec.rb +169 -0
  31. data/spec/message_builder_spec.rb +71 -0
  32. data/spec/redmine_mattermost_spec.rb +9 -0
  33. data/spec/spec_helper.rb +17 -0
  34. data/spec/support/mock_support.rb +113 -0
  35. data/spec/utils/text_spec.rb +142 -0
  36. data/spec/utils/text_spec_alt.rb +142 -0
  37. metadata +87 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b17e4d44f9c953dabfdd955d7970ab1630ea37f2
4
- data.tar.gz: 861ca5d7a696c50dc9c01a1690ae7599ceee0e8e
3
+ metadata.gz: ccd8592a22be9dae331562b803f93b1515ed9022
4
+ data.tar.gz: 0243de9f725c2751744d4106dcc7f4446a30b44b
5
5
  SHA512:
6
- metadata.gz: 2e95d3b82ecc49c62cdff20a3ca87a13d61464860e2fe98fd854cb027f8e1743628a2523cdfd73962bb92f5a62ac6552abca76ede3d76cbba3bcfae2af262837
7
- data.tar.gz: 0a389640ad699753e24731d29768f24dc2df5dac2825c3a7db9c4fa1222ffb1b00336316a3ab70214bac93271dd0ef14d840243cf6d1537bae374bb3d1bfa980
6
+ metadata.gz: ec2dcbbbde62fa4e40070f7ac1691487a029463bdcae62bc3c050e904ee752b8f12e162d6f2f05808685f0f86808a36298853ede6e684e10b33552f8488da0e9
7
+ data.tar.gz: 68e1c9640f500e3fc491d6c90deeec7a196e9f46aa18cbfb5a8fc4ee2a46127b18f9a4955f448cadd496ddd9c4eae14b5cd69590df90a3480481e85c844a19e1
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ rvm:
5
+ - ruby-head
6
+ - 2.3.1
7
+ - 2.2
8
+ - 2.1
9
+ - 2.0
10
+ before_install:
11
+ - gem install bundler
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: ruby-head
15
+ fast_finish: true
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
- ### NOTE: This is a fork of [altsol/redmine_mattermost](https://github.com/altsol/redmine_mattermost) to support a Gem-based plugin for Redmine!
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 in your Redmine installation to a Mattermost
6
- channel.
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.x - 3.2.x.
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/altsol/redmine_mattermost/assets/screenshot.png)
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.3"
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 Routing
44
+ ## Customized channels
30
45
 
31
- You can also route messages to different channels on a per-project basis. To
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/altsol/redmine_mattermost/assets/step1.png)
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/altsol/redmine_mattermost/assets/step3.png)
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/altsol/redmine_mattermost/assets/step4a.png)
52
- ![step4b](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step4b.png)
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/altsol/redmine_mattermost/assets/step5.png)
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. This fork is just refactored to allow a Gem-based installation.
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,3 +1,12 @@
1
1
  #!/usr/bin/env rake
2
2
 
3
3
  require "bundler/gem_tasks"
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:spec) do |test|
7
+ test.test_files = FileList['spec/**/*_spec.rb']
8
+ test.libs << 'spec'
9
+ test.verbose = true
10
+ end
11
+
12
+ task default: [:spec]
@@ -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
- <label for="settings_mattermost_url">Mattermost URL</label>
3
- <input type="text" id="settings_mattermost_url" size=80 value="<%= settings['mattermost_url'] %>" name="settings[mattermost_url]" />
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
- 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).
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
- <label for="settings_channel">Mattermost Channel</label>
12
- <input type="text" id="settings_channel" value="<%= settings['channel'] %>" name="settings[channel]" />
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
- The channel can be changed on a per-project basis by creating a
17
- <a href="/custom_fields/new?type=ProjectCustomField">project custom field</a> named "Mattermost Channel" (without quotes).
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
- <label for="settings_icon">Mattermost Icon</label>
22
- <input type="text" id="settings_icon" value="<%= settings['icon'] %>" name="settings[icon]" />
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
- <label for="settings_username">Mattermost Username</label>
27
- <input type="text" id="settings_username" value="<%= settings['username'] %>" name="settings[username]" />
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
- <label for="settings_display_watchers">Display Watchers?</label>
32
- <select id="settings_display_watchers" value="<%= settings['display_watchers'] %>" name="settings[display_watchers]">
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
- <label for="settings_post_updates">Post Issue Updates?</label>
40
- <input type="checkbox" id="settings_post_updates" value="1" name="settings[post_updates]" <%= settings['post_updates'] == '1' ? 'checked="checked"' : '' %> />
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
- <label for="settings_post_wiki_updates">Post Wiki Updates?</label>
45
- <input type="checkbox" id="settings_post_wiki_updates" value="1" name="settings[post_wiki_updates]" <%= settings['post_wiki_updates'] == '1' ? 'checked="checked"' : '' %> />
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>
@@ -1,6 +1,14 @@
1
1
  require "redmine_mattermost/version"
2
2
  require "redmine_mattermost/infos"
3
- require "redmine_mattermost/engine"
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"