Skip to content

Commit 7fa248a

Browse files
author
Komatsu Yuji(小松 裕二)
committed
'first'
1 parent 0e69688 commit 7fa248a

File tree

10 files changed

+368
-0
lines changed

10 files changed

+368
-0
lines changed

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gem "httpclient"

README.rdoc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
= redmine_hangouts_chat_integration
2+
3+
Description goes here
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<table>
2+
<tbody>
3+
<tr>
4+
<th>Webhook</th>
5+
<td>
6+
<input type="text" id="settings_hangouts_chat_webhook" value="<%= settings['hangouts_chat_webhook'] %>" name="settings[hangouts_chat_webhook]" size=120 >
7+
</td>
8+
</tr>
9+
</tbody>
10+
</table>

config/locales/en.yml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# English strings go here for Rails i18n
2+
en:
3+
# my_label: "My label"

config/routes.rb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Plugin's routes
2+
# See: http://guides.rubyonrails.org/routing.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class PopulateCustomFields < ActiveRecord::Migration
2+
3+
################################################################################
4+
## Create custom field
5+
################################################################################
6+
def self.up
7+
if ProjectCustomField.find_by_name('Hangouts Chat Webhook').nil?
8+
ProjectCustomField.create(name: 'Hangouts Chat Webhook', field_format: 'string', visible: 0, default_value: '')
9+
end
10+
if UserCustomField.find_by_name('Hangouts Chat Disabled').nil?
11+
UserCustomField.create(name: 'Hangouts Chat Disabled', field_format: 'bool', visible: 0, default_value: 0, is_required: 1)
12+
end
13+
end
14+
15+
################################################################################
16+
## Delete custom field
17+
################################################################################
18+
def self.down
19+
unless ProjectCustomField.find_by_name('Hangouts Chat Webhook').nil?
20+
ProjectCustomField.find_by_name('Hangouts Chat Webhook').delete
21+
end
22+
unless UserCustomField.find_by_name('Hangouts Chat Disabled').nil?
23+
UserCustomField.find_by_name('Hangouts Chat Disabled').delete
24+
end
25+
end
26+
end

init.rb

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require_dependency 'redmine_hangouts_chat_integration/hooks'
2+
require_dependency 'redmine_hangouts_chat_integration/issue_relations_controller_patch'
3+
4+
################################################################################
5+
## Register Plugin
6+
################################################################################
7+
Redmine::Plugin.register :redmine_hangouts_chat_integration do
8+
name 'Redmine Hangouts Chat Integration plugin'
9+
author 'Komatsu Yuji'
10+
description 'This is a plugin for Redmine Google Hangouts Chat Integration'
11+
version '0.0.4'
12+
url 'https://www.future.co.jp/'
13+
author_url 'https://www.future.co.jp/'
14+
15+
settings :default => {}, :partial => 'settings/hangouts_chat_integration_settings'
16+
17+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
require 'httpclient'
2+
3+
module RedmineHangoutsChatIntegration
4+
class Hooks < Redmine::Hook::ViewListener
5+
include ApplicationHelper
6+
include CustomFieldsHelper
7+
include IssuesHelper
8+
9+
################################################################################
10+
## Hook for issues_new_after_save
11+
################################################################################
12+
def controller_issues_new_after_save(context={})
13+
## check user
14+
return if is_disabled(User.current)
15+
16+
## Get issue
17+
issue = context[:issue]
18+
19+
## Is private issue
20+
return if issue.is_private
21+
22+
## Get issue URL
23+
issue_url = get_object_url(issue)
24+
25+
## Get Webhook URL
26+
webhook_url = get_webhook_url(issue.project)
27+
return if webhook_url.nil?
28+
29+
## Make Webhook Thread URL
30+
thread_key = Digest::MD5.hexdigest(issue_url)
31+
thread_url = webhook_url + "&thread_key=" + thread_key
32+
return unless thread_url =~ URI::regexp
33+
34+
## webhook data
35+
data = {}
36+
37+
## Add issue updated_on
38+
data['text'] = "*#{l(:field_updated_on)}:#{issue.updated_on}*"
39+
40+
## Add issue subject
41+
subject = issue.subject.gsub(/[ |\s|]+$/, "")
42+
data['text'] = data['text'] + "\n*#{l(:field_subject)}:<#{issue_url}|[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{subject}>*"
43+
44+
## Add issue URL
45+
data['text'] = data['text'] + "\n*URL:* #{issue_url}"
46+
47+
## Add issue author
48+
data['text'] = data['text'] + "\n```" + l(:text_issue_added, :id => "##{issue.id}", :author => issue.author)
49+
50+
## Add issue attributes
51+
data['text'] = data['text'] + "\n#{''.ljust(37, '-')}\n" + render_email_issue_attributes(issue, User.current)
52+
53+
## Add issue descripption
54+
unless issue.description.blank?
55+
data['text'] = data['text'] + "\n#{''.ljust(37, '-')}\n#{issue.description}"
56+
end
57+
58+
## Add issue attachments
59+
if issue.attachments.any?
60+
data['text'] = data['text'] + "\n\n#{l(:label_attachment_plural).ljust(37, '-')}"
61+
issue.attachments.each do |attachment|
62+
data['text'] = data['text'] + "\n#{attachment.filename} #{number_to_human_size(attachment.filesize)}"
63+
end
64+
end
65+
66+
## Add ```
67+
data['text'] = data['text'] + "\n```"
68+
69+
## Send webhook data
70+
send_webhook_data(thread_url, data)
71+
end
72+
73+
################################################################################
74+
## Hook for controller_issues_edit_after_save
75+
################################################################################
76+
def controller_issues_edit_after_save(context={})
77+
## check user
78+
return if is_disabled(User.current)
79+
80+
## Get issue and journal
81+
issue = context[:issue]
82+
journal = context[:journal]
83+
84+
## Is private issue
85+
return if issue.is_private
86+
return if journal.private_notes
87+
88+
## Get issue URL
89+
issue_url = get_object_url(issue)
90+
91+
## Get Webhook URL
92+
webhook_url = get_webhook_url(issue.project)
93+
return if webhook_url.nil?
94+
95+
## Make Webhook Thread URL
96+
thread_key = Digest::MD5.hexdigest(issue_url)
97+
thread_url = webhook_url + "&thread_key=" + thread_key
98+
return unless thread_url =~ URI::regexp
99+
100+
## webhook data
101+
data = {}
102+
103+
## Add issue updated_on
104+
data['text'] = "*#{l(:field_updated_on)}:#{issue.updated_on}*"
105+
106+
## Add issue subject
107+
subject = issue.subject.gsub(/[ |\s|]+$/, "")
108+
data['text'] = data['text'] + "\n*#{l(:field_subject)}:<#{issue_url}|[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{subject}>*"
109+
110+
## Add issue URL
111+
data['text'] = data['text'] + "\n*URL:* #{issue_url}"
112+
113+
## Add issue author
114+
data['text'] = data['text'] + "\n```" + l(:text_issue_updated, :id => "##{issue.id}", :author => journal.user)
115+
116+
## Add issue details
117+
details = details_to_strings(journal.visible_details, true).join("\n")
118+
unless details.blank?
119+
data['text'] = data['text'] + "\n#{''.ljust(37, '-')}\n#{details}"
120+
end
121+
122+
## Add issue notes
123+
unless issue.notes.blank?
124+
data['text'] = data['text'] + "\n#{''.ljust(37, '-')}\n#{issue.notes}"
125+
end
126+
127+
## Add ```
128+
data['text'] = data['text'] + "\n```"
129+
130+
## Don't send empty data
131+
return if details.blank? && issue.notes.blank?
132+
133+
## Send webhook data
134+
send_webhook_data(thread_url, data)
135+
end
136+
137+
################################################################################
138+
## Hook for controller_issue_relations_new_after_save
139+
################################################################################
140+
def controller_issue_relations_new_after_save(context={})
141+
call_hook(:controller_issues_edit_after_save, context)
142+
end
143+
144+
################################################################################
145+
## Hook for controller_issue_relations_move_after_save
146+
################################################################################
147+
def controller_issue_relations_move_after_save(context={})
148+
call_hook(:controller_issues_edit_after_save, context)
149+
end
150+
151+
################################################################################
152+
## Private Method
153+
################################################################################
154+
private
155+
156+
################################################################################
157+
## Get Redmine Object URL
158+
################################################################################
159+
def get_object_url(obj)
160+
routes = Rails.application.routes.url_helpers
161+
if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
162+
host, port, prefix = $2, $4, $5
163+
routes.url_for(obj.event_url({
164+
:host => host,
165+
:protocol => Setting.protocol,
166+
:port => port,
167+
:script_name => prefix
168+
}))
169+
else
170+
routes.url_for(obj.event_url({
171+
:host => Setting.host_name,
172+
:protocol => Setting.protocol
173+
}))
174+
end
175+
end
176+
177+
################################################################################
178+
## Is Hangouts Chat Disabled
179+
################################################################################
180+
def is_disabled(user)
181+
## check user
182+
return true if user.nil?
183+
184+
## check user custom field
185+
user_cf = UserCustomField.find_by_name("Hangouts Chat Disabled")
186+
return true if user_cf.nil?
187+
188+
## check user custom value
189+
user_cv = user.custom_value_for(user_cf)
190+
191+
## user_cv is null
192+
return false if user_cv.nil?
193+
194+
return false if user_cv.value == '0'
195+
196+
return true
197+
end
198+
199+
################################################################################
200+
## Get Hangouts Chat Webhook URL
201+
################################################################################
202+
def get_webhook_url(proj)
203+
## used value from this plugin's setting
204+
if proj.nil?
205+
return Setting.plugin_redmine_hangouts_chat_integration['hangouts_chat_webhook']
206+
end
207+
208+
## used value from this project's custom field
209+
proj_cf = ProjectCustomField.find_by_name("Hangouts Chat Webhook")
210+
unless proj_cf.nil?
211+
proj_cv = proj.custom_value_for(proj_cf)
212+
unless proj_cv.nil?
213+
url = proj_cv.value
214+
return url if url =~ URI::regexp
215+
end
216+
end
217+
218+
## used value from parent project's custom field
219+
return get_webhook_url(proj.parent)
220+
end
221+
222+
################################################################################
223+
## Send data to Hangouts Chat
224+
################################################################################
225+
def send_webhook_data(url, data)
226+
Rails.logger.debug("Webhook URL: #{url}")
227+
Rails.logger.debug("Webhook Data: #{data.to_json}")
228+
229+
## Send data
230+
begin
231+
https_proxy = ENV['https_proxy'] || ENV['HTTPS_PROXY']
232+
client = HTTPClient.new(https_proxy)
233+
client.ssl_config.cert_store.set_default_paths
234+
client.ssl_config.ssl_version = :auto
235+
client.post_async url, {:body => data.to_json, :header => {'Content-Type' => 'application/json'}}
236+
rescue Exception => e
237+
Rails.logger.warn("cannot connect to #{url}")
238+
Rails.logger.warn(e)
239+
end
240+
end
241+
end
242+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
module RedmineHangoutsChatIntegration
2+
3+
################################################################################
4+
## Expand IssueRelationsController
5+
################################################################################
6+
module IssueRelationsControllerPatch
7+
def self.included(base)
8+
base.send(:include, InstanceMethods)
9+
## Same as typing in the class
10+
base.class_eval do
11+
unloadable
12+
after_filter :after_action_create, :only => :create
13+
after_filter :after_action_destroy, :only => :destroy
14+
end
15+
end
16+
17+
################################################################################
18+
## Expand InstanceMethods
19+
################################################################################
20+
module InstanceMethods
21+
22+
################################################################################
23+
## Call a action after issue_relation is created
24+
################################################################################
25+
def after_action_create
26+
return if @relation.nil?
27+
return if @relation.id.nil?
28+
return if @issue.nil?
29+
30+
call_hook(:controller_issue_relations_new_after_save, { :params => params, :issue => @issue, :journal => @issue.current_journal })
31+
end
32+
33+
################################################################################
34+
## Call a action after issue_relation is destroied
35+
################################################################################
36+
def after_action_destroy
37+
return if @relation.nil?
38+
return if @relation.id.nil?
39+
40+
issue_relation = nil
41+
begin
42+
issue_relation = IssueRelation.find(@relation.id)
43+
rescue ActiveRecord::RecordNotFound
44+
issue_relation = nil
45+
end
46+
return unless issue_relation.nil?
47+
48+
issue_from = @relation.issue_from
49+
issue_to = @relation.issue_to
50+
51+
call_hook(:controller_issue_relations_move_after_save, { :params => params, :issue => issue_from, :journal => issue_from.current_journal })
52+
call_hook(:controller_issue_relations_move_after_save, { :params => params, :issue => issue_to, :journal => issue_to.current_journal })
53+
end
54+
55+
end
56+
end
57+
end
58+
59+
################################################################################
60+
## Include IssueRelationsController
61+
################################################################################
62+
IssueRelationsController.send(:include, RedmineHangoutsChatIntegration::IssueRelationsControllerPatch)

test/test_helper.rb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Load the Redmine helper
2+
require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')

0 commit comments

Comments
 (0)