##// END OF EJS Templates
slack: updated slack integration to use the attachements for nicer formatting.
marcink -
r1467:bfe33223 default
parent child
Show More
@@ -128,6 +128,9 class PullRequestCommentEvent(PullReques
128 'comment': {
128 'comment': {
129 'status': status,
129 'status': status,
130 'text': self.comment.text,
130 'text': self.comment.text,
131 'type': self.comment.comment_type,
132 'file': self.comment.f_path,
133 'line': self.comment.line_no,
131 'url': CommentsModel().get_url(self.comment)
134 'url': CommentsModel().get_url(self.comment)
132 }
135 }
133 })
136 })
@@ -34,10 +34,9 def _commits_as_dict(commit_ids, repos):
34 :param repos: list of repos to check
34 :param repos: list of repos to check
35 """
35 """
36 from rhodecode.lib.utils2 import extract_mentioned_users
36 from rhodecode.lib.utils2 import extract_mentioned_users
37 from rhodecode.model.db import Repository
38 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
39 from rhodecode.lib.helpers import process_patterns
38 from rhodecode.lib.helpers import (
40 from rhodecode.lib.helpers import urlify_commit_message
39 urlify_commit_message, process_patterns, chop_at_smart)
41
40
42 if not repos:
41 if not repos:
43 raise Exception('no repo defined')
42 raise Exception('no repo defined')
@@ -78,14 +77,15 def _commits_as_dict(commit_ids, repos):
78 cs_data['issues'] = issues_data
77 cs_data['issues'] = issues_data
79 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
78 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
80 repo.repo_name)
79 repo.repo_name)
80 cs_data['message_html_title'] = chop_at_smart(cs_data['message'], '\n', suffix_if_chopped='...')
81 commits.append(cs_data)
81 commits.append(cs_data)
82
82
83 needed_commits.remove(commit_id)
83 needed_commits.remove(commit_id)
84
84
85 except Exception as e:
85 except Exception as e:
86 log.exception(e)
86 log.exception(e)
87 # we don't send any commits when crash happens, only full list matters
87 # we don't send any commits when crash happens, only full list
88 # we short circuit then.
88 # matters we short circuit then.
89 return []
89 return []
90
90
91 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
91 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
@@ -19,12 +19,14
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22 import re
23 import time
24 import textwrap
25 import logging
26
22 import deform
27 import deform
23 import re
24 import logging
25 import requests
28 import requests
26 import colander
29 import colander
27 import textwrap
28 from celery.task import task
30 from celery.task import task
29 from mako.template import Template
31 from mako.template import Template
30
32
@@ -85,17 +87,6 class SlackSettingsSchema(colander.Schem
85 )
87 )
86
88
87
89
88 repo_push_template = Template(r'''
89 *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>:
90 %for branch, branch_commits in branches_commits.items():
91 branch: <${branch_commits['branch']['url']}|${branch_commits['branch']['name']}>
92 %for commit in branch_commits['commits']:
93 > <${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
94 %endfor
95 %endfor
96 ''')
97
98
99 class SlackIntegrationType(IntegrationTypeBase):
90 class SlackIntegrationType(IntegrationTypeBase):
100 key = 'slack'
91 key = 'slack'
101 display_name = _('Slack')
92 display_name = _('Slack')
@@ -124,25 +115,31 class SlackIntegrationType(IntegrationTy
124
115
125 data = event.as_dict()
116 data = event.as_dict()
126
117
118 # defaults
119 title = '*%s* caused a *%s* event' % (
120 data['actor']['username'], event.name)
127 text = '*%s* caused a *%s* event' % (
121 text = '*%s* caused a *%s* event' % (
128 data['actor']['username'], event.name)
122 data['actor']['username'], event.name)
123 fields = None
124 overrides = None
129
125
130 log.debug('handling slack event for %s' % event.name)
126 log.debug('handling slack event for %s' % event.name)
131
127
132 if isinstance(event, events.PullRequestCommentEvent):
128 if isinstance(event, events.PullRequestCommentEvent):
133 text = self.format_pull_request_comment_event(event, data)
129 (title, text, fields, overrides) \
130 = self.format_pull_request_comment_event(event, data)
134 elif isinstance(event, events.PullRequestReviewEvent):
131 elif isinstance(event, events.PullRequestReviewEvent):
135 text = self.format_pull_request_review_event(event, data)
132 title, text = self.format_pull_request_review_event(event, data)
136 elif isinstance(event, events.PullRequestEvent):
133 elif isinstance(event, events.PullRequestEvent):
137 text = self.format_pull_request_event(event, data)
134 title, text = self.format_pull_request_event(event, data)
138 elif isinstance(event, events.RepoPushEvent):
135 elif isinstance(event, events.RepoPushEvent):
139 text = self.format_repo_push_event(data)
136 title, text = self.format_repo_push_event(data)
140 elif isinstance(event, events.RepoCreateEvent):
137 elif isinstance(event, events.RepoCreateEvent):
141 text = self.format_repo_create_event(data)
138 title, text = self.format_repo_create_event(data)
142 else:
139 else:
143 log.error('unhandled event type: %r' % event)
140 log.error('unhandled event type: %r' % event)
144
141
145 run_task(post_text_to_slack, self.settings, text)
142 run_task(post_text_to_slack, self.settings, title, text, fields, overrides)
146
143
147 def settings_schema(self):
144 def settings_schema(self):
148 schema = SlackSettingsSchema()
145 schema = SlackSettingsSchema()
@@ -167,37 +164,60 class SlackIntegrationType(IntegrationTy
167 comment_url=data['comment']['url'],
164 comment_url=data['comment']['url'],
168 )
165 )
169
166
170 comment_status = ''
167 fields = None
168 overrides = None
169 status_text = None
170
171 if data['comment']['status']:
171 if data['comment']['status']:
172 comment_status = '[{}]: '.format(data['comment']['status'])
172 status_color = {
173 'approved': '#0ac878',
174 'rejected': '#e85e4d'}.get(data['comment']['status'])
175
176 if status_color:
177 overrides = {"color": status_color}
178
179 status_text = data['comment']['status']
180
181 if data['comment']['file']:
182 fields = [
183 {
184 "title": "file",
185 "value": data['comment']['file']
186 },
187 {
188 "title": "line",
189 "value": data['comment']['line']
190 }
191 ]
173
192
174 return (textwrap.dedent(
193 title = Template(textwrap.dedent(r'''
175 '''
194 *${data['actor']['username']}* left ${data['comment']['type']} on pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
176 *{user}* commented on pull request <{pr_url}|#{number}> - {pr_title}:
195 ''')).render(data=data, comment=event.comment)
177 >>> {comment_status}{comment_text}
196
178 ''').format(
197 text = Template(textwrap.dedent(r'''
179 comment_status=comment_status,
198 *pull request title*: ${pr_title}
180 user=data['actor']['username'],
199 % if status_text:
181 number=data['pullrequest']['pull_request_id'],
200 *submitted status*: `${status_text}`
182 pr_url=data['pullrequest']['url'],
201 % endif
183 pr_status=data['pullrequest']['status'],
202 >>> ${comment_text}
184 pr_title=data['pullrequest']['title'],
203 ''')).render(comment_text=comment_text,
185 comment_text=comment_text
204 pr_title=data['pullrequest']['title'],
186 )
205 status_text=status_text)
206
207 return title, text, fields, overrides
208
209 def format_pull_request_review_event(self, event, data):
210 title = Template(textwrap.dedent(r'''
211 *${data['actor']['username']}* changed status of pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']} to `${data['pullrequest']['status']}`>:
212 ''')).render(data=data)
213
214 text = Template(textwrap.dedent(r'''
215 *pull request title*: ${pr_title}
216 ''')).render(
217 pr_title=data['pullrequest']['title'],
187 )
218 )
188
219
189 def format_pull_request_review_event(self, event, data):
220 return title, text
190 return (textwrap.dedent(
191 '''
192 Status changed to {pr_status} for pull request <{pr_url}|#{number}> - {pr_title}
193 ''').format(
194 user=data['actor']['username'],
195 number=data['pullrequest']['pull_request_id'],
196 pr_url=data['pullrequest']['url'],
197 pr_status=data['pullrequest']['status'],
198 pr_title=data['pullrequest']['title'],
199 )
200 )
201
221
202 def format_pull_request_event(self, event, data):
222 def format_pull_request_event(self, event, data):
203 action = {
223 action = {
@@ -207,15 +227,22 class SlackIntegrationType(IntegrationTy
207 events.PullRequestCreateEvent: 'created',
227 events.PullRequestCreateEvent: 'created',
208 }.get(event.__class__, str(event.__class__))
228 }.get(event.__class__, str(event.__class__))
209
229
210 return ('Pull request <{url}|#{number}> - {title} '
230 title = Template(textwrap.dedent(r'''
211 '`{action}` by *{user}*').format(
231 *${data['actor']['username']}* `${action}` pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
212 user=data['actor']['username'],
232 ''')).render(data=data, action=action)
213 number=data['pullrequest']['pull_request_id'],
233
214 url=data['pullrequest']['url'],
234 text = Template(textwrap.dedent(r'''
215 title=data['pullrequest']['title'],
235 *pull request title*: ${pr_title}
216 action=action
236 %if data['pullrequest']['commits']:
237 *commits*: ${len(data['pullrequest']['commits'])}
238 %endif
239 ''')).render(
240 pr_title=data['pullrequest']['title'],
241 data=data
217 )
242 )
218
243
244 return title, text
245
219 def format_repo_push_event(self, data):
246 def format_repo_push_event(self, data):
220 branch_data = {branch['name']: branch
247 branch_data = {branch['name']: branch
221 for branch in data['push']['branches']}
248 for branch in data['push']['branches']}
@@ -230,20 +257,38 class SlackIntegrationType(IntegrationTy
230 branch_commits = branches_commits[commit['branch']]
257 branch_commits = branches_commits[commit['branch']]
231 branch_commits['commits'].append(commit)
258 branch_commits['commits'].append(commit)
232
259
233 result = repo_push_template.render(
260 title = Template(r'''
261 *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>:
262 ''').render(data=data)
263
264 repo_push_template = Template(textwrap.dedent(r'''
265 %for branch, branch_commits in branches_commits.items():
266 branch: <${branch_commits['branch']['url']}|${branch_commits['branch']['name']}>
267 %for commit in branch_commits['commits']:
268 `<${commit['url']}|${commit['short_id']}>` - ${commit['message_html_title']|html_to_slack_links}
269 %endfor
270 %endfor
271 '''))
272
273 text = repo_push_template.render(
234 data=data,
274 data=data,
235 branches_commits=branches_commits,
275 branches_commits=branches_commits,
236 html_to_slack_links=html_to_slack_links,
276 html_to_slack_links=html_to_slack_links,
237 )
277 )
238 return result
278
279 return title, text
239
280
240 def format_repo_create_event(self, data):
281 def format_repo_create_event(self, data):
241 return '<{}|{}> ({}) repository created by *{}*'.format(
282 title = Template(r'''
242 data['repo']['url'],
283 *${data['actor']['username']}* created new repository ${data['repo']['repo_name']}:
243 data['repo']['repo_name'],
284 ''').render(data=data)
244 data['repo']['repo_type'],
285
245 data['actor']['username'],
286 text = Template(textwrap.dedent(r'''
246 )
287 repo_url: ${data['repo']['url']}
288 repo_type: ${data['repo']['repo_type']}
289 ''')).render(data=data)
290
291 return title, text
247
292
248
293
249 def html_to_slack_links(message):
294 def html_to_slack_links(message):
@@ -252,12 +297,38 def html_to_slack_links(message):
252
297
253
298
254 @task(ignore_result=True)
299 @task(ignore_result=True)
255 def post_text_to_slack(settings, text):
300 def post_text_to_slack(settings, title, text, fields=None, overrides=None):
256 log.debug('sending %s to slack %s' % (text, settings['service']))
301 log.debug('sending %s (%s) to slack %s' % (
257 resp = requests.post(settings['service'], json={
302 title, text, settings['service']))
303
304 fields = fields or []
305 overrides = overrides or {}
306
307 message_data = {
308 "fallback": text,
309 "color": "#427cc9",
310 "pretext": title,
311 #"author_name": "Bobby Tables",
312 #"author_link": "http://flickr.com/bobby/",
313 #"author_icon": "http://flickr.com/icons/bobby.jpg",
314 #"title": "Slack API Documentation",
315 #"title_link": "https://api.slack.com/",
316 "text": text,
317 "fields": fields,
318 #"image_url": "http://my-website.com/path/to/image.jpg",
319 #"thumb_url": "http://example.com/path/to/thumb.png",
320 "footer": "RhodeCode",
321 #"footer_icon": "",
322 "ts": time.time(),
323 "mrkdwn_in": ["pretext", "text"]
324 }
325 message_data.update(overrides)
326 json_message = {
327 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:'),
258 "channel": settings.get('channel', ''),
328 "channel": settings.get('channel', ''),
259 "username": settings.get('username', 'Rhodecode'),
329 "username": settings.get('username', 'Rhodecode'),
260 "text": text,
330 "attachments": [message_data]
261 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
331 }
262 })
332
333 resp = requests.post(settings['service'], json=json_message)
263 resp.raise_for_status() # raise exception on a failed request
334 resp.raise_for_status() # raise exception on a failed request
General Comments 0
You need to be logged in to leave comments. Login now