70 Commits
0.2 ... 1.0.0

Author SHA1 Message Date
Alexander Meindl
18a2856aa2 Documentation fix for installation 2017-07-29 11:24:55 +02:00
Alexander Meindl
df46bf0f15 Improve installation and uninstall documentation 2017-07-29 06:59:43 +02:00
Alexander Meindl
5d6495f009 Redmine 3.4.x support and version bump to 1.0.0 2017-07-13 12:32:43 +02:00
Alexander Meindl
8162eeb01d Remove issue.save from changeset hook 2017-07-12 19:00:22 +02:00
Alexander Meindl
7e9a994c03 Merge pull request #3 from danfai/master
Send repository update message only if updates should be sent
2017-07-12 18:48:50 +02:00
danfai
2e2e2c4286 Send repository update message only if updates should be sent 2017-07-12 17:08:18 +02:00
Alexander Meindl
5882bad2cb Merge pull request #2 from TRIOTECH-fr/master
Fixed undefined project variable
2017-07-11 15:28:47 +02:00
qkdreyer
92f0c40b50 Merge branch 'master' of https://github.com/TRIOTECH-fr/redmine_messenger 2017-07-01 10:45:21 +02:00
qkdreyer
a91795a818 Fixed undefined project variable 2017-06-30 17:17:23 +02:00
Alexander Meindl
6295fb3b26 add_filter init.rb 2017-06-28 16:19:37 +02:00
Alexander Meindl
a68e3a2fb1 Add code coverage to tests 2017-06-28 15:21:30 +02:00
Alexander Meindl
b37759d850 Fork year in copyright 2017-05-25 14:40:01 +02:00
Alexander Meindl
19f7c9cd05 Some code cleanups 2017-05-25 14:23:45 +02:00
Alexander Meindl
cb8852d95c Fix compatible with other plugins which overwrites projects_helper 2017-05-25 13:32:47 +02:00
Alexander Meindl
56a3eef324 More secure route for settings 2017-05-25 12:20:11 +02:00
Alexander Meindl
87869706a6 Parent project support for icon and username has been added 2017-05-25 11:58:40 +02:00
Alexander Meindl
22ccab7e72 Parent project settings fix 2017-05-25 10:49:39 +02:00
Alexander Meindl
7f1ac1ff38 Switch from user defined project fields to project settings 2017-05-23 22:05:27 +02:00
Alexander Meindl
26d2e3978f module RedmineMessenger 2017-05-17 17:25:57 +02:00
Alexander Meindl
b783970da5 Fix spelling error for plugin class validation 2017-05-17 17:21:05 +02:00
Alexander Meindl
5689393e8d Fix mattermost documentation api url 2017-05-17 10:09:39 +02:00
Alexander Meindl
b03c15586b Only send description for issues, if not empty 2017-05-11 09:40:20 +02:00
Alexander Meindl
36fb2bb786 no escape has been fixed 2017-05-11 09:26:19 +02:00
Alexander Meindl
86ecd0676c Switch from httpclient to net/http 2017-05-11 09:20:34 +02:00
Alexander Meindl
35798e451c More localized strings and parent issue support 2017-05-10 17:49:54 +02:00
Alexander Meindl
19dd1712db Add changelog and db, passwords and contacts support 2017-05-10 16:57:33 +02:00
Alexander Meindl
f1257faf83 Fix badge 2017-05-10 13:37:27 +02:00
Alexander Meindl
abbf20b5bd Fix badge 2017-05-10 13:36:52 +02:00
Alexander Meindl
4775c2ac49 Add badges 2017-05-10 13:32:11 +02:00
Alexander Meindl
a567405682 More documenation updates 2017-05-10 13:01:03 +02:00
Alexander Meindl
0672f3808d Add assets to master branch 2017-05-10 12:51:29 +02:00
Alexander Meindl
2c0211310b Documentation update 2017-05-10 12:41:56 +02:00
Alexander Meindl
b852a33598 Fix screens 2017-05-10 12:32:16 +02:00
Alexander Meindl
3116fa6953 Documentation update 2017-05-10 12:28:17 +02:00
Alexander Meindl
1a0a087477 More documentation fixes 2017-05-10 12:17:41 +02:00
Alexander Meindl
d8010c6ff0 Installation instruction updated 2017-05-10 12:08:38 +02:00
Alexander Meindl
69a4070dcb Use issues without hooks 2017-05-10 10:52:21 +02:00
Alexander Meindl
f25dda021e More restructure code 2017-05-10 10:23:08 +02:00
Alexander Meindl
28eebbbc80 More cleanups 2017-05-09 12:22:43 +02:00
Alexander Meindl
ca69cfd67f Start working and refactoring 2017-05-08 12:11:08 +02:00
Egon Zemmer
715ac9f753 Add .gitignore. 2017-03-24 18:58:16 +01:00
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
Thanos Kyritsis
9d1c844c4c skip mentions if text is nil (can happen during bulk updates) (refs #8) 2016-03-18 11:01:55 +02:00
Thanos Kyritsis
42ff187547 do not reload issues before calling hooks, it affects custom fields (refs #7) 2016-03-16 13:58:56 +02:00
Thanos Kyritsis
3271493126 do not fire update hook if journal is nil (e.g. when creating sub-tasks) (refs #8) 2016-03-16 13:14:11 +02:00
Thanos Kyritsis
e001232b23 utilize Rails callbacks to trap issues created/saved via mail (refs #5) 2016-02-27 20:03:29 +02:00
Thanos Kyritsis
b3e8f3a6e4 fix on previous commit 2016-02-27 13:28:19 +02:00
Thanos Kyritsis
4719dec85a Add Mattermost notification when wiki pages are updated (sync with sciyoshi/redmine-slack commit 5b6ffe019635e9c925a59b688fb35568bfc96594) 2016-02-27 13:08:43 +02:00
Thanos Kyritsis
6a3c5c7694 Clarify the plugin page on Redmine. The Mattermost URL is generated from the integrations configuration page. (sync with sciyoshi/redmine-slack commit 4352948dce3a1ad400857894c9ff481052d5c8ca) 2016-02-27 13:03:38 +02:00
37 changed files with 1532 additions and 353 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.DS_Store
.buildpath
.project
.settings/
docs/_build
docs/_static
docs/_templates

43
.rubocop.yml Normal file
View File

@@ -0,0 +1,43 @@
Rails:
Enabled: true
Rails/ApplicationRecord:
Enabled: false
AllCops:
TargetRubyVersion: 2.1
Metrics/LineLength:
Max: 140
# Offense count: 34
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 60
# Offense count: 11
Metrics/CyclomaticComplexity:
Max: 13
# Offense count: 10
Metrics/PerceivedComplexity:
Max: 14
# Offense count: 15
Metrics/AbcSize:
Max: 65
Metrics/ModuleLength:
Max: 300
Metrics/ClassLength:
Max: 300
Style/Documentation:
Enabled: false
Rails/HttpPositionalArguments:
Enabled: false
Metrics/BlockLength:
Max: 50

41
CHANGELOG.md Normal file
View File

@@ -0,0 +1,41 @@
Changelog
=========
## 1.0.0
* Redmine 3.4.x compatibility
* Commit message issue bug fix
* Some code cleanups
## 0.9.9
* All global messenger settings can be overwritten project based
* Locale support added
* Wiki added supported for notification
* Contact added/updated supported for notification (if redmine_contacts is installed)
* Password added/updated supported for notification (if redmine_passwords is installed)
* DB entry added/updated supported for notification (if redmine_db is installed)
* SSL verify can be disabled
* Lots of refactoring and code cleanups
* Swith from httpclient to net/http
* Fork of redmine_rocketchat, redmine_slack and redmine_mattermost (base functions for all three messenger)
## v0.6.1
unknown changes
## v0.4
unknown changes
## v0.3
unknown changes
## v0.2
unknown changes
## v0.1
unknown changes

View File

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

View File

@@ -2,6 +2,7 @@ Released under the MIT license.
Project redmine-slack is Copyright (c) 2014-2016 by Samuel Cormier-Iijima (https://github.com/sciyoshi/redmine-slack).
Fork project redmine_mattermost is Copyright (c) 2015-2016 by AltSol (https://github.com/altsol/redmine_mattermost).
Fork project redmine_rocketchat is Copyright (c) 2016-2017 by Phlegx (https://github.com/phlegx/redmine_rocketchat).
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:

114
README.md
View File

@@ -1,64 +1,88 @@
# Mattermost chat plugin for Redmine
# Messenger plugin for Redmine
This plugin posts updates to issues in your Redmine installation to a Mattermost
channel.
This plugin posts updates to issues in your Redmine installation to [Slack](https://slack.com/), [Rocket.Chat](https://rocket.chat/) or [Mattermost](https://about.mattermost.com/) channel.
Redmine Supported versions: 2.0.x - 3.2.x.
[![Dependency Status](https://gemnasium.com/badges/github.com/AlphaNodes/redmine_messenger.svg)](https://gemnasium.com/github.com/AlphaNodes/redmine_messenger) ![Jenkins Build Status](https://pm.alphanodes.com/jenkins/buildStatus/icon?job=Devel-build-redmine-messenger)
## Features
* Post information to messenger channel
* post issue updates
* post private issue updates
* display watchers
* convert username to mentions
* post wiki updates
* post db entry (if redmine_db is installed) updates
* post password (if redmine_passwords is installed) updates
* post contact (if redmine_contacts is installed) updates
* overwrite messenger settings at project level
* parent project support (inherit messenger settings from parent project)
* multiple channel support (define one or more channels to deliver note)
## Screenshot
![screenshot](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/screenshot.png)
Mattermost output:
![screenshot](https://raw.githubusercontent.com/alphanodes/redmine_messenger/master/assets/images/screenshot_mattermost.png)
Redmine configuration:
![screenshot](https://raw.githubusercontent.com/alphanodes/redmine_messenger/master/assets/images/screenshot_redmine_settings.png)
## Prepare your messenger service
### Slack
Go to Slack documentation [Incoming Webhooks](https://api.slack.com/incoming-webhooks) for more information to set up Incoming WebHook
### Mattermost
Go to Mattermost documentation [Incoming Webhooks](https://docs.mattermost.com/developer/webhooks-incoming.html) for more information to set up Incoming WebHook
### Rocket.Chat
Go to Rocket.Chat documentation [Incoming WebHook Scripting](https://rocket.chat/docs/administrator-guides/integrations/) for more information to set up Incoming WebHook
## Requirements
* Redmine version >= 3.0.0
* Ruby version >= 2.1.5
## Installation
From your Redmine plugins directory, clone this repository as `redmine_mattermost` (note
the underscore!):
Install ``redmine_messenger`` plugin for `Redmine`
git clone https://github.com/altsol/redmine_mattermost.git redmine_mattermost
cd $REDMINE_ROOT
git clone git://github.com/alphanodes/redmine_messenger.git plugins/redmine_messenger
bundle exec rake redmine:plugins:migrate RAILS_ENV=production
You will also need the `httpclient` dependency, which can be installed by running
Restart Redmine (application server) and you should see the plugin show up in the Plugins page.
Under the configuration options, set the Messenger API URL to the URL for an
Incoming WebHook integration in your Messenger account and also set the Messenger
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).
bundle install
from the plugin directory.
## Uninstall
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).
Uninstall ``redmine_messenger``
## Customized Routing
cd $REDMINE_ROOT
bundle exec rake redmine:plugins:migrate NAME=redmine_messenger VERSION=0 RAILS_ENV=production
rm -rf plugins/redmine_messenger
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
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 `-`.
Restart Redmine (application server)
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).
![step1](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step1.png)
Step 2: Install this Redmine plugin for Mattermost.
![step2](https://raw.githubusercontent.com/altsol/redmine_mattermost/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.
![step3](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step3.png)
Step 4: For per-project customized routing, first create the project custom field (Administration > Custom fields > Project).
![step4a](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step4a.png)
![step4b](https://raw.githubusercontent.com/altsol/redmine_mattermost/assets/step4b.png)
Step 5: For per-project customized routing, configure the Mattermost channel handle inside your Redmine project.
![step5](https://raw.githubusercontent.com/altsol/redmine_mattermost/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
- [redmine_rocketchat](https://github.com/phlegx/redmine_rocketchat)
- [redmine_mattermost](https://github.com/altsol/redmine_mattermost)
- [redmine-slack](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 Messenger-namespaced configuration options in order to use all hooks for Rocket.Chat, Mattermost AND Slack in a Redmine installation.

View File

@@ -0,0 +1,35 @@
# Redmine Messenger plugin for Redmine
class MessengerSettingsController < ApplicationController
layout 'base'
before_action :find_project, :authorize, :find_user
def save
setting = MessengerSetting.find_or_create @project.id
begin
setting.transaction do
# setting.auto_preview_enabled = auto_preview_enabled
setting.assign_attributes(params[:setting])
setting.save!
end
flash[:notice] = l(:notice_successful_update)
rescue => e
flash[:error] = 'Updating failed.' + e.message
end
redirect_to controller: 'projects', action: 'settings', id: @project, tab: 'messenger'
end
private
def find_project
@project = Project.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_user
@user = User.current
end
end

234
app/models/messenger.rb Normal file
View File

@@ -0,0 +1,234 @@
# Redmine Messenger plugin for Redmine
require 'net/http'
class Messenger
include Redmine::I18n
def self.speak(msg, channels, url, options)
url = RedmineMessenger.settings[:messenger_url] unless url
return if url.blank?
return if channels.blank?
params = {
text: msg,
link_names: 1
}
username = Messenger.textfield_for_project(options[:project], :messenger_username)
params[:username] = username if username.present?
params[:attachments] = [options[:attachment]] if options[:attachment] && options[:attachment].any?
icon = Messenger.textfield_for_project(options[:project], :messenger_icon)
if icon.present?
if icon.start_with? ':'
params[:icon_emoji] = icon
else
params[:icon_url] = icon
end
end
channels.each do |channel|
uri = URI(url)
params[:channel] = channel
http_options = { use_ssl: uri.scheme == 'https' }
if RedmineMessenger.settings[:messenger_verify_ssl] != 1
http_options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
end
begin
req = Net::HTTP::Post.new(uri)
req.set_form_data(payload: params.to_json)
Net::HTTP.start(uri.hostname, uri.port, http_options) do |http|
response = http.request(req)
unless [Net::HTTPSuccess, Net::HTTPRedirection, Net::HTTPOK].include? response
Rails.logger.warn(response)
end
end
rescue StandardError => e
Rails.logger.warn("cannot connect to #{url}")
Rails.logger.warn(e)
end
end
end
def self.object_url(obj)
if Setting.host_name.to_s =~ %r{/\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i}
host = Regexp.last_match(2)
port = Regexp.last_match(4)
prefix = Regexp.last_match(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 self.url_for_project(proj)
return if proj.blank?
# project based
pm = MessengerSetting.find_by(project_id: proj.id)
return pm.messenger_url if !pm.nil? && pm.messenger_url.present?
# parent project based
parent_url = url_for_project(proj.parent)
return parent_url if parent_url.present?
# system based
return RedmineMessenger.settings[:messenger_url] if RedmineMessenger.settings[:messenger_url].present?
nil
end
def self.textfield_for_project(proj, config)
return if proj.blank?
# project based
pm = MessengerSetting.find_by(project_id: proj.id)
return pm.send(config) if !pm.nil? && pm.send(config).present?
default_textfield(proj, config)
end
def self.default_textfield(proj, config)
# parent project based
parent_field = textfield_for_project(proj.parent, config)
return parent_field if parent_field.present?
if RedmineMessenger.settings[config].present?
return RedmineMessenger.settings[config]
end
''
end
def self.channels_for_project(proj)
return [] if proj.blank?
# project based
pm = MessengerSetting.find_by(project_id: proj.id)
if !pm.nil? && pm.messenger_channel.present?
return [] if pm.messenger_channel == '-'
return pm.messenger_channel.split(',').map(&:strip).uniq
end
default_project_channels(proj)
end
def self.default_project_channels(proj)
# parent project based
parent_channel = channels_for_project(proj.parent)
return parent_channel if parent_channel.present?
# system based
if RedmineMessenger.settings[:messenger_channel].present? &&
RedmineMessenger.settings[:messenger_channel] != '-'
return RedmineMessenger.settings[:messenger_channel].split(',').map(&:strip).uniq
end
[]
end
def self.setting_for_project(proj, config)
return false if proj.blank?
@setting_found = 0
# project based
pm = MessengerSetting.find_by(project_id: proj.id)
unless pm.nil? || pm.send(config).zero?
@setting_found = 1
return false if pm.send(config) == 1
return true if pm.send(config) == 2
# 0 = use system based settings
end
default_project_setting(proj, config)
end
def self.default_project_setting(proj, config)
if proj.present? && proj.parent.present?
parent_setting = setting_for_project(proj.parent, config)
return parent_setting if @setting_found == 1
end
# system based
return true if RedmineMessenger.settings[config].present? && RedmineMessenger.settings[config] == '1'
false
end
def self.detail_to_field(detail)
field_format = nil
key = nil
escape = true
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 = if key == 'parent'
I18n.t "field_#{key}_issue"
else
I18n.t "field_#{key}"
end
end
short = true
value = detail.value.to_s
case key
when 'title', 'subject', 'description'
short = false
when 'tracker'
tracker = Tracker.find(detail.value)
value = tracker.to_s if tracker.present?
when 'project'
project = Project.find(detail.value)
value = project.to_s if project.present?
when 'status'
status = IssueStatus.find(detail.value)
value = status.to_s if status.present?
when 'priority'
priority = IssuePriority.find(detail.value)
value = priority.to_s if priority.present?
when 'category'
category = IssueCategory.find(detail.value)
value = category.to_s if category.present?
when 'assigned_to'
user = User.find(detail.value)
value = user.to_s if user.present?
when 'fixed_version'
fixed_version = Version.find(detail.value)
value = fixed_version.to_s if fixed_version.present?
when 'attachment'
attachment = Attachment.find(detail.prop_key)
value = "<#{Messenger.object_url attachment}|#{ERB::Util.html_escape(attachment.filename)}>" if attachment.present?
escape = false
when 'parent'
issue = Issue.find(detail.value)
value = "<#{Messenger.object_url issue}|#{ERB::Util.html_escape(issue)}>" if issue.present?
escape = false
end
if detail.property == 'cf' && field_format == 'version'
version = Version.find(detail.value)
value = version.to_s if version.present?
end
value = if value.present?
if escape
ERB::Util.html_escape(value)
else
value
end
else
'-'
end
result = { title: title, value: value }
result[:short] = true if short
result
end
def self.mentions(text)
return nil if text.nil?
names = extract_usernames(text)
names.present? ? '\nTo: ' + names.join(', ') : nil
end
def self.extract_usernames(text)
text = '' if text.nil?
# messenger 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

View File

@@ -0,0 +1,61 @@
# Redmine Messenger plugin for Redmine
class MessengerSetting < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :project
validates :project_id, presence: true
safe_attributes 'messenger_url',
'messenger_icon',
'messenger_channel',
'messenger_username',
'messenger_verify_ssl',
'auto_mentions',
'display_watchers',
'post_updates',
'new_include_description',
'updated_include_description',
'post_private_issues',
'post_private_notes',
'post_wiki',
'post_wiki_updates',
'post_db',
'post_db_updates',
'post_contact',
'post_contact_updates',
'post_password',
'post_password_updates'
attr_accessible :messenger_url,
:messenger_icon,
:messenger_channel,
:messenger_username,
:messenger_verify_ssl,
:auto_mentions,
:display_watchers,
:post_updates,
:new_include_description,
:updated_include_description,
:post_private_issues,
:post_private_notes,
:post_wiki,
:post_wiki_updates,
:post_db,
:post_db_updates,
:post_contact,
:post_contact_updates,
:post_password,
:post_password_updates
def self.find_or_create(p_id)
setting = MessengerSetting.find_by(project_id: p_id)
unless setting
setting = MessengerSetting.new
setting.project_id = p_id
setting.save!
end
setting
end
end

View File

@@ -0,0 +1,4 @@
<p>
<%= f.select mf, project_messenger_options(@messenger_setting.send(mf)), label: l("label_settings_#{mf}") %>
<em class="info" style="display: inline;"><%= l(:label_default) %>: <%= project_setting_messenger_default_value(mf) %></em>
</p>

View File

@@ -0,0 +1,4 @@
<p>
<%= f.text_field mf, size: size, label: l("label_settings_#{mf}") %>
<em class="info"><%= l(:label_messenger_project_text_field_info) %> (<%= l(:label_default) %>: <%= Messenger.default_textfield(@project, mf) %>)</em>
</p>

View File

@@ -0,0 +1,74 @@
<div class="box tabular messenger_settings">
<%
@messenger_setting = MessengerSetting.find_or_create(@project.id)
%>
<%= labelled_form_for :setting, @messenger_setting,
url: { controller: 'messenger_settings',
action: 'save', id: @project, tab: 'messenger',
partial: 'messenger_settings/save',
setting_id: @messenger_setting.id } do |f| %>
<%= error_messages_for 'messenger_setting' %>
<div class="box">
<div class="info"><%= t(:messenger_settings_project_intro) %></div><br />
<p><%= f.text_field :messenger_url, size: 60, label: l(:label_settings_messenger_url) %><em class="info"><%= l(:label_messenger_project_text_field_info) %> (<%= l(:label_messenger_default_not_visible) %>)</em></p>
<%= render partial: 'messenger_settings/messenger_text', locals: { f: f, mf: :messenger_icon, size: 60 } %>
<%= render partial: 'messenger_settings/messenger_text', locals: { f: f, mf: :messenger_channel, size: 30 } %>
<%= render partial: 'messenger_settings/messenger_text', locals: { f: f, mf: :messenger_username, size: 30 } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :messenger_verify_ssl } %>
<br />
<h3><%= l(:label_issue_plural) %></h3>
<div class="info"><%= t(:messenger_issue_intro) %></div><br />
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :auto_mentions } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :display_watchers } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_updates } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :new_include_description } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :updated_include_description } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_private_issues } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_private_notes } %>
<br />
<h3><%= l(:label_wiki) %></h3>
<div class="info"><%= t(:messenger_wiki_intro) %></div><br />
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_wiki } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_wiki_updates } %>
<% if RedmineMessenger::REDMINE_DB_SUPPORT && User.current.allowed_to?(:view_db_entries, @project) %>
<br />
<h3><%= l(:label_db_entry_plural) %></h3>
<div class="info"><%= t(:messenger_db_intro) %></div><br />
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_db } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_db_updates } %>
<% end %>
<% if RedmineMessenger::REDMINE_CONTACTS_SUPPORT && User.current.allowed_to?(:view_contacts, @project) %>
<br />
<h3><%= l(:label_contact_plural) %></h3>
<div class="info"><%= t(:messenger_contacts_intro) %></div><br />
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_contact } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_contact_updates } %>
<% end %>
<% if Redmine::Plugin.installed?('redmine_passwords') && User.current.allowed_to?(:view_passwords, @project) %>
<br />
<h3><%= l(:label_settings_post_password) %></h3>
<div class="info"><%= t(:messenger_passwords_intro) %></div><br />
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_password } %>
<%= render partial: 'messenger_settings/messenger_select', locals: { f: f, mf: :post_password_updates } %>
<% end %>
</div>
<%= submit_tag l(:button_save) %>
<% end %>
</div>

View File

@@ -1,42 +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>
The 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>

View File

@@ -0,0 +1,128 @@
<% @settings = ActionController::Parameters.new(@settings) %>
<div class="info"><%= t(:messenger_settings_intro) %></div><br />
<p>
<%= content_tag(:label, l(:label_settings_messenger_url)) %>
<%= text_field_tag('settings[messenger_url]', @settings[:messenger_url], size: 60, placeholder: 'https://rocket.chat/hooks/my_rocket_chat_token') %>
<em class="info"><%= t(:messenger_url_info_html) %></em>
</p>
<p>
<%= content_tag(:label, l(:label_settings_messenger_icon)) %>
<%= text_field_tag('settings[messenger_icon]', @settings[:messenger_icon], size: 60) %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_messenger_channel)) %>
<%= text_field_tag('settings[messenger_channel]', @settings[:messenger_channel], size: 30, placeholder: 'redmine') %>
<em class="info"><%= t(:messenger_channel_info_html) %></em>
</p>
<p>
<%= content_tag(:label, l(:label_settings_messenger_username)) %>
<%= text_field_tag('settings[messenger_username]', @settings[:messenger_username], size: 30) %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_messenger_verify_ssl)) %>
<%= check_box_tag 'settings[messenger_verify_ssl]', 1, @settings[:messenger_verify_ssl].to_i == 1 %>
<em class="info"><%= t(:messenger_verify_ssl_info_html) %></em>
</p>
<br />
<h3><%= l(:label_issue_plural) %></h3>
<div class="info"><%= t(:messenger_issue_intro) %></div><br />
<p>
<%= content_tag(:label, l(:label_settings_auto_mentions)) %>
<%= check_box_tag 'settings[auto_mentions]', 1, @settings[:auto_mentions].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_display_watchers)) %>
<%= check_box_tag 'settings[display_watchers]', 1, @settings[:display_watchers].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_post_updates)) %>
<%= check_box_tag 'settings[post_updates]', 1, @settings[:post_updates].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_new_include_description)) %>
<%= check_box_tag 'settings[new_include_description]', 1, @settings[:new_include_description].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_updated_include_description)) %>
<%= check_box_tag 'settings[updated_include_description]', 1, @settings[:updated_include_description].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_post_private_issues)) %>
<%= check_box_tag 'settings[post_private_issues]', 1, @settings[:post_private_issues].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_post_private_notes)) %>
<%= check_box_tag 'settings[post_private_notes]', 1, @settings[:post_private_notes].to_i == 1 %>
</p>
<br />
<h3><%= l(:label_wiki) %></h3>
<div class="info"><%= t(:messenger_wiki_intro) %></div><br />
<p>
<%= content_tag(:label, l(:label_settings_post_wiki)) %>
<%= check_box_tag 'settings[post_wiki]', 1, @settings[:post_wiki].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_post_wiki_updates)) %>
<%= check_box_tag 'settings[post_wiki_updates]', 1, @settings[:post_wiki_updates].to_i == 1 %>
</p>
<% if RedmineMessenger::REDMINE_DB_SUPPORT %>
<br />
<h3><%= l(:label_db_entry_plural) %></h3>
<div class="info"><%= t(:messenger_db_intro) %></div><br />
<p>
<%= content_tag(:label, l(:label_settings_post_db)) %>
<%= check_box_tag 'settings[post_db]', 1, @settings[:post_db].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_post_db_updates)) %>
<%= check_box_tag 'settings[post_db_updates]', 1, @settings[:post_db_updates].to_i == 1 %>
</p>
<% end %>
<% if RedmineMessenger::REDMINE_CONTACTS_SUPPORT %>
<br />
<h3><%= l(:label_contact_plural) %></h3>
<div class="info"><%= t(:messenger_contacts_intro) %></div><br />
<p>
<%= content_tag(:label, l(:label_settings_post_contact)) %>
<%= check_box_tag 'settings[post_contact]', 1, @settings[:post_contact].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_post_contact_updates)) %>
<%= check_box_tag 'settings[post_contact_updates]', 1, @settings[:post_contact_updates].to_i == 1 %>
</p>
<% end %>
<% if Redmine::Plugin.installed?('redmine_passwords') %>
<br />
<h3><%= l(:label_password_plural) %></h3>
<div class="info"><%= t(:messenger_passwords_intro) %></div><br />
<p>
<%= content_tag(:label, l(:label_settings_post_password)) %>
<%= check_box_tag 'settings[post_password]', 1, @settings[:post_password].to_i == 1 %>
</p>
<p>
<%= content_tag(:label, l(:label_settings_post_password_updates)) %>
<%= check_box_tag 'settings[post_password_updates]', 1, @settings[:post_password_updates].to_i == 1 %>
</p>
<% end %>

BIN
assets/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

49
config/locales/de.yml Normal file
View File

@@ -0,0 +1,49 @@
# German strings
de:
permission_manage_messenger: Messenger verwalten
label_messenger_contact_created: "[%{project_url}] Kontakt %{url} erstellt von *%{user}*"
label_messenger_contact_updated: "[%{project_url}] Kontakt %{url} aktualisiert von *%{user}*"
label_messenger_db_entry_created: "[%{project_url}] DB Eintrag %{url} erstellt von *%{user}*"
label_messenger_db_entry_updated: "[%{project_url}] DB Eintrag %{url} aktualisiert von *%{user}*"
label_messenger_default_not_visible: Standardeinstellung wird aus Sicherheitsgründen nicht angezeigt
label_messenger_issue_created: "[%{project_url}] Ticket %{url} erstellt von *%{user}*"
label_messenger_issue_updated: "[%{project_url}] Ticket %{url} aktualisiert von *%{user}*"
label_messenger_password_created: "[%{project_url}] Kennwort %{url} erstellt von *%{user}*"
label_messenger_password_updated: "[%{project_url}] Kennwort %{url} aktualisiert von *%{user}*"
label_messenger_project_text_field_info: Leer lassen für Systemstandard.
label_messenger_settings_default: Standardeinstellung
label_messenger_settings_disabled: Deaktiviert
label_messenger_settings_enabled: Aktiviert
label_messenger_wiki_created: "[%{project_url}] Wiki %{url} erstellt von *%{user}*"
label_messenger_wiki_updated: "[%{project_url}] Wiki %{url} aktualisiert von *%{user}*"
label_messenger: Messenger
label_settings_auto_mentions: Namen für Mentions konvertiert?
label_settings_display_watchers: Beobachter?
label_settings_messenger_channel: Messenger Channel
label_settings_messenger_icon: Messenger Icon
label_settings_messenger_url: Messenger URL
label_settings_messenger_username: Messenger Benutzer
label_settings_messenger_verify_ssl: SSL verifiziert
label_settings_new_include_description: Neue Ticketeschreibung?
label_settings_post_contact_updates: Kontakt Updates?
label_settings_post_contact: Neue Kontakte?
label_settings_post_db_updates: DB entry Updates?
label_settings_post_db: Neue DB entries?
label_settings_post_password_updates: Passwort Updates?
label_settings_post_password: Neue Passwörter?
label_settings_post_private_issues: Private Ticket Updates?
label_settings_post_private_notes: Private Notizen?
label_settings_post_updates: Ticket Updates?
label_settings_post_wiki_updates: Wiki Updates?
label_settings_post_wiki: Neue Wikis?
label_settings_updated_include_description: Ticket Beschreibungsupdates?
messenger_channel_info_html: 'Hier wird der Channel dem die Nachrichten gesendet werden sollen. Es können mehrere Channel angegeben werden, indem Sie mit Komma getrennt werden.'
messenger_contacts_intro: Legen Sie fest welche Einträge oder Änderungen für Kontakte im angegebenen Messenger Channel versendet werden.
messenger_db_intro: Legen Sie fest welche Einträge oder Änderungen für DB-Einträge im angegebenen Messenger Channel versendet werden.
messenger_issue_intro: Legen Sie fest welche Einträge oder Änderungen für Tickets im angegebenen Messenger Channel versendet werden.
messenger_passwords_intro: Legen Sie fest welche Einträge oder Änderungen für Passwörter im angegebenen Messenger Channel versendet werden.
messenger_settings_intro: Ist die Messenger URL im Adminbereich leer werden keine Nachrichten von Redmine an den Messenger verschickt. Egal welche Angaben sonst hier durchgeführt werden. Ist der globale Versand von Nachrichten für alle Projekte gewünscht, trägt man hier die URL ein. Für alle Projekte werden dann in den entsprechenden Channel Nachrichten versendet. Will man jedoch nur für bestimmte Projekte etwas schicken lässt man die URL hier leer. Die entsprechenden Einstellungen müssen dann in den jeweiligen Projekten in der Projektkonfiguration durchgeführt werden.
messenger_settings_project_intro: Wurde die Messenger URL im Administrationsbereich leer gelassen weil man Änderungen nur für bestimmte Projekte an den Messenger schicken will, trägt man in der jeweiligen Projektkonfiguration die Messenger URL ein.
messenger_url_info_html: 'Generiere eine <a target="_blank" href="https://github.com/AlphaNodes/redmine_messenger#prepare-your-messenger-service">Incoming WebHook</a> URL vom Messenger Service. Um nur aus bestimmten Projekten Nachrichten zu versenden, kann diese URL leer bleiben und in den Projekteinstellungen gesetzt werden.'
messenger_verify_ssl_info_html: 'Falls der Messenger Service ein ungültiges oder selbst erstelltes (self-signed) SSL Zertifikat verwendet bitte deaktivieren.'
messenger_wiki_intro: Legen Sie fest welche Einträge oder Änderungen für Wikis im angegebenen Messenger Channel versendet werden.

49
config/locales/en.yml Normal file
View File

@@ -0,0 +1,49 @@
# English strings
en:
permission_manage_messenger: Manage messenger
label_messenger_contact_created: "[%{project_url}] Contact %{url} created by *%{user}*"
label_messenger_contact_updated: "[%{project_url}] Contact %{url} updated by *%{user}*"
label_messenger_db_entry_created: "[%{project_url}] DB entry %{url} created by *%{user}*"
label_messenger_db_entry_updated: "[%{project_url}] DB entry %{url} updated by *%{user}*"
label_messenger_default_not_visible: Default setting is not visible for security reasons
label_messenger_issue_created: "[%{project_url}] Issue %{url} created by *%{user}*"
label_messenger_issue_updated: "[%{project_url}] Issue %{url} updated by *%{user}*"
label_messenger_password_created: "[%{project_url}] Kennwort %{url} created by *%{user}*"
label_messenger_password_updated: "[%{project_url}] Kennwort %{url} updated by *%{user}*"
label_messenger_project_text_field_info: Leave it blank for system default.
label_messenger_settings_default: System default
label_messenger_settings_disabled: Disabled
label_messenger_settings_enabled: Enabled
label_messenger_wiki_created: "[%{project_url}] Wiki %{url} created by *%{user}*"
label_messenger_wiki_updated: "[%{project_url}] Wiki %{url} updated by *%{user}*"
label_messenger: Messenger
label_settings_auto_mentions: Convert names to mentions?
label_settings_display_watchers: Display watchers?
label_settings_messenger_channel: Messenger Channel
label_settings_messenger_icon: Messenger Icon
label_settings_messenger_url: Messenger URL
label_settings_messenger_username: Messenger username
label_settings_messenger_verify_ssl: Verify SSL
label_settings_new_include_description: New issue description?
label_settings_post_contact_updates: Contact updates?
label_settings_post_contact: Contact added?
label_settings_post_db_updates: DB entry updates?
label_settings_post_db: DB entry added?
label_settings_post_password_updates: Password updates?
label_settings_post_password: Password added?
label_settings_post_private_issues: Private issue updates?
label_settings_post_private_notes: Private notes updates?
label_settings_post_updates: Issue updates?
label_settings_post_wiki_updates: Wiki updates?
label_settings_post_wiki: Post Wiki added?
label_settings_updated_include_description: Description in update issue?
messenger_channel_info_html: 'Here you have to specify the channel, which should be used. You can define multible channels, seperated by comma'
messenger_contacts_intro: Activate the changes for Issues that should be sent to the pre-defined Messenger channel.
messenger_db_intro: Activate the changes for DB that should be sent to the pre-defined Messenger channel.
messenger_issue_intro: Activate the changes for Issues that should be sent to the pre-defined Messenger channel.
messenger_passwords_intro: Activate the changes for Passwords that should be sent to the pre-defined Messenger channel.
messenger_settings_intro: "Leave the Messenger URL in the administration area empty in order to send no messages from Redmine to the Messenger. No matter what else you have configured here. In case you wish to send messages of all Redmine projects to the Messenger fill out the URL. The messages will be sent to the specific channel. If you want only to be notified by some of the projects (not all) leave the messenger URL here blank and go to the specific project configuration."
messenger_settings_project_intro: "If you left empty the Messenger URL in the administration area in case to be not globally notified by all project changes you can configure your Messenger URL in the project settings."
messenger_url_info_html: 'Generate an <a target="_blank" href="https://github.com/AlphaNodes/redmine_messenger#prepare-your-messenger-service">Incoming WebHook</a> URL from the messenger service. Leave it empty, if you only want to activate specific projects with project based settings'
messenger_verify_ssl_info_html: 'If your Messenger service uses an invalid or self-signed SSL certificate, disable it.'
messenger_wiki_intro: Activate the changes for Wikis that should be sent to the pre-defined Messenger channel.

7
config/routes.rb Normal file
View File

@@ -0,0 +1,7 @@
# Redmine Messenger plugin for Redmine
RedmineApp::Application.routes.draw do
match 'projects/:id/messenger_settings/save',
to: 'messenger_settings#save',
via: %i[post put patch]
end

View File

@@ -0,0 +1,29 @@
# Redmine Messenger plugin for Redmine
class CreateMessengerSettings < ActiveRecord::Migration
def change
create_table :messenger_settings do |t|
t.belongs_to :project, null: false
t.string :messenger_url
t.string :messenger_icon
t.string :messenger_channel
t.string :messenger_username
t.integer :messenger_verify_ssl, default: 0, null: false
t.integer :auto_mentions, default: 0, null: false
t.integer :display_watchers, default: 0, null: false
t.integer :post_updates, default: 0, null: false
t.integer :new_include_description, default: 0, null: false
t.integer :updated_include_description, default: 0, null: false
t.integer :post_private_issues, default: 0, null: false
t.integer :post_private_notes, default: 0, null: false
t.integer :post_wiki, default: 0, null: false
t.integer :post_wiki_updates, default: 0, null: false
t.integer :post_db, default: 0, null: false
t.integer :post_db_updates, default: 0, null: false
t.integer :post_contact, default: 0, null: false
t.integer :post_contact_updates, default: 0, null: false
t.integer :post_password, default: 0, null: false
t.integer :post_password_updates, default: 0, null: false
end
end
end

52
init.rb
View File

@@ -1,24 +1,40 @@
# Redmine Messenger plugin for Redmine
require 'redmine'
require 'redmine_messenger'
require_dependency 'redmine_mattermost/listener'
Redmine::Plugin.register :redmine_messenger do
name 'Redmine Messenger'
author 'AlphaNodes GmbH'
url 'https://github.com/alphanodes/redmine_messenger'
author_url 'https://alphanodes.com/'
description 'Messenger integration for Slack, Rocketchat and Mattermost support'
version '1.0.0'
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.2'
requires_redmine version_or_higher: '3.0.0'
requires_redmine :version_or_higher => '2.0.0'
permission :manage_messenger, projects: :settings, messenger_settings: :save
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: {
messenger_url: '',
messenger_icon: 'https://raw.githubusercontent.com/alphanodes/redmine_messenger/master/assets/images/icon.png',
messenger_channel: 'redmine',
messenger_username: 'robot',
messenger_verify_ssl: '1',
auto_mentions: '0',
display_watchers: '0',
post_updates: '1',
new_include_description: '1',
updated_include_description: '1',
post_private_issues: '1',
post_private_notes: '1',
post_wiki: '0',
post_wiki_updates: '0',
post_db: '0',
post_db_updates: '0',
post_contact: '0',
post_contact_updates: '0',
post_password: '0',
post_password_updates: '0'
}, partial: 'settings/messenger_settings'
end

View File

@@ -1,245 +0,0 @@
require 'httpclient'
class MattermostListener < 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
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 controller_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 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
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

29
lib/redmine_messenger.rb Normal file
View File

@@ -0,0 +1,29 @@
# Redmine Messenger plugin for Redmine
Rails.configuration.to_prepare do
module RedmineMessenger
REDMINE_CONTACTS_SUPPORT = Redmine::Plugin.installed?('redmine_contacts') ? true : false
REDMINE_DB_SUPPORT = Redmine::Plugin.installed?('redmine_db') ? true : false
# this does not work at the moment, because redmine loads passwords after messener plugin
REDMINE_PASSWORDS_SUPPORT = Redmine::Plugin.installed?('redmine_passwords') ? true : false
def self.settings
ActionController::Parameters.new(Setting[:plugin_redmine_messenger])
end
end
# Patches
require_dependency 'redmine_messenger/patches/issue_patch'
require_dependency 'redmine_messenger/patches/wiki_page_patch'
require_dependency 'redmine_messenger/patches/projects_helper_patch'
require 'redmine_messenger/patches/contact_patch' if RedmineMessenger::REDMINE_CONTACTS_SUPPORT
require 'redmine_messenger/patches/db_entry_patch' if RedmineMessenger::REDMINE_DB_SUPPORT
require 'redmine_messenger/patches/password_patch' if Redmine::Plugin.installed?('redmine_passwords')
# Global helpers
require_dependency 'redmine_messenger/helpers'
# Hooks
require_dependency 'redmine_messenger/hooks'
end

View File

@@ -0,0 +1,21 @@
# Redmine Messenger plugin for Redmine
module RedmineMessenger
module Helpers
def project_messenger_options(active)
options_for_select({ l(:label_messenger_settings_default) => 0,
l(:label_messenger_settings_disabled) => 1,
l(:label_messenger_settings_enabled) => 2 }, active)
end
def project_setting_messenger_default_value(value)
if Messenger.default_project_setting(@project, value)
l(:label_messenger_settings_enabled)
else
l(:label_messenger_settings_disabled)
end
end
end
end
ActionView::Base.send :include, RedmineMessenger::Helpers

View File

@@ -0,0 +1,54 @@
# Redmine Messenger plugin for Redmine
module RedmineMessenger
class MessengerListener < Redmine::Hook::Listener
def model_changeset_scan_commit_for_issue_ids_pre_issue_update(context = {})
issue = context[:issue]
journal = issue.current_journal
changeset = context[:changeset]
channels = Messenger.channels_for_project issue.project
url = Messenger.url_for_project issue.project
return unless channels.present? && url && issue.changes.any? && Messenger.setting_for_project(issue.project, :post_updates)
return if issue.is_private? && !Messenger.setting_for_project(issue.project, :post_private_issues)
msg = "[#{ERB::Util.html_escape(issue.project)}] #{ERB::Util.html_escape(journal.user.to_s)} updated <#{Messenger.object_url issue}|#{ERB::Util.html_escape(issue)}>"
repository = changeset.repository
if Setting.host_name.to_s =~ %r{/\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i}
host = Regexp.last_match(2)
port = Regexp.last_match(4)
prefix = Regexp.last_match(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}|#{ERB::Util.html_escape(changeset.comments)}>")
attachment[:fields] = journal.details.map { |d| Messenger.detail_to_field d }
Messenger.speak(msg, channels, url, attachment: attachment, project: repository.project)
end
end
end

View File

@@ -0,0 +1,51 @@
# Redmine Messenger plugin for Redmine
module RedmineMessenger
module Patches
module ContactPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
after_create :send_messenger_create
after_update :send_messenger_update
end
end
module InstanceMethods
def send_messenger_create
return unless Messenger.setting_for_project(project, :post_contact)
set_language_if_valid Setting.default_language
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
Messenger.speak(l(:label_messenger_contact_created,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{name}>",
user: User.current),
channels, url, project: project)
end
def send_messenger_update
return unless Messenger.setting_for_project(project, :post_contact_updates)
set_language_if_valid Setting.default_language
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
Messenger.speak(l(:label_messenger_contact_updated,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{name}>",
user: User.current),
channels, url, project: project)
end
end
end
end
end
unless Contact.included_modules.include? RedmineMessenger::Patches::ContactPatch
Contact.send(:include, RedmineMessenger::Patches::ContactPatch)
end

View File

@@ -0,0 +1,51 @@
# Redmine Messenger plugin for Redmine
module RedmineMessenger
module Patches
module DbEntryPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
after_create :send_messenger_create
after_update :send_messenger_update
end
end
module InstanceMethods
def send_messenger_create
return unless Messenger.setting_for_project(project, :post_db)
set_language_if_valid Setting.default_language
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
Messenger.speak(l(:label_messenger_db_entry_created,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{name}>",
user: User.current),
channels, url, project: project)
end
def send_messenger_update
return unless Messenger.setting_for_project(project, :post_db_updates)
set_language_if_valid Setting.default_language
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
Messenger.speak(l(:label_messenger_db_entry_updated,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{name}>",
user: User.current),
channels, url, project: project)
end
end
end
end
end
unless DbEntry.included_modules.include? RedmineMessenger::Patches::DbEntryPatch
DbEntry.send(:include, RedmineMessenger::Patches::DbEntryPatch)
end

View File

@@ -0,0 +1,89 @@
# Redmine Messenger plugin for Redmine
module RedmineMessenger
module Patches
module IssuePatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
after_create :send_messenger_create
after_update :send_messenger_update
end
end
module InstanceMethods
def send_messenger_create
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
return if is_private? && !Messenger.setting_for_project(project, :post_private_issues)
set_language_if_valid Setting.default_language
attachment = {}
if description.present? && Messenger.setting_for_project(project, :new_include_description)
attachment[:text] = ERB::Util.html_escape(description)
end
attachment[:fields] = [{
title: I18n.t(:field_status),
value: ERB::Util.html_escape(status.to_s),
short: true
}, {
title: I18n.t(:field_priority),
value: ERB::Util.html_escape(priority.to_s),
short: true
}, {
title: I18n.t(:field_assigned_to),
value: ERB::Util.html_escape(assigned_to.to_s),
short: true
}]
if RedmineMessenger.settings[:display_watchers] == '1'
attachment[:fields] << {
title: I18n.t(:field_watcher),
value: ERB::Util.html_escape(watcher_users.join(', ')),
short: true
}
end
Messenger.speak(l(:label_messenger_issue_created,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url(self)}|#{ERB::Util.html_escape(self)}>#{Messenger.mentions description if RedmineMessenger.settings[:auto_mentions] == '1'}",
user: author),
channels, url, attachment: attachment, project: project)
end
def send_messenger_update
return if current_journal.nil?
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url && Messenger.setting_for_project(project, :post_updates)
return if is_private? && !Messenger.setting_for_project(project, :post_private_issues)
return if current_journal.private_notes? && !Messenger.setting_for_project(project, :post_private_notes)
set_language_if_valid Setting.default_language
attachment = {}
if current_journal.notes.present? && Messenger.setting_for_project(project, :updated_include_description)
attachment[:text] = ERB::Util.html_escape(current_journal.notes)
end
fields = current_journal.details.map { |d| Messenger.detail_to_field d }
attachment[:fields] = fields if fields.any?
Messenger.speak(l(:label_messenger_issue_updated,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{ERB::Util.html_escape(self)}>#{Messenger.mentions(current_journal.notes) if Messenger.setting_for_project(project, :auto_mentions)}",
user: current_journal.user),
channels, url, attachment: attachment, project: project)
end
end
end
end
end
unless WikiPage.included_modules.include? RedmineMessenger::Patches::WikiPagePatch
WikiPage.send(:include, RedmineMessenger::Patches::WikiPagePatch)
end

View File

@@ -0,0 +1,51 @@
# Redmine Messenger plugin for Redmine
module RedmineMessenger
module Patches
module PasswordPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
after_create :send_messenger_create
after_update :send_messenger_update
end
end
module InstanceMethods
def send_messenger_create
return unless Messenger.setting_for_project(project, :post_password)
set_language_if_valid Setting.default_language
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
Messenger.speak(l(:label_messenger_password_created,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{name}>",
user: User.current),
channels, url, project: project)
end
def send_messenger_update
return unless Messenger.setting_for_project(project, :post_password_updates)
set_language_if_valid Setting.default_language
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
Messenger.speak(l(:label_messenger_password_updated,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{name}>",
user: User.current),
channels, url, project: project)
end
end
end
end
end
unless Password.included_modules.include? RedmineMessenger::Patches::PasswordPatch
Password.send(:include, RedmineMessenger::Patches::PasswordPatch)
end

View File

@@ -0,0 +1,32 @@
# Redmine Messenger plugin for Redmine
module RedmineMessenger
module Patches
module ProjectsHelperPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :project_settings_tabs, :messenger
end
end
module InstanceMethods
def project_settings_tabs_with_messenger
tabs = project_settings_tabs_without_messenger
action = { name: 'messenger',
controller: 'messenger_settings',
action: :show,
partial: 'messenger_settings/show',
label: :label_messenger }
tabs << action if User.current.allowed_to?(:manage_messenger, @project)
tabs
end
end
end
end
end
unless ProjectsHelper.included_modules.include?(RedmineMessenger::Patches::ProjectsHelperPatch)
ProjectsHelper.send(:include, RedmineMessenger::Patches::ProjectsHelperPatch)
end

View File

@@ -0,0 +1,58 @@
# Redmine Messenger plugin for Redmine
module RedmineMessenger
module Patches
module WikiPagePatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
after_create :send_messenger_create
after_update :send_messenger_update
end
end
module InstanceMethods
def send_messenger_create
return unless Messenger.setting_for_project(project, :post_wiki)
set_language_if_valid Setting.default_language
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
Messenger.speak(l(:label_messenger_wiki_created,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{title}>",
user: User.current),
channels, url, project: project)
end
def send_messenger_update
return unless Messenger.setting_for_project(project, :post_wiki_updates)
set_language_if_valid Setting.default_language
channels = Messenger.channels_for_project project
url = Messenger.url_for_project project
return unless channels.present? && url
attachment = nil
unless content.comments.empty?
attachment = {}
attachment[:text] = ERB::Util.html_escape(content.comments.to_s)
end
Messenger.speak(l(:label_messenger_wiki_updated,
project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>",
url: "<#{Messenger.object_url self}|#{title}>",
user: content.author),
channels, url, project: project, attachment: attachment)
end
end
end
end
end
unless Issue.included_modules.include? RedmineMessenger::Patches::IssuePatch
Issue.send(:include, RedmineMessenger::Patches::IssuePatch)
end

View File

@@ -0,0 +1,43 @@
# Redmine Messenger plugin for Redmine
require File.expand_path('../../test_helper', __FILE__)
class CommonViewsTest < Redmine::IntegrationTest
fixtures :projects,
:users,
:roles,
:members,
:member_roles,
:trackers,
:projects_trackers,
:enabled_modules,
:issue_statuses,
:issues,
:enumerations,
:custom_fields,
:custom_values,
:custom_fields_trackers
def setup
RedmineMessenger::TestCase.prepare
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@request.env['HTTP_REFERER'] = '/'
end
test 'View user' do
log_user('admin', 'admin')
get '/users/2'
assert_response :success
end
test 'View issue' do
log_user('admin', 'admin')
EnabledModule.create(project_id: 1, name: 'issue_tracking')
issue = Issue.where(id: 1).first
issue.save
get '/issues/1'
assert_response :success
end
end

56
test/test_helper.rb Normal file
View File

@@ -0,0 +1,56 @@
# Redmine Messenger plugin for Redmine
require 'simplecov'
require 'simplecov-rcov'
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
SimpleCov::Formatter::HTMLFormatter,
SimpleCov::Formatter::RcovFormatter
]
SimpleCov.start :rails do
add_filter 'init.rb'
root File.expand_path(File.dirname(__FILE__) + '/..')
end
require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
module RedmineMessenger
class TestCase
include ActionDispatch::TestProcess
def self.plugin_fixtures(plugin, *fixture_names)
plugin_fixture_path = "#{Redmine::Plugin.find(plugin).directory}/test/fixtures"
if fixture_names.first == :all
fixture_names = Dir["#{plugin_fixture_path}/**/*.{yml}"]
fixture_names.map! { |f| f[(plugin_fixture_path.size + 1)..-5] }
else
fixture_names = fixture_names.flatten.map(&:to_s)
end
ActiveRecord::Fixtures.create_fixtures(plugin_fixture_path, fixture_names)
end
def uploaded_test_file(name, mime)
ActionController::TestUploadedFile.new(ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime, true)
end
def self.arrays_equal?(a1, a2)
(a1 - a2) - (a2 - a1) == []
end
def self.create_fixtures(fixtures_directory, table_names, _class_names = {})
ActiveRecord::FixtureSet.create_fixtures(fixtures_directory, table_names, _class_names = {})
end
def self.prepare
Role.where(id: [1, 2]).each do |r|
r.permissions << :view_issues
r.save
end
Project.where(id: [1, 2]).each do |project|
EnabledModule.create(project: project, name: 'issue_tracking')
end
end
end
end

41
test/unit/i18n_test.rb Normal file
View File

@@ -0,0 +1,41 @@
# Redmine Messenger plugin for Redmine
require File.expand_path('../../test_helper', __FILE__)
module Redmine
class I18nTest < ActiveSupport::TestCase
include Redmine::I18n
def setup
User.current = nil
end
def teardown
set_language_if_valid 'en'
end
def test_valid_languages
assert valid_languages.is_a?(Array)
assert valid_languages.first.is_a?(Symbol)
end
def test_locales_validness
lang_files_count = Dir[Rails.root.join('plugins',
'redmine_messenger',
'config',
'locales',
'*.yml')].size
assert_equal lang_files_count, 2
valid_languages.each do |lang|
assert set_language_if_valid(lang)
end
# check if parse error exists
::I18n.locale = 'de'
assert_equal 'Messenger Benutzer', l(:label_settings_messenger_username)
::I18n.locale = 'en'
assert_equal 'Messenger username', l(:label_settings_messenger_username)
set_language_if_valid('en')
end
end
end

31
test/unit/issue_test.rb Normal file
View File

@@ -0,0 +1,31 @@
# Redmine Messenger plugin for Redmine
require File.expand_path('../../test_helper', __FILE__)
class IssueTest < ActiveSupport::TestCase
fixtures :projects, :users, :members, :member_roles, :roles,
:trackers, :projects_trackers,
:enabled_modules,
:issue_statuses, :issue_categories, :workflows,
:enumerations,
:issues, :journals, :journal_details,
:custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
:time_entries
include Redmine::I18n
def setup
set_language_if_valid 'en'
end
def teardown
User.current = nil
end
def test_create
issue = Issue.new(project_id: 1, tracker_id: 1, author_id: 3, subject: 'test_create')
assert issue.save
assert_equal issue.tracker.default_status, issue.status
assert issue.description.nil?
end
end

56
test/unit/project_test.rb Normal file
View File

@@ -0,0 +1,56 @@
# This file is a part of redmine_reporting,
# a reporting and statistics plugin for Redmine.
#
# Copyright (c) 2016-2017 AlphaNodes GmbH
# https://alphanodes.com
#
# redmine_reporting is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# redmine_reporting is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_reporting. If not, see <http://www.gnu.org/licenses/>.
require File.expand_path('../../test_helper', __FILE__)
class ProjectTest < ActiveSupport::TestCase
fixtures :projects, :trackers, :issue_statuses, :issues,
:journals, :journal_details,
:enumerations, :users, :issue_categories,
:projects_trackers,
:custom_fields,
:custom_fields_projects,
:custom_fields_trackers,
:custom_values,
:roles,
:member_roles,
:members,
:enabled_modules,
:versions,
:wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
:groups_users,
:time_entries,
:news, :comments,
:documents,
:workflows
def setup
User.current = User.find(1)
end
def test_create_project
Project.delete_all
Project.create!(name: 'Project Messenger', identifier: 'project-messenger')
assert_equal 1, Project.count
end
def test_load_project
Project.find(1)
end
end