##// END OF EJS Templates
slack: added a case for SVN commits to trunk
marcink -
r2647:b00c6b3e default
parent child Browse files
Show More
@@ -1,342 +1,350 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
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
22 import re
23 import time
23 import time
24 import textwrap
24 import textwrap
25 import logging
25 import logging
26
26
27 import deform
27 import deform
28 import requests
28 import requests
29 import colander
29 import colander
30 from mako.template import Template
30 from mako.template import Template
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.translation import _
33 from rhodecode.translation import _
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
35 from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
36 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.lib.colander_utils import strip_whitespace
37 from rhodecode.integrations.types.base import (
37 from rhodecode.integrations.types.base import (
38 IntegrationTypeBase, CommitParsingDataHandler, render_with_traceback)
38 IntegrationTypeBase, CommitParsingDataHandler, render_with_traceback)
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class SlackSettingsSchema(colander.Schema):
43 class SlackSettingsSchema(colander.Schema):
44 service = colander.SchemaNode(
44 service = colander.SchemaNode(
45 colander.String(),
45 colander.String(),
46 title=_('Slack service URL'),
46 title=_('Slack service URL'),
47 description=h.literal(_(
47 description=h.literal(_(
48 'This can be setup at the '
48 'This can be setup at the '
49 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
49 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
50 'slack app manager</a>')),
50 'slack app manager</a>')),
51 default='',
51 default='',
52 preparer=strip_whitespace,
52 preparer=strip_whitespace,
53 validator=colander.url,
53 validator=colander.url,
54 widget=deform.widget.TextInputWidget(
54 widget=deform.widget.TextInputWidget(
55 placeholder='https://hooks.slack.com/services/...',
55 placeholder='https://hooks.slack.com/services/...',
56 ),
56 ),
57 )
57 )
58 username = colander.SchemaNode(
58 username = colander.SchemaNode(
59 colander.String(),
59 colander.String(),
60 title=_('Username'),
60 title=_('Username'),
61 description=_('Username to show notifications coming from.'),
61 description=_('Username to show notifications coming from.'),
62 missing='Rhodecode',
62 missing='Rhodecode',
63 preparer=strip_whitespace,
63 preparer=strip_whitespace,
64 widget=deform.widget.TextInputWidget(
64 widget=deform.widget.TextInputWidget(
65 placeholder='Rhodecode'
65 placeholder='Rhodecode'
66 ),
66 ),
67 )
67 )
68 channel = colander.SchemaNode(
68 channel = colander.SchemaNode(
69 colander.String(),
69 colander.String(),
70 title=_('Channel'),
70 title=_('Channel'),
71 description=_('Channel to send notifications to.'),
71 description=_('Channel to send notifications to.'),
72 missing='',
72 missing='',
73 preparer=strip_whitespace,
73 preparer=strip_whitespace,
74 widget=deform.widget.TextInputWidget(
74 widget=deform.widget.TextInputWidget(
75 placeholder='#general'
75 placeholder='#general'
76 ),
76 ),
77 )
77 )
78 icon_emoji = colander.SchemaNode(
78 icon_emoji = colander.SchemaNode(
79 colander.String(),
79 colander.String(),
80 title=_('Emoji'),
80 title=_('Emoji'),
81 description=_('Emoji to use eg. :studio_microphone:'),
81 description=_('Emoji to use eg. :studio_microphone:'),
82 missing='',
82 missing='',
83 preparer=strip_whitespace,
83 preparer=strip_whitespace,
84 widget=deform.widget.TextInputWidget(
84 widget=deform.widget.TextInputWidget(
85 placeholder=':studio_microphone:'
85 placeholder=':studio_microphone:'
86 ),
86 ),
87 )
87 )
88
88
89
89
90 class SlackIntegrationType(IntegrationTypeBase, CommitParsingDataHandler):
90 class SlackIntegrationType(IntegrationTypeBase, CommitParsingDataHandler):
91 key = 'slack'
91 key = 'slack'
92 display_name = _('Slack')
92 display_name = _('Slack')
93 description = _('Send events such as repo pushes and pull requests to '
93 description = _('Send events such as repo pushes and pull requests to '
94 'your slack channel.')
94 'your slack channel.')
95
95
96 @classmethod
96 @classmethod
97 def icon(cls):
97 def icon(cls):
98 return '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"><g><path d="M165.963541,15.8384262 C162.07318,3.86308197 149.212328,-2.69009836 137.239082,1.20236066 C125.263738,5.09272131 118.710557,17.9535738 122.603016,29.9268197 L181.550164,211.292328 C185.597902,222.478689 197.682361,228.765377 209.282098,225.426885 C221.381246,221.943607 228.756984,209.093246 224.896,197.21023 C224.749115,196.756984 165.963541,15.8384262 165.963541,15.8384262" fill="#DFA22F"></path><path d="M74.6260984,45.515541 C70.7336393,33.5422951 57.8727869,26.9891148 45.899541,30.8794754 C33.9241967,34.7698361 27.3710164,47.6306885 31.2634754,59.6060328 L90.210623,240.971541 C94.2583607,252.157902 106.34282,258.44459 117.942557,255.104 C130.041705,251.62282 137.417443,238.772459 133.556459,226.887344 C133.409574,226.436197 74.6260984,45.515541 74.6260984,45.515541" fill="#3CB187"></path><path d="M240.161574,166.045377 C252.136918,162.155016 258.688,149.294164 254.797639,137.31882 C250.907279,125.345574 238.046426,118.792393 226.07318,122.682754 L44.7076721,181.632 C33.5213115,185.677639 27.234623,197.762098 30.5731148,209.361836 C34.0563934,221.460984 46.9067541,228.836721 58.7897705,224.975738 C59.2430164,224.828852 240.161574,166.045377 240.161574,166.045377" fill="#CE1E5B"></path><path d="M82.507541,217.270557 C94.312918,213.434754 109.528131,208.491016 125.855475,203.186361 C122.019672,191.380984 117.075934,176.163672 111.76918,159.83423 L68.4191475,173.924721 L82.507541,217.270557" fill="#392538"></path><path d="M173.847082,187.591344 C190.235279,182.267803 205.467279,177.31777 217.195016,173.507148 C213.359213,161.70177 208.413377,146.480262 203.106623,130.146623 L159.75659,144.237115 L173.847082,187.591344" fill="#BB242A"></path><path d="M210.484459,74.7058361 C222.457705,70.8154754 229.010885,57.954623 225.120525,45.9792787 C221.230164,34.0060328 208.369311,27.4528525 196.393967,31.3432131 L15.028459,90.292459 C3.84209836,94.3380984 -2.44459016,106.422557 0.896,118.022295 C4.37718033,130.121443 17.227541,137.49718 29.1126557,133.636197 C29.5638033,133.489311 210.484459,74.7058361 210.484459,74.7058361" fill="#72C5CD"></path><path d="M52.8220328,125.933115 C64.6274098,122.097311 79.8468197,117.151475 96.1762623,111.84682 C90.8527213,95.4565246 85.9026885,80.2245246 82.0920656,68.4946885 L38.731541,82.5872787 L52.8220328,125.933115" fill="#248C73"></path><path d="M144.159475,96.256 C160.551869,90.9303607 175.785967,85.9803279 187.515803,82.1676066 C182.190164,65.7752131 177.240131,50.5390164 173.42741,38.807082 L130.068984,52.8996721 L144.159475,96.256" fill="#62803A"></path></g></svg>'''
98 return '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"><g><path d="M165.963541,15.8384262 C162.07318,3.86308197 149.212328,-2.69009836 137.239082,1.20236066 C125.263738,5.09272131 118.710557,17.9535738 122.603016,29.9268197 L181.550164,211.292328 C185.597902,222.478689 197.682361,228.765377 209.282098,225.426885 C221.381246,221.943607 228.756984,209.093246 224.896,197.21023 C224.749115,196.756984 165.963541,15.8384262 165.963541,15.8384262" fill="#DFA22F"></path><path d="M74.6260984,45.515541 C70.7336393,33.5422951 57.8727869,26.9891148 45.899541,30.8794754 C33.9241967,34.7698361 27.3710164,47.6306885 31.2634754,59.6060328 L90.210623,240.971541 C94.2583607,252.157902 106.34282,258.44459 117.942557,255.104 C130.041705,251.62282 137.417443,238.772459 133.556459,226.887344 C133.409574,226.436197 74.6260984,45.515541 74.6260984,45.515541" fill="#3CB187"></path><path d="M240.161574,166.045377 C252.136918,162.155016 258.688,149.294164 254.797639,137.31882 C250.907279,125.345574 238.046426,118.792393 226.07318,122.682754 L44.7076721,181.632 C33.5213115,185.677639 27.234623,197.762098 30.5731148,209.361836 C34.0563934,221.460984 46.9067541,228.836721 58.7897705,224.975738 C59.2430164,224.828852 240.161574,166.045377 240.161574,166.045377" fill="#CE1E5B"></path><path d="M82.507541,217.270557 C94.312918,213.434754 109.528131,208.491016 125.855475,203.186361 C122.019672,191.380984 117.075934,176.163672 111.76918,159.83423 L68.4191475,173.924721 L82.507541,217.270557" fill="#392538"></path><path d="M173.847082,187.591344 C190.235279,182.267803 205.467279,177.31777 217.195016,173.507148 C213.359213,161.70177 208.413377,146.480262 203.106623,130.146623 L159.75659,144.237115 L173.847082,187.591344" fill="#BB242A"></path><path d="M210.484459,74.7058361 C222.457705,70.8154754 229.010885,57.954623 225.120525,45.9792787 C221.230164,34.0060328 208.369311,27.4528525 196.393967,31.3432131 L15.028459,90.292459 C3.84209836,94.3380984 -2.44459016,106.422557 0.896,118.022295 C4.37718033,130.121443 17.227541,137.49718 29.1126557,133.636197 C29.5638033,133.489311 210.484459,74.7058361 210.484459,74.7058361" fill="#72C5CD"></path><path d="M52.8220328,125.933115 C64.6274098,122.097311 79.8468197,117.151475 96.1762623,111.84682 C90.8527213,95.4565246 85.9026885,80.2245246 82.0920656,68.4946885 L38.731541,82.5872787 L52.8220328,125.933115" fill="#248C73"></path><path d="M144.159475,96.256 C160.551869,90.9303607 175.785967,85.9803279 187.515803,82.1676066 C182.190164,65.7752131 177.240131,50.5390164 173.42741,38.807082 L130.068984,52.8996721 L144.159475,96.256" fill="#62803A"></path></g></svg>'''
99
99
100 valid_events = [
100 valid_events = [
101 events.PullRequestCloseEvent,
101 events.PullRequestCloseEvent,
102 events.PullRequestMergeEvent,
102 events.PullRequestMergeEvent,
103 events.PullRequestUpdateEvent,
103 events.PullRequestUpdateEvent,
104 events.PullRequestCommentEvent,
104 events.PullRequestCommentEvent,
105 events.PullRequestReviewEvent,
105 events.PullRequestReviewEvent,
106 events.PullRequestCreateEvent,
106 events.PullRequestCreateEvent,
107 events.RepoPushEvent,
107 events.RepoPushEvent,
108 events.RepoCreateEvent,
108 events.RepoCreateEvent,
109 ]
109 ]
110
110
111 def send_event(self, event):
111 def send_event(self, event):
112 if event.__class__ not in self.valid_events:
112 if event.__class__ not in self.valid_events:
113 log.debug('event not valid: %r' % event)
113 log.debug('event not valid: %r' % event)
114 return
114 return
115
115
116 if event.name not in self.settings['events']:
116 if event.name not in self.settings['events']:
117 log.debug('event ignored: %r' % event)
117 log.debug('event ignored: %r' % event)
118 return
118 return
119
119
120 data = event.as_dict()
120 data = event.as_dict()
121
121
122 # defaults
122 # defaults
123 title = '*%s* caused a *%s* event' % (
123 title = '*%s* caused a *%s* event' % (
124 data['actor']['username'], event.name)
124 data['actor']['username'], event.name)
125 text = '*%s* caused a *%s* event' % (
125 text = '*%s* caused a *%s* event' % (
126 data['actor']['username'], event.name)
126 data['actor']['username'], event.name)
127 fields = None
127 fields = None
128 overrides = None
128 overrides = None
129
129
130 log.debug('handling slack event for %s' % event.name)
130 log.debug('handling slack event for %s' % event.name)
131
131
132 if isinstance(event, events.PullRequestCommentEvent):
132 if isinstance(event, events.PullRequestCommentEvent):
133 (title, text, fields, overrides) \
133 (title, text, fields, overrides) \
134 = self.format_pull_request_comment_event(event, data)
134 = self.format_pull_request_comment_event(event, data)
135 elif isinstance(event, events.PullRequestReviewEvent):
135 elif isinstance(event, events.PullRequestReviewEvent):
136 title, text = self.format_pull_request_review_event(event, data)
136 title, text = self.format_pull_request_review_event(event, data)
137 elif isinstance(event, events.PullRequestEvent):
137 elif isinstance(event, events.PullRequestEvent):
138 title, text = self.format_pull_request_event(event, data)
138 title, text = self.format_pull_request_event(event, data)
139 elif isinstance(event, events.RepoPushEvent):
139 elif isinstance(event, events.RepoPushEvent):
140 title, text = self.format_repo_push_event(data)
140 title, text = self.format_repo_push_event(data)
141 elif isinstance(event, events.RepoCreateEvent):
141 elif isinstance(event, events.RepoCreateEvent):
142 title, text = self.format_repo_create_event(data)
142 title, text = self.format_repo_create_event(data)
143 else:
143 else:
144 log.error('unhandled event type: %r' % event)
144 log.error('unhandled event type: %r' % event)
145
145
146 run_task(post_text_to_slack, self.settings, title, text, fields, overrides)
146 run_task(post_text_to_slack, self.settings, title, text, fields, overrides)
147
147
148 def settings_schema(self):
148 def settings_schema(self):
149 schema = SlackSettingsSchema()
149 schema = SlackSettingsSchema()
150 schema.add(colander.SchemaNode(
150 schema.add(colander.SchemaNode(
151 colander.Set(),
151 colander.Set(),
152 widget=deform.widget.CheckboxChoiceWidget(
152 widget=deform.widget.CheckboxChoiceWidget(
153 values=sorted(
153 values=sorted(
154 [(e.name, e.display_name) for e in self.valid_events]
154 [(e.name, e.display_name) for e in self.valid_events]
155 )
155 )
156 ),
156 ),
157 description="Events activated for this integration",
157 description="Events activated for this integration",
158 name='events'
158 name='events'
159 ))
159 ))
160
160
161 return schema
161 return schema
162
162
163 def format_pull_request_comment_event(self, event, data):
163 def format_pull_request_comment_event(self, event, data):
164 comment_text = data['comment']['text']
164 comment_text = data['comment']['text']
165 if len(comment_text) > 200:
165 if len(comment_text) > 200:
166 comment_text = '<{comment_url}|{comment_text}...>'.format(
166 comment_text = '<{comment_url}|{comment_text}...>'.format(
167 comment_text=comment_text[:200],
167 comment_text=comment_text[:200],
168 comment_url=data['comment']['url'],
168 comment_url=data['comment']['url'],
169 )
169 )
170
170
171 fields = None
171 fields = None
172 overrides = None
172 overrides = None
173 status_text = None
173 status_text = None
174
174
175 if data['comment']['status']:
175 if data['comment']['status']:
176 status_color = {
176 status_color = {
177 'approved': '#0ac878',
177 'approved': '#0ac878',
178 'rejected': '#e85e4d'}.get(data['comment']['status'])
178 'rejected': '#e85e4d'}.get(data['comment']['status'])
179
179
180 if status_color:
180 if status_color:
181 overrides = {"color": status_color}
181 overrides = {"color": status_color}
182
182
183 status_text = data['comment']['status']
183 status_text = data['comment']['status']
184
184
185 if data['comment']['file']:
185 if data['comment']['file']:
186 fields = [
186 fields = [
187 {
187 {
188 "title": "file",
188 "title": "file",
189 "value": data['comment']['file']
189 "value": data['comment']['file']
190 },
190 },
191 {
191 {
192 "title": "line",
192 "title": "line",
193 "value": data['comment']['line']
193 "value": data['comment']['line']
194 }
194 }
195 ]
195 ]
196
196
197 template = Template(textwrap.dedent(r'''
197 template = Template(textwrap.dedent(r'''
198 *${data['actor']['username']}* left ${data['comment']['type']} on pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
198 *${data['actor']['username']}* left ${data['comment']['type']} on pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
199 '''))
199 '''))
200 title = render_with_traceback(
200 title = render_with_traceback(
201 template, data=data, comment=event.comment)
201 template, data=data, comment=event.comment)
202
202
203 template = Template(textwrap.dedent(r'''
203 template = Template(textwrap.dedent(r'''
204 *pull request title*: ${pr_title}
204 *pull request title*: ${pr_title}
205 % if status_text:
205 % if status_text:
206 *submitted status*: `${status_text}`
206 *submitted status*: `${status_text}`
207 % endif
207 % endif
208 >>> ${comment_text}
208 >>> ${comment_text}
209 '''))
209 '''))
210 text = render_with_traceback(
210 text = render_with_traceback(
211 template,
211 template,
212 comment_text=comment_text,
212 comment_text=comment_text,
213 pr_title=data['pullrequest']['title'],
213 pr_title=data['pullrequest']['title'],
214 status_text=status_text)
214 status_text=status_text)
215
215
216 return title, text, fields, overrides
216 return title, text, fields, overrides
217
217
218 def format_pull_request_review_event(self, event, data):
218 def format_pull_request_review_event(self, event, data):
219 template = Template(textwrap.dedent(r'''
219 template = Template(textwrap.dedent(r'''
220 *${data['actor']['username']}* changed status of pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']} to `${data['pullrequest']['status']}`>:
220 *${data['actor']['username']}* changed status of pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']} to `${data['pullrequest']['status']}`>:
221 '''))
221 '''))
222 title = render_with_traceback(template, data=data)
222 title = render_with_traceback(template, data=data)
223
223
224 template = Template(textwrap.dedent(r'''
224 template = Template(textwrap.dedent(r'''
225 *pull request title*: ${pr_title}
225 *pull request title*: ${pr_title}
226 '''))
226 '''))
227 text = render_with_traceback(
227 text = render_with_traceback(
228 template,
228 template,
229 pr_title=data['pullrequest']['title'])
229 pr_title=data['pullrequest']['title'])
230
230
231 return title, text
231 return title, text
232
232
233 def format_pull_request_event(self, event, data):
233 def format_pull_request_event(self, event, data):
234 action = {
234 action = {
235 events.PullRequestCloseEvent: 'closed',
235 events.PullRequestCloseEvent: 'closed',
236 events.PullRequestMergeEvent: 'merged',
236 events.PullRequestMergeEvent: 'merged',
237 events.PullRequestUpdateEvent: 'updated',
237 events.PullRequestUpdateEvent: 'updated',
238 events.PullRequestCreateEvent: 'created',
238 events.PullRequestCreateEvent: 'created',
239 }.get(event.__class__, str(event.__class__))
239 }.get(event.__class__, str(event.__class__))
240
240
241 template = Template(textwrap.dedent(r'''
241 template = Template(textwrap.dedent(r'''
242 *${data['actor']['username']}* `${action}` pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
242 *${data['actor']['username']}* `${action}` pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
243 '''))
243 '''))
244 title = render_with_traceback(template, data=data, action=action)
244 title = render_with_traceback(template, data=data, action=action)
245
245
246 template = Template(textwrap.dedent(r'''
246 template = Template(textwrap.dedent(r'''
247 *pull request title*: ${pr_title}
247 *pull request title*: ${pr_title}
248 %if data['pullrequest']['commits']:
248 %if data['pullrequest']['commits']:
249 *commits*: ${len(data['pullrequest']['commits'])}
249 *commits*: ${len(data['pullrequest']['commits'])}
250 %endif
250 %endif
251 '''))
251 '''))
252 text = render_with_traceback(
252 text = render_with_traceback(
253 template,
253 template,
254 pr_title=data['pullrequest']['title'],
254 pr_title=data['pullrequest']['title'],
255 data=data)
255 data=data)
256
256
257 return title, text
257 return title, text
258
258
259 def format_repo_push_event(self, data):
259 def format_repo_push_event(self, data):
260
260
261 branches_commits = self.aggregate_branch_data(
261 branches_commits = self.aggregate_branch_data(
262 data['push']['branches'], data['push']['commits'])
262 data['push']['branches'], data['push']['commits'])
263
263
264 template = Template(r'''
264 template = Template(r'''
265 *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>:
265 *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>:
266 ''')
266 ''')
267 title = render_with_traceback(template, data=data)
267 title = render_with_traceback(template, data=data)
268
268
269 repo_push_template = Template(textwrap.dedent(r'''
269 repo_push_template = Template(textwrap.dedent(r'''
270 %for branch, branch_commits in branches_commits.items():
270 <%
271 ${len(branch_commits['commits'])} ${'commit' if len(branch_commits['commits']) == 1 else 'commits'} on branch: <${branch_commits['branch']['url']}|${branch_commits['branch']['name']}>
271 def branch_text(branch):
272 %for commit in branch_commits['commits']:
272 if branch:
273 return 'on branch: <{}|{}>'.format(branch_commits['branch']['url'], branch_commits['branch']['name'])
274 else:
275 ## case for SVN no branch push...
276 return 'to trunk'
277 %> \
278 % for branch, branch_commits in branches_commits.items():
279 ${len(branch_commits['commits'])} ${'commit' if len(branch_commits['commits']) == 1 else 'commits'} ${branch_text(branch)}
280 % for commit in branch_commits['commits']:
273 `<${commit['url']}|${commit['short_id']}>` - ${commit['message_html']|html_to_slack_links}
281 `<${commit['url']}|${commit['short_id']}>` - ${commit['message_html']|html_to_slack_links}
274 %endfor
282 % endfor
275 %endfor
283 % endfor
276 '''))
284 '''))
277
285
278 text = render_with_traceback(
286 text = render_with_traceback(
279 repo_push_template,
287 repo_push_template,
280 data=data,
288 data=data,
281 branches_commits=branches_commits,
289 branches_commits=branches_commits,
282 html_to_slack_links=html_to_slack_links,
290 html_to_slack_links=html_to_slack_links,
283 )
291 )
284
292
285 return title, text
293 return title, text
286
294
287 def format_repo_create_event(self, data):
295 def format_repo_create_event(self, data):
288 template = Template(r'''
296 template = Template(r'''
289 *${data['actor']['username']}* created new repository ${data['repo']['repo_name']}:
297 *${data['actor']['username']}* created new repository ${data['repo']['repo_name']}:
290 ''')
298 ''')
291 title = render_with_traceback(template, data=data)
299 title = render_with_traceback(template, data=data)
292
300
293 template = Template(textwrap.dedent(r'''
301 template = Template(textwrap.dedent(r'''
294 repo_url: ${data['repo']['url']}
302 repo_url: ${data['repo']['url']}
295 repo_type: ${data['repo']['repo_type']}
303 repo_type: ${data['repo']['repo_type']}
296 '''))
304 '''))
297 text = render_with_traceback(template, data=data)
305 text = render_with_traceback(template, data=data)
298
306
299 return title, text
307 return title, text
300
308
301
309
302 def html_to_slack_links(message):
310 def html_to_slack_links(message):
303 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
311 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
304 r'<\1|\2>', message)
312 r'<\1|\2>', message)
305
313
306
314
307 @async_task(ignore_result=True, base=RequestContextTask)
315 @async_task(ignore_result=True, base=RequestContextTask)
308 def post_text_to_slack(settings, title, text, fields=None, overrides=None):
316 def post_text_to_slack(settings, title, text, fields=None, overrides=None):
309 log.debug('sending %s (%s) to slack %s' % (
317 log.debug('sending %s (%s) to slack %s' % (
310 title, text, settings['service']))
318 title, text, settings['service']))
311
319
312 fields = fields or []
320 fields = fields or []
313 overrides = overrides or {}
321 overrides = overrides or {}
314
322
315 message_data = {
323 message_data = {
316 "fallback": text,
324 "fallback": text,
317 "color": "#427cc9",
325 "color": "#427cc9",
318 "pretext": title,
326 "pretext": title,
319 #"author_name": "Bobby Tables",
327 #"author_name": "Bobby Tables",
320 #"author_link": "http://flickr.com/bobby/",
328 #"author_link": "http://flickr.com/bobby/",
321 #"author_icon": "http://flickr.com/icons/bobby.jpg",
329 #"author_icon": "http://flickr.com/icons/bobby.jpg",
322 #"title": "Slack API Documentation",
330 #"title": "Slack API Documentation",
323 #"title_link": "https://api.slack.com/",
331 #"title_link": "https://api.slack.com/",
324 "text": text,
332 "text": text,
325 "fields": fields,
333 "fields": fields,
326 #"image_url": "http://my-website.com/path/to/image.jpg",
334 #"image_url": "http://my-website.com/path/to/image.jpg",
327 #"thumb_url": "http://example.com/path/to/thumb.png",
335 #"thumb_url": "http://example.com/path/to/thumb.png",
328 "footer": "RhodeCode",
336 "footer": "RhodeCode",
329 #"footer_icon": "",
337 #"footer_icon": "",
330 "ts": time.time(),
338 "ts": time.time(),
331 "mrkdwn_in": ["pretext", "text"]
339 "mrkdwn_in": ["pretext", "text"]
332 }
340 }
333 message_data.update(overrides)
341 message_data.update(overrides)
334 json_message = {
342 json_message = {
335 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:'),
343 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:'),
336 "channel": settings.get('channel', ''),
344 "channel": settings.get('channel', ''),
337 "username": settings.get('username', 'Rhodecode'),
345 "username": settings.get('username', 'Rhodecode'),
338 "attachments": [message_data]
346 "attachments": [message_data]
339 }
347 }
340
348
341 resp = requests.post(settings['service'], json=json_message)
349 resp = requests.post(settings['service'], json=json_message)
342 resp.raise_for_status() # raise exception on a failed request
350 resp.raise_for_status() # raise exception on a failed request
General Comments 0
You need to be logged in to leave comments. Login now