redmine-mattermost 0.3 → 0.4.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +15 -0
- data/LICENSE +1 -0
- data/README.md +30 -17
- data/Rakefile +9 -0
- data/app/views/settings/_mattermost_settings.html.erb +60 -20
- data/lib/redmine-mattermost.rb +9 -1
- data/lib/redmine_mattermost/bridge.rb +63 -0
- data/lib/redmine_mattermost/client.rb +28 -0
- data/lib/redmine_mattermost/extractors.rb +88 -0
- data/lib/redmine_mattermost/extractors/applied_changeset.rb +55 -0
- data/lib/redmine_mattermost/extractors/changed_wiki_page.rb +39 -0
- data/lib/redmine_mattermost/extractors/new_issue.rb +44 -0
- data/lib/redmine_mattermost/extractors/updated_issue.rb +42 -0
- data/lib/redmine_mattermost/infos.rb +1 -2
- data/lib/redmine_mattermost/issue_patch.rb +14 -14
- data/lib/redmine_mattermost/listener.rb +38 -268
- data/lib/redmine_mattermost/message_builder.rb +61 -0
- data/lib/redmine_mattermost/redmine_plugin.rb +3 -4
- data/lib/redmine_mattermost/utils.rb +3 -0
- data/lib/redmine_mattermost/utils/journal_mapper.rb +75 -0
- data/lib/redmine_mattermost/utils/text.rb +37 -0
- data/lib/redmine_mattermost/utils/urls.rb +20 -0
- data/lib/redmine_mattermost/version.rb +1 -1
- data/redmine-mattermost.gemspec +4 -1
- data/spec/client_spec.rb +29 -0
- data/spec/extractors/applied_changeset_spec.rb +82 -0
- data/spec/extractors/changed_wiki_page_spec.rb +63 -0
- data/spec/extractors/new_issue_spec.rb +209 -0
- data/spec/extractors/updated_issue_spec.rb +169 -0
- data/spec/message_builder_spec.rb +71 -0
- data/spec/redmine_mattermost_spec.rb +9 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/mock_support.rb +113 -0
- data/spec/utils/text_spec.rb +142 -0
- data/spec/utils/text_spec_alt.rb +142 -0
- metadata +87 -9
@@ -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
|