redmine-mattermost 0.3 → 0.4.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +15 -0
- data/LICENSE +1 -0
- data/README.md +30 -17
- data/Rakefile +9 -0
- data/app/views/settings/_mattermost_settings.html.erb +60 -20
- data/lib/redmine-mattermost.rb +9 -1
- data/lib/redmine_mattermost/bridge.rb +63 -0
- data/lib/redmine_mattermost/client.rb +28 -0
- data/lib/redmine_mattermost/extractors.rb +88 -0
- data/lib/redmine_mattermost/extractors/applied_changeset.rb +55 -0
- data/lib/redmine_mattermost/extractors/changed_wiki_page.rb +39 -0
- data/lib/redmine_mattermost/extractors/new_issue.rb +44 -0
- data/lib/redmine_mattermost/extractors/updated_issue.rb +42 -0
- data/lib/redmine_mattermost/infos.rb +1 -2
- data/lib/redmine_mattermost/issue_patch.rb +14 -14
- data/lib/redmine_mattermost/listener.rb +38 -268
- data/lib/redmine_mattermost/message_builder.rb +61 -0
- data/lib/redmine_mattermost/redmine_plugin.rb +3 -4
- data/lib/redmine_mattermost/utils.rb +3 -0
- data/lib/redmine_mattermost/utils/journal_mapper.rb +75 -0
- data/lib/redmine_mattermost/utils/text.rb +37 -0
- data/lib/redmine_mattermost/utils/urls.rb +20 -0
- data/lib/redmine_mattermost/version.rb +1 -1
- data/redmine-mattermost.gemspec +4 -1
- data/spec/client_spec.rb +29 -0
- data/spec/extractors/applied_changeset_spec.rb +82 -0
- data/spec/extractors/changed_wiki_page_spec.rb +63 -0
- data/spec/extractors/new_issue_spec.rb +209 -0
- data/spec/extractors/updated_issue_spec.rb +169 -0
- data/spec/message_builder_spec.rb +71 -0
- data/spec/redmine_mattermost_spec.rb +9 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/mock_support.rb +113 -0
- data/spec/utils/text_spec.rb +142 -0
- data/spec/utils/text_spec_alt.rb +142 -0
- metadata +87 -9
@@ -0,0 +1,55 @@
|
|
1
|
+
module RedmineMattermost
|
2
|
+
module Extractors
|
3
|
+
class AppliedChangeset < Base
|
4
|
+
MESSAGE = "[%{project_link}] %{editor} updated %{object_link}"
|
5
|
+
REVISION_LINK = "<%{url}|%{title}>"
|
6
|
+
|
7
|
+
def from_context(context)
|
8
|
+
issue = context.fetch(:issue)
|
9
|
+
changeset = context.fetch(:changeset)
|
10
|
+
journal = issue.current_journal
|
11
|
+
|
12
|
+
return if issue.is_private?
|
13
|
+
return unless issue.valid?
|
14
|
+
return unless channel = determine_channel(issue.project)
|
15
|
+
return unless url = determine_url(issue.project)
|
16
|
+
|
17
|
+
args = {
|
18
|
+
project_link: link(issue.project, event_url(issue.project)),
|
19
|
+
editor: h(journal.user),
|
20
|
+
object_link: link(issue, event_url(issue))
|
21
|
+
}
|
22
|
+
|
23
|
+
msg = MessageBuilder.new(MESSAGE % args)
|
24
|
+
msg.channel(channel)
|
25
|
+
msg.icon(determine_icon(issue.project))
|
26
|
+
msg.username(determine_username(issue.project))
|
27
|
+
attachment = msg.attachment
|
28
|
+
attachment.text(t("text_status_changed_by_changeset", value: revision_link(changeset)))
|
29
|
+
mapper = Utils::JournalMapper.new(bridge)
|
30
|
+
mapper.to_fields(journal).map do |field|
|
31
|
+
attachment.field(*field)
|
32
|
+
end
|
33
|
+
|
34
|
+
{ url: url, message: msg.to_hash }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def revision_link(changeset)
|
40
|
+
repository = changeset.repository
|
41
|
+
options = default_url_options.merge(
|
42
|
+
controller: "repositories",
|
43
|
+
action: "revision",
|
44
|
+
id: repository.project,
|
45
|
+
repository_id: repository.identifier_param,
|
46
|
+
rev: changeset.revision
|
47
|
+
)
|
48
|
+
REVISION_LINK % {
|
49
|
+
url: bridge.url_for(options),
|
50
|
+
title: h(changeset.comments)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RedmineMattermost
|
2
|
+
module Extractors
|
3
|
+
class ChangedWikiPage < Base
|
4
|
+
MESSAGE = "[%{project_link}] %{editor} updated %{object_link}"
|
5
|
+
|
6
|
+
def from_context(context)
|
7
|
+
page = context.fetch(:page)
|
8
|
+
project = context.fetch(:project)
|
9
|
+
|
10
|
+
return unless notify?
|
11
|
+
return unless channel = determine_channel(project)
|
12
|
+
return unless url = determine_url(project)
|
13
|
+
|
14
|
+
args = {
|
15
|
+
project_link: link(project, event_url(project)),
|
16
|
+
editor: h(page.content.author),
|
17
|
+
object_link: link(page.title, event_url(page)),
|
18
|
+
}
|
19
|
+
|
20
|
+
msg = MessageBuilder.new(MESSAGE % args)
|
21
|
+
msg.channel(channel)
|
22
|
+
msg.icon(determine_icon(project))
|
23
|
+
msg.username(determine_username(project))
|
24
|
+
unless page.content.comments.empty?
|
25
|
+
attachment = msg.attachment
|
26
|
+
attachment.text(h page.content.comments)
|
27
|
+
end
|
28
|
+
|
29
|
+
{ url: url, message: msg.to_hash }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def notify?
|
35
|
+
settings.plugin_redmine_mattermost[:post_wiki_updates] == "1"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RedmineMattermost
|
2
|
+
module Extractors
|
3
|
+
class NewIssue < Base
|
4
|
+
MESSAGE = "[%{project_link}] %{author} created %{object_link}%{mentions}"
|
5
|
+
|
6
|
+
def from_context(context)
|
7
|
+
issue = context.fetch(:issue)
|
8
|
+
return if issue.is_private?
|
9
|
+
return unless channel = determine_channel(issue.project)
|
10
|
+
return unless url = determine_url(issue.project)
|
11
|
+
|
12
|
+
args = {
|
13
|
+
project_link: link(issue.project, event_url(issue.project)),
|
14
|
+
author: h(issue.author),
|
15
|
+
object_link: link(issue, event_url(issue)),
|
16
|
+
mentions: extract_mentions(issue.description)
|
17
|
+
}
|
18
|
+
|
19
|
+
msg = MessageBuilder.new(MESSAGE % args)
|
20
|
+
msg.channel(channel)
|
21
|
+
msg.icon(determine_icon(issue.project))
|
22
|
+
msg.username(determine_username(issue.project))
|
23
|
+
attachment = msg.attachment
|
24
|
+
attachment.text(to_markdown issue.description) if issue.description
|
25
|
+
add_field(attachment, "field_status", issue.status)
|
26
|
+
add_field(attachment, "field_priority", issue.priority)
|
27
|
+
add_field(attachment, "field_assigned_to", issue.assigned_to)
|
28
|
+
|
29
|
+
if show_watchers? && !issue.watcher_users.empty?
|
30
|
+
add_field(attachment, "field_watcher", issue.watcher_users.join(", "))
|
31
|
+
end
|
32
|
+
|
33
|
+
{ url: url, message: msg.to_hash }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def add_field(attachment, key, value)
|
39
|
+
value = "-" if value.nil? || value.to_s.empty?
|
40
|
+
attachment.field(t(key), h(value), true)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RedmineMattermost
|
2
|
+
module Extractors
|
3
|
+
class UpdatedIssue < Base
|
4
|
+
MESSAGE = "[%{project_link}] %{editor} updated %{object_link}%{mentions}"
|
5
|
+
|
6
|
+
def from_context(context)
|
7
|
+
issue = context.fetch(:issue)
|
8
|
+
journal = context.fetch(:journal)
|
9
|
+
return unless notify?
|
10
|
+
return if issue.is_private?
|
11
|
+
return unless channel = determine_channel(issue.project)
|
12
|
+
return unless url = determine_url(issue.project)
|
13
|
+
|
14
|
+
args = {
|
15
|
+
project_link: link(issue.project, event_url(issue.project)),
|
16
|
+
editor: h(journal.user),
|
17
|
+
object_link: link(issue, event_url(issue)),
|
18
|
+
mentions: extract_mentions(journal.notes)
|
19
|
+
}
|
20
|
+
|
21
|
+
msg = MessageBuilder.new(MESSAGE % args)
|
22
|
+
msg.channel(channel)
|
23
|
+
msg.icon(determine_icon(issue.project))
|
24
|
+
msg.username(determine_username(issue.project))
|
25
|
+
attachment = msg.attachment
|
26
|
+
attachment.text(to_markdown journal.notes) if journal.notes
|
27
|
+
mapper = Utils::JournalMapper.new(bridge)
|
28
|
+
mapper.to_fields(journal).map do |field|
|
29
|
+
attachment.field(*field)
|
30
|
+
end
|
31
|
+
|
32
|
+
{ url: url, message: msg.to_hash }
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def notify?
|
38
|
+
settings.plugin_redmine_mattermost[:post_updates] == "1"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,33 +1,33 @@
|
|
1
1
|
module RedmineMattermost
|
2
2
|
module IssuePatch
|
3
|
-
def self.included(base)
|
4
|
-
base.extend(ClassMethods)
|
3
|
+
def self.included(base)
|
5
4
|
base.send(:include, InstanceMethods)
|
6
5
|
|
7
6
|
base.class_eval do
|
8
|
-
unloadable
|
7
|
+
unloadable
|
9
8
|
after_create :create_from_issue
|
10
9
|
after_save :save_from_issue
|
11
10
|
end
|
12
11
|
end
|
13
|
-
|
14
|
-
module ClassMethods
|
15
|
-
end
|
16
|
-
|
12
|
+
|
17
13
|
module InstanceMethods
|
18
14
|
def create_from_issue
|
19
15
|
@create_already_fired = true
|
20
|
-
Redmine::Hook.call_hook(:redmine_mattermost_issues_new_after_save, {
|
21
|
-
|
16
|
+
Redmine::Hook.call_hook(:redmine_mattermost_issues_new_after_save, {
|
17
|
+
issue: self
|
18
|
+
})
|
19
|
+
true
|
22
20
|
end
|
23
21
|
|
24
22
|
def save_from_issue
|
25
|
-
|
26
|
-
Redmine::Hook.call_hook(:redmine_mattermost_issues_edit_after_save, {
|
23
|
+
unless @create_already_fired || current_journal.nil?
|
24
|
+
Redmine::Hook.call_hook(:redmine_mattermost_issues_edit_after_save, {
|
25
|
+
issue: self,
|
26
|
+
journal: self.current_journal
|
27
|
+
})
|
27
28
|
end
|
28
|
-
|
29
|
+
true
|
29
30
|
end
|
30
|
-
|
31
|
-
end
|
31
|
+
end
|
32
32
|
end
|
33
33
|
end
|
@@ -1,269 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def redmine_mattermost_issues_edit_after_save(context={})
|
41
|
-
issue = context[:issue]
|
42
|
-
journal = context[:journal]
|
43
|
-
|
44
|
-
channel = channel_for_project issue.project
|
45
|
-
url = url_for_project issue.project
|
46
|
-
|
47
|
-
return unless channel and url and Setting.plugin_redmine_mattermost[:post_updates] == '1'
|
48
|
-
return if issue.is_private?
|
49
|
-
|
50
|
-
msg = "[#{escape issue.project}] #{escape journal.user.to_s} updated <#{object_url issue}|#{escape issue}>#{mentions journal.notes}"
|
51
|
-
|
52
|
-
attachment = {}
|
53
|
-
attachment[:text] = escape journal.notes if journal.notes
|
54
|
-
attachment[:fields] = journal.details.map { |d| detail_to_field d }
|
55
|
-
|
56
|
-
speak msg, channel, attachment, url
|
57
|
-
end
|
58
|
-
|
59
|
-
def model_changeset_scan_commit_for_issue_ids_pre_issue_update(context={})
|
60
|
-
issue = context[:issue]
|
61
|
-
journal = issue.current_journal
|
62
|
-
changeset = context[:changeset]
|
63
|
-
|
64
|
-
channel = channel_for_project issue.project
|
65
|
-
url = url_for_project issue.project
|
66
|
-
|
67
|
-
return unless channel and url and issue.save
|
68
|
-
return if issue.is_private?
|
69
|
-
|
70
|
-
msg = "[#{escape issue.project}] #{escape journal.user.to_s} updated <#{object_url issue}|#{escape issue}>"
|
71
|
-
|
72
|
-
repository = changeset.repository
|
73
|
-
|
74
|
-
if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
|
75
|
-
host, port, prefix = $2, $4, $5
|
76
|
-
revision_url = Rails.application.routes.url_for(
|
77
|
-
:controller => 'repositories',
|
78
|
-
:action => 'revision',
|
79
|
-
:id => repository.project,
|
80
|
-
:repository_id => repository.identifier_param,
|
81
|
-
:rev => changeset.revision,
|
82
|
-
:host => host,
|
83
|
-
:protocol => Setting.protocol,
|
84
|
-
:port => port,
|
85
|
-
:script_name => prefix
|
86
|
-
)
|
87
|
-
else
|
88
|
-
revision_url = Rails.application.routes.url_for(
|
89
|
-
:controller => 'repositories',
|
90
|
-
:action => 'revision',
|
91
|
-
:id => repository.project,
|
92
|
-
:repository_id => repository.identifier_param,
|
93
|
-
:rev => changeset.revision,
|
94
|
-
:host => Setting.host_name,
|
95
|
-
:protocol => Setting.protocol
|
96
|
-
)
|
97
|
-
end
|
98
|
-
|
99
|
-
attachment = {}
|
100
|
-
attachment[:text] = ll(Setting.default_language, :text_status_changed_by_changeset, "<#{revision_url}|#{escape changeset.comments}>")
|
101
|
-
attachment[:fields] = journal.details.map { |d| detail_to_field d }
|
102
|
-
|
103
|
-
speak msg, channel, attachment, url
|
104
|
-
end
|
105
|
-
|
106
|
-
def controller_wiki_edit_after_save(context = { })
|
107
|
-
return unless Setting.plugin_redmine_mattermost[:post_wiki_updates] == '1'
|
108
|
-
|
109
|
-
project = context[:project]
|
110
|
-
page = context[:page]
|
111
|
-
|
112
|
-
user = page.content.author
|
113
|
-
project_url = "<#{object_url project}|#{escape project}>"
|
114
|
-
page_url = "<#{object_url page}|#{page.title}>"
|
115
|
-
comment = "[#{project_url}] #{page_url} updated by *#{user}*"
|
116
|
-
|
117
|
-
channel = channel_for_project project
|
118
|
-
url = url_for_project project
|
119
|
-
|
120
|
-
attachment = nil
|
121
|
-
if not page.content.comments.empty?
|
122
|
-
attachment = {}
|
123
|
-
attachment[:text] = "#{escape page.content.comments}"
|
124
|
-
end
|
125
|
-
|
126
|
-
speak comment, channel, attachment, url
|
127
|
-
end
|
128
|
-
|
129
|
-
def speak(msg, channel, attachment=nil, url=nil)
|
130
|
-
url = Setting.plugin_redmine_mattermost[:mattermost_url] if not url
|
131
|
-
username = Setting.plugin_redmine_mattermost[:username]
|
132
|
-
icon = Setting.plugin_redmine_mattermost[:icon]
|
133
|
-
|
134
|
-
params = {
|
135
|
-
:text => msg,
|
136
|
-
:link_names => 1,
|
137
|
-
}
|
138
|
-
|
139
|
-
params[:username] = username if username
|
140
|
-
params[:channel] = channel if channel
|
141
|
-
|
142
|
-
params[:attachments] = [attachment] if attachment
|
143
|
-
|
144
|
-
if icon and not icon.empty?
|
145
|
-
if icon.start_with? ':'
|
146
|
-
params[:icon_emoji] = icon
|
147
|
-
else
|
148
|
-
params[:icon_url] = icon
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
begin
|
153
|
-
client = HTTPClient.new
|
154
|
-
client.ssl_config.cert_store.set_default_paths
|
155
|
-
client.ssl_config.ssl_version = "SSLv23"
|
156
|
-
client.post_async url, {:payload => params.to_json}
|
157
|
-
rescue
|
158
|
-
# Bury exception if connection error
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
private
|
163
|
-
def escape(msg)
|
164
|
-
msg.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">")
|
165
|
-
end
|
166
|
-
|
167
|
-
def object_url(obj)
|
168
|
-
if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
|
169
|
-
host, port, prefix = $2, $4, $5
|
170
|
-
Rails.application.routes.url_for(obj.event_url({:host => host, :protocol => Setting.protocol, :port => port, :script_name => prefix}))
|
171
|
-
else
|
172
|
-
Rails.application.routes.url_for(obj.event_url({:host => Setting.host_name, :protocol => Setting.protocol}))
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def url_for_project(proj)
|
177
|
-
return nil if proj.blank?
|
178
|
-
|
179
|
-
cf = ProjectCustomField.find_by_name("Mattermost URL")
|
180
|
-
|
181
|
-
return [
|
182
|
-
(proj.custom_value_for(cf).value rescue nil),
|
183
|
-
(url_for_project proj.parent),
|
184
|
-
Setting.plugin_redmine_mattermost[:mattermost_url],
|
185
|
-
].find{|v| v.present?}
|
186
|
-
end
|
187
|
-
|
188
|
-
def channel_for_project(proj)
|
189
|
-
return nil if proj.blank?
|
190
|
-
|
191
|
-
cf = ProjectCustomField.find_by_name("Mattermost Channel")
|
192
|
-
|
193
|
-
val = [
|
194
|
-
(proj.custom_value_for(cf).value rescue nil),
|
195
|
-
(channel_for_project proj.parent),
|
196
|
-
Setting.plugin_redmine_mattermost[:channel],
|
197
|
-
].find{|v| v.present?}
|
198
|
-
|
199
|
-
# Channel name '-' is reserved for NOT notifying
|
200
|
-
return nil if val.to_s == '-'
|
201
|
-
val
|
202
|
-
end
|
203
|
-
|
204
|
-
def detail_to_field(detail)
|
205
|
-
if detail.property == "cf"
|
206
|
-
key = CustomField.find(detail.prop_key).name rescue nil
|
207
|
-
title = key
|
208
|
-
elsif detail.property == "attachment"
|
209
|
-
key = "attachment"
|
210
|
-
title = I18n.t :label_attachment
|
211
|
-
else
|
212
|
-
key = detail.prop_key.to_s.sub("_id", "")
|
213
|
-
title = I18n.t "field_#{key}"
|
214
|
-
end
|
215
|
-
|
216
|
-
short = true
|
217
|
-
value = escape detail.value.to_s
|
218
|
-
|
219
|
-
case key
|
220
|
-
when "title", "subject", "description"
|
221
|
-
short = false
|
222
|
-
when "tracker"
|
223
|
-
tracker = Tracker.find(detail.value) rescue nil
|
224
|
-
value = escape tracker.to_s
|
225
|
-
when "project"
|
226
|
-
project = Project.find(detail.value) rescue nil
|
227
|
-
value = escape project.to_s
|
228
|
-
when "status"
|
229
|
-
status = IssueStatus.find(detail.value) rescue nil
|
230
|
-
value = escape status.to_s
|
231
|
-
when "priority"
|
232
|
-
priority = IssuePriority.find(detail.value) rescue nil
|
233
|
-
value = escape priority.to_s
|
234
|
-
when "category"
|
235
|
-
category = IssueCategory.find(detail.value) rescue nil
|
236
|
-
value = escape category.to_s
|
237
|
-
when "assigned_to"
|
238
|
-
user = User.find(detail.value) rescue nil
|
239
|
-
value = escape user.to_s
|
240
|
-
when "fixed_version"
|
241
|
-
version = Version.find(detail.value) rescue nil
|
242
|
-
value = escape version.to_s
|
243
|
-
when "attachment"
|
244
|
-
attachment = Attachment.find(detail.prop_key) rescue nil
|
245
|
-
value = "<#{object_url attachment}|#{escape attachment.filename}>" if attachment
|
246
|
-
when "parent"
|
247
|
-
issue = Issue.find(detail.value) rescue nil
|
248
|
-
value = "<#{object_url issue}|#{escape issue}>" if issue
|
249
|
-
end
|
250
|
-
|
251
|
-
value = "-" if value.empty?
|
252
|
-
|
253
|
-
result = { :title => title, :value => value }
|
254
|
-
result[:short] = true if short
|
255
|
-
result
|
256
|
-
end
|
257
|
-
|
258
|
-
def mentions text
|
259
|
-
return nil if text.nil?
|
260
|
-
names = extract_usernames text
|
261
|
-
names.present? ? "\nTo: " + names.join(', ') : nil
|
262
|
-
end
|
263
|
-
|
264
|
-
def extract_usernames text = ''
|
265
|
-
# mattermost usernames may only contain lowercase letters, numbers,
|
266
|
-
# dashes and underscores and must start with a letter or number.
|
267
|
-
text.scan(/@[a-z0-9][a-z0-9_\-]*/).uniq
|
268
|
-
end
|
1
|
+
module RedmineMattermost
|
2
|
+
class MattermostListener < Redmine::Hook::Listener
|
3
|
+
def initialize
|
4
|
+
@bridge = Bridge.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def redmine_mattermost_issues_new_after_save(context)
|
8
|
+
process Extractors::NewIssue, context
|
9
|
+
end
|
10
|
+
|
11
|
+
def redmine_mattermost_issues_edit_after_save(context)
|
12
|
+
process Extractors::UpdatedIssue, context
|
13
|
+
end
|
14
|
+
|
15
|
+
def model_changeset_scan_commit_for_issue_ids_pre_issue_update(context)
|
16
|
+
process Extractors::AppliedChangeset, context
|
17
|
+
end
|
18
|
+
|
19
|
+
def controller_wiki_edit_after_save(context)
|
20
|
+
process Extractors::ChangedWikiPage, context
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def process(extractor_class, context)
|
26
|
+
notify(extractor_class.new(@bridge).from_context(context))
|
27
|
+
end
|
28
|
+
|
29
|
+
def notify(options)
|
30
|
+
return unless options
|
31
|
+
url = options.fetch(:url)
|
32
|
+
message = options.fetch(:message)
|
33
|
+
|
34
|
+
Client.new(url).speak(message)
|
35
|
+
rescue
|
36
|
+
# Bury exception for connection errors or other types
|
37
|
+
end
|
38
|
+
end
|
269
39
|
end
|