##// END OF EJS Templates
integrations: use orderedDict in all integrations for settings.
marcink -
r2420:e50c0448 default
parent child Browse files
Show More
@@ -1,252 +1,252 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from __future__ import unicode_literals
22 22 import deform
23 23 import logging
24 24 import requests
25 25 import colander
26 26 import textwrap
27 from collections import OrderedDict
27 28 from mako.template import Template
28
29 29 from rhodecode import events
30 30 from rhodecode.translation import _
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
33 33 from rhodecode.lib.colander_utils import strip_whitespace
34 34 from rhodecode.integrations.types.base import IntegrationTypeBase
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class HipchatSettingsSchema(colander.Schema):
40 40 color_choices = [
41 41 ('yellow', _('Yellow')),
42 42 ('red', _('Red')),
43 43 ('green', _('Green')),
44 44 ('purple', _('Purple')),
45 45 ('gray', _('Gray')),
46 46 ]
47 47
48 48 server_url = colander.SchemaNode(
49 49 colander.String(),
50 50 title=_('Hipchat server URL'),
51 51 description=_('Hipchat integration url.'),
52 52 default='',
53 53 preparer=strip_whitespace,
54 54 validator=colander.url,
55 55 widget=deform.widget.TextInputWidget(
56 56 placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
57 57 ),
58 58 )
59 59 notify = colander.SchemaNode(
60 60 colander.Bool(),
61 61 title=_('Notify'),
62 62 description=_('Make a notification to the users in room.'),
63 63 missing=False,
64 64 default=False,
65 65 )
66 66 color = colander.SchemaNode(
67 67 colander.String(),
68 68 title=_('Color'),
69 69 description=_('Background color of message.'),
70 70 missing='',
71 71 validator=colander.OneOf([x[0] for x in color_choices]),
72 72 widget=deform.widget.Select2Widget(
73 73 values=color_choices,
74 74 ),
75 75 )
76 76
77 77
78 78 repo_push_template = Template('''
79 79 <b>${data['actor']['username']}</b> pushed to repo <a href="${data['repo']['url']}">${data['repo']['repo_name']}</a>:
80 80 <br>
81 81 <ul>
82 82 %for branch, branch_commits in branches_commits.items():
83 83 <li>
84 84 <a href="${branch_commits['branch']['url']}">branch: ${branch_commits['branch']['name']}</a>
85 85 <ul>
86 86 %for commit in branch_commits['commits']:
87 87 <li><a href="${commit['url']}">${commit['short_id']}</a> - ${commit['message_html']}</li>
88 88 %endfor
89 89 </ul>
90 90 </li>
91 91 %endfor
92 92 ''')
93 93
94 94
95 95 class HipchatIntegrationType(IntegrationTypeBase):
96 96 key = 'hipchat'
97 97 display_name = _('Hipchat')
98 98 description = _('Send events such as repo pushes and pull requests to '
99 99 'your hipchat channel.')
100 100 icon = '''<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve"><g><g transform="translate(0.000000,511.000000) scale(0.100000,-0.100000)"><path fill="#205281" d="M4197.1,4662.4c-1661.5-260.4-3018-1171.6-3682.6-2473.3C219.9,1613.6,100,1120.3,100,462.6c0-1014,376.8-1918.4,1127-2699.4C2326.7-3377.6,3878.5-3898.3,5701-3730.5l486.5,44.5l208.9-123.3c637.2-373.4,1551.8-640.6,2240.4-650.9c304.9-6.9,335.7,0,417.9,75.4c185,174.7,147.3,411.1-89.1,548.1c-315.2,181.6-620,544.7-733.1,870.1l-51.4,157.6l472.7,472.7c349.4,349.4,520.7,551.5,657.7,774.2c784.5,1281.2,784.5,2788.5,0,4052.6c-236.4,376.8-794.8,966-1178.4,1236.7c-572.1,407.7-1264.1,709.1-1993.7,870.1c-267.2,58.2-479.6,75.4-1038,82.2C4714.4,4686.4,4310.2,4679.6,4197.1,4662.4z M5947.6,3740.9c1856.7-380.3,3127.6-1709.4,3127.6-3275c0-1000.3-534.4-1949.2-1466.2-2600.1c-188.4-133.6-287.8-226.1-301.5-284.4c-41.1-157.6,263.8-938.6,397.4-1020.8c20.5-10.3,34.3-44.5,34.3-75.4c0-167.8-811.9,195.3-1363.4,609.8l-181.6,137l-332.3-58.2c-445.3-78.8-1281.2-78.8-1702.6,0C2796-2569.2,1734.1-1832.6,1220.2-801.5C983.8-318.5,905,51.5,929,613.3c27.4,640.6,243.2,1192.1,685.1,1740.3c620,770.8,1661.5,1305.2,2822.8,1452.5C4806.9,3854,5553.7,3819.7,5947.6,3740.9z"/><path fill="#205281" d="M2381.5-345.9c-75.4-106.2-68.5-167.8,34.3-322c332.3-500.2,1010.6-928.4,1760.8-1120.2c417.9-106.2,1226.4-106.2,1644.3,0c712.5,181.6,1270.9,517.3,1685.4,1014C7681-561.7,7715.3-424.7,7616-325.4c-89.1,89.1-167.9,65.1-431.7-133.6c-835.8-630.3-2028-856.4-3086.5-585.8C3683.3-938.6,3142-685,2830.3-448.7C2576.8-253.4,2463.7-229.4,2381.5-345.9z"/></g></g><!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon --></svg>'''
101 101 valid_events = [
102 102 events.PullRequestCloseEvent,
103 103 events.PullRequestMergeEvent,
104 104 events.PullRequestUpdateEvent,
105 105 events.PullRequestCommentEvent,
106 106 events.PullRequestReviewEvent,
107 107 events.PullRequestCreateEvent,
108 108 events.RepoPushEvent,
109 109 events.RepoCreateEvent,
110 110 ]
111 111
112 112 def send_event(self, event):
113 113 if event.__class__ not in self.valid_events:
114 114 log.debug('event not valid: %r' % event)
115 115 return
116 116
117 117 if event.name not in self.settings['events']:
118 118 log.debug('event ignored: %r' % event)
119 119 return
120 120
121 121 data = event.as_dict()
122 122
123 123 text = '<b>%s<b> caused a <b>%s</b> event' % (
124 124 data['actor']['username'], event.name)
125 125
126 126 log.debug('handling hipchat event for %s' % event.name)
127 127
128 128 if isinstance(event, events.PullRequestCommentEvent):
129 129 text = self.format_pull_request_comment_event(event, data)
130 130 elif isinstance(event, events.PullRequestReviewEvent):
131 131 text = self.format_pull_request_review_event(event, data)
132 132 elif isinstance(event, events.PullRequestEvent):
133 133 text = self.format_pull_request_event(event, data)
134 134 elif isinstance(event, events.RepoPushEvent):
135 135 text = self.format_repo_push_event(data)
136 136 elif isinstance(event, events.RepoCreateEvent):
137 137 text = self.format_repo_create_event(data)
138 138 else:
139 139 log.error('unhandled event type: %r' % event)
140 140
141 141 run_task(post_text_to_hipchat, self.settings, text)
142 142
143 143 def settings_schema(self):
144 144 schema = HipchatSettingsSchema()
145 145 schema.add(colander.SchemaNode(
146 146 colander.Set(),
147 147 widget=deform.widget.CheckboxChoiceWidget(
148 148 values=sorted(
149 149 [(e.name, e.display_name) for e in self.valid_events]
150 150 )
151 151 ),
152 152 description="Events activated for this integration",
153 153 name='events'
154 154 ))
155 155
156 156 return schema
157 157
158 158 def format_pull_request_comment_event(self, event, data):
159 159 comment_text = data['comment']['text']
160 160 if len(comment_text) > 200:
161 161 comment_text = '{comment_text}<a href="{comment_url}">...<a/>'.format(
162 162 comment_text=h.html_escape(comment_text[:200]),
163 163 comment_url=data['comment']['url'],
164 164 )
165 165
166 166 comment_status = ''
167 167 if data['comment']['status']:
168 168 comment_status = '[{}]: '.format(data['comment']['status'])
169 169
170 170 return (textwrap.dedent(
171 171 '''
172 172 {user} commented on pull request <a href="{pr_url}">{number}</a> - {pr_title}:
173 173 >>> {comment_status}{comment_text}
174 174 ''').format(
175 175 comment_status=comment_status,
176 176 user=data['actor']['username'],
177 177 number=data['pullrequest']['pull_request_id'],
178 178 pr_url=data['pullrequest']['url'],
179 179 pr_status=data['pullrequest']['status'],
180 180 pr_title=h.html_escape(data['pullrequest']['title']),
181 181 comment_text=h.html_escape(comment_text)
182 182 )
183 183 )
184 184
185 185 def format_pull_request_review_event(self, event, data):
186 186 return (textwrap.dedent(
187 187 '''
188 188 Status changed to {pr_status} for pull request <a href="{pr_url}">#{number}</a> - {pr_title}
189 189 ''').format(
190 190 user=data['actor']['username'],
191 191 number=data['pullrequest']['pull_request_id'],
192 192 pr_url=data['pullrequest']['url'],
193 193 pr_status=data['pullrequest']['status'],
194 194 pr_title=h.html_escape(data['pullrequest']['title']),
195 195 )
196 196 )
197 197
198 198 def format_pull_request_event(self, event, data):
199 199 action = {
200 200 events.PullRequestCloseEvent: 'closed',
201 201 events.PullRequestMergeEvent: 'merged',
202 202 events.PullRequestUpdateEvent: 'updated',
203 203 events.PullRequestCreateEvent: 'created',
204 204 }.get(event.__class__, str(event.__class__))
205 205
206 206 return ('Pull request <a href="{url}">#{number}</a> - {title} '
207 207 '{action} by <b>{user}</b>').format(
208 208 user=data['actor']['username'],
209 209 number=data['pullrequest']['pull_request_id'],
210 210 url=data['pullrequest']['url'],
211 211 title=h.html_escape(data['pullrequest']['title']),
212 212 action=action
213 213 )
214 214
215 215 def format_repo_push_event(self, data):
216 216 branch_data = {branch['name']: branch
217 217 for branch in data['push']['branches']}
218 218
219 branches_commits = {}
219 branches_commits = OrderedDict()
220 220 for commit in data['push']['commits']:
221 221 if commit['branch'] not in branches_commits:
222 222 branch_commits = {'branch': branch_data[commit['branch']],
223 223 'commits': []}
224 224 branches_commits[commit['branch']] = branch_commits
225 225
226 226 branch_commits = branches_commits[commit['branch']]
227 227 branch_commits['commits'].append(commit)
228 228
229 229 result = repo_push_template.render(
230 230 data=data,
231 231 branches_commits=branches_commits,
232 232 )
233 233 return result
234 234
235 235 def format_repo_create_event(self, data):
236 236 return '<a href="{}">{}</a> ({}) repository created by <b>{}</b>'.format(
237 237 data['repo']['url'],
238 238 h.html_escape(data['repo']['repo_name']),
239 239 data['repo']['repo_type'],
240 240 data['actor']['username'],
241 241 )
242 242
243 243
244 244 @async_task(ignore_result=True, base=RequestContextTask)
245 245 def post_text_to_hipchat(settings, text):
246 246 log.debug('sending %s to hipchat %s' % (text, settings['server_url']))
247 247 resp = requests.post(settings['server_url'], json={
248 248 "message": text,
249 249 "color": settings.get('color', 'yellow'),
250 250 "notify": settings.get('notify', False),
251 251 })
252 252 resp.raise_for_status() # raise exception on a failed request
@@ -1,333 +1,334 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from __future__ import unicode_literals
22 22 import re
23 23 import time
24 24 import textwrap
25 25 import logging
26 26
27 27 import deform
28 28 import requests
29 29 import colander
30 30 from mako.template import Template
31 from collections import OrderedDict
31 32
32 33 from rhodecode import events
33 34 from rhodecode.translation import _
34 35 from rhodecode.lib import helpers as h
35 36 from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
36 37 from rhodecode.lib.colander_utils import strip_whitespace
37 38 from rhodecode.integrations.types.base import IntegrationTypeBase
38 39
39 40 log = logging.getLogger(__name__)
40 41
41 42
42 43 class SlackSettingsSchema(colander.Schema):
43 44 service = colander.SchemaNode(
44 45 colander.String(),
45 46 title=_('Slack service URL'),
46 47 description=h.literal(_(
47 48 'This can be setup at the '
48 49 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
49 50 'slack app manager</a>')),
50 51 default='',
51 52 preparer=strip_whitespace,
52 53 validator=colander.url,
53 54 widget=deform.widget.TextInputWidget(
54 55 placeholder='https://hooks.slack.com/services/...',
55 56 ),
56 57 )
57 58 username = colander.SchemaNode(
58 59 colander.String(),
59 60 title=_('Username'),
60 61 description=_('Username to show notifications coming from.'),
61 62 missing='Rhodecode',
62 63 preparer=strip_whitespace,
63 64 widget=deform.widget.TextInputWidget(
64 65 placeholder='Rhodecode'
65 66 ),
66 67 )
67 68 channel = colander.SchemaNode(
68 69 colander.String(),
69 70 title=_('Channel'),
70 71 description=_('Channel to send notifications to.'),
71 72 missing='',
72 73 preparer=strip_whitespace,
73 74 widget=deform.widget.TextInputWidget(
74 75 placeholder='#general'
75 76 ),
76 77 )
77 78 icon_emoji = colander.SchemaNode(
78 79 colander.String(),
79 80 title=_('Emoji'),
80 81 description=_('Emoji to use eg. :studio_microphone:'),
81 82 missing='',
82 83 preparer=strip_whitespace,
83 84 widget=deform.widget.TextInputWidget(
84 85 placeholder=':studio_microphone:'
85 86 ),
86 87 )
87 88
88 89
89 90 class SlackIntegrationType(IntegrationTypeBase):
90 91 key = 'slack'
91 92 display_name = _('Slack')
92 93 description = _('Send events such as repo pushes and pull requests to '
93 94 'your slack channel.')
94 95 icon = '''<?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>'''
95 96 valid_events = [
96 97 events.PullRequestCloseEvent,
97 98 events.PullRequestMergeEvent,
98 99 events.PullRequestUpdateEvent,
99 100 events.PullRequestCommentEvent,
100 101 events.PullRequestReviewEvent,
101 102 events.PullRequestCreateEvent,
102 103 events.RepoPushEvent,
103 104 events.RepoCreateEvent,
104 105 ]
105 106
106 107 def send_event(self, event):
107 108 if event.__class__ not in self.valid_events:
108 109 log.debug('event not valid: %r' % event)
109 110 return
110 111
111 112 if event.name not in self.settings['events']:
112 113 log.debug('event ignored: %r' % event)
113 114 return
114 115
115 116 data = event.as_dict()
116 117
117 118 # defaults
118 119 title = '*%s* caused a *%s* event' % (
119 120 data['actor']['username'], event.name)
120 121 text = '*%s* caused a *%s* event' % (
121 122 data['actor']['username'], event.name)
122 123 fields = None
123 124 overrides = None
124 125
125 126 log.debug('handling slack event for %s' % event.name)
126 127
127 128 if isinstance(event, events.PullRequestCommentEvent):
128 129 (title, text, fields, overrides) \
129 130 = self.format_pull_request_comment_event(event, data)
130 131 elif isinstance(event, events.PullRequestReviewEvent):
131 132 title, text = self.format_pull_request_review_event(event, data)
132 133 elif isinstance(event, events.PullRequestEvent):
133 134 title, text = self.format_pull_request_event(event, data)
134 135 elif isinstance(event, events.RepoPushEvent):
135 136 title, text = self.format_repo_push_event(data)
136 137 elif isinstance(event, events.RepoCreateEvent):
137 138 title, text = self.format_repo_create_event(data)
138 139 else:
139 140 log.error('unhandled event type: %r' % event)
140 141
141 142 run_task(post_text_to_slack, self.settings, title, text, fields, overrides)
142 143
143 144 def settings_schema(self):
144 145 schema = SlackSettingsSchema()
145 146 schema.add(colander.SchemaNode(
146 147 colander.Set(),
147 148 widget=deform.widget.CheckboxChoiceWidget(
148 149 values=sorted(
149 150 [(e.name, e.display_name) for e in self.valid_events]
150 151 )
151 152 ),
152 153 description="Events activated for this integration",
153 154 name='events'
154 155 ))
155 156
156 157 return schema
157 158
158 159 def format_pull_request_comment_event(self, event, data):
159 160 comment_text = data['comment']['text']
160 161 if len(comment_text) > 200:
161 162 comment_text = '<{comment_url}|{comment_text}...>'.format(
162 163 comment_text=comment_text[:200],
163 164 comment_url=data['comment']['url'],
164 165 )
165 166
166 167 fields = None
167 168 overrides = None
168 169 status_text = None
169 170
170 171 if data['comment']['status']:
171 172 status_color = {
172 173 'approved': '#0ac878',
173 174 'rejected': '#e85e4d'}.get(data['comment']['status'])
174 175
175 176 if status_color:
176 177 overrides = {"color": status_color}
177 178
178 179 status_text = data['comment']['status']
179 180
180 181 if data['comment']['file']:
181 182 fields = [
182 183 {
183 184 "title": "file",
184 185 "value": data['comment']['file']
185 186 },
186 187 {
187 188 "title": "line",
188 189 "value": data['comment']['line']
189 190 }
190 191 ]
191 192
192 193 title = Template(textwrap.dedent(r'''
193 194 *${data['actor']['username']}* left ${data['comment']['type']} on pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
194 195 ''')).render(data=data, comment=event.comment)
195 196
196 197 text = Template(textwrap.dedent(r'''
197 198 *pull request title*: ${pr_title}
198 199 % if status_text:
199 200 *submitted status*: `${status_text}`
200 201 % endif
201 202 >>> ${comment_text}
202 203 ''')).render(comment_text=comment_text,
203 204 pr_title=data['pullrequest']['title'],
204 205 status_text=status_text)
205 206
206 207 return title, text, fields, overrides
207 208
208 209 def format_pull_request_review_event(self, event, data):
209 210 title = Template(textwrap.dedent(r'''
210 211 *${data['actor']['username']}* changed status of pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']} to `${data['pullrequest']['status']}`>:
211 212 ''')).render(data=data)
212 213
213 214 text = Template(textwrap.dedent(r'''
214 215 *pull request title*: ${pr_title}
215 216 ''')).render(
216 217 pr_title=data['pullrequest']['title'],
217 218 )
218 219
219 220 return title, text
220 221
221 222 def format_pull_request_event(self, event, data):
222 223 action = {
223 224 events.PullRequestCloseEvent: 'closed',
224 225 events.PullRequestMergeEvent: 'merged',
225 226 events.PullRequestUpdateEvent: 'updated',
226 227 events.PullRequestCreateEvent: 'created',
227 228 }.get(event.__class__, str(event.__class__))
228 229
229 230 title = Template(textwrap.dedent(r'''
230 231 *${data['actor']['username']}* `${action}` pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
231 232 ''')).render(data=data, action=action)
232 233
233 234 text = Template(textwrap.dedent(r'''
234 235 *pull request title*: ${pr_title}
235 236 %if data['pullrequest']['commits']:
236 237 *commits*: ${len(data['pullrequest']['commits'])}
237 238 %endif
238 239 ''')).render(
239 240 pr_title=data['pullrequest']['title'],
240 241 data=data
241 242 )
242 243
243 244 return title, text
244 245
245 246 def format_repo_push_event(self, data):
246 247 branch_data = {branch['name']: branch
247 248 for branch in data['push']['branches']}
248 249
249 branches_commits = {}
250 branches_commits = OrderedDict()
250 251 for commit in data['push']['commits']:
251 252 if commit['branch'] not in branches_commits:
252 253 branch_commits = {'branch': branch_data[commit['branch']],
253 254 'commits': []}
254 255 branches_commits[commit['branch']] = branch_commits
255 256
256 257 branch_commits = branches_commits[commit['branch']]
257 258 branch_commits['commits'].append(commit)
258 259
259 260 title = Template(r'''
260 261 *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>:
261 262 ''').render(data=data)
262 263
263 264 repo_push_template = Template(textwrap.dedent(r'''
264 265 %for branch, branch_commits in branches_commits.items():
265 266 ${len(branch_commits['commits'])} ${'commit' if len(branch_commits['commits']) == 1 else 'commits'} on branch: <${branch_commits['branch']['url']}|${branch_commits['branch']['name']}>
266 267 %for commit in branch_commits['commits']:
267 268 `<${commit['url']}|${commit['short_id']}>` - ${commit['message_html']|html_to_slack_links}
268 269 %endfor
269 270 %endfor
270 271 '''))
271 272
272 273 text = repo_push_template.render(
273 274 data=data,
274 275 branches_commits=branches_commits,
275 276 html_to_slack_links=html_to_slack_links,
276 277 )
277 278
278 279 return title, text
279 280
280 281 def format_repo_create_event(self, data):
281 282 title = Template(r'''
282 283 *${data['actor']['username']}* created new repository ${data['repo']['repo_name']}:
283 284 ''').render(data=data)
284 285
285 286 text = Template(textwrap.dedent(r'''
286 287 repo_url: ${data['repo']['url']}
287 288 repo_type: ${data['repo']['repo_type']}
288 289 ''')).render(data=data)
289 290
290 291 return title, text
291 292
292 293
293 294 def html_to_slack_links(message):
294 295 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
295 296 r'<\1|\2>', message)
296 297
297 298
298 299 @async_task(ignore_result=True, base=RequestContextTask)
299 300 def post_text_to_slack(settings, title, text, fields=None, overrides=None):
300 301 log.debug('sending %s (%s) to slack %s' % (
301 302 title, text, settings['service']))
302 303
303 304 fields = fields or []
304 305 overrides = overrides or {}
305 306
306 307 message_data = {
307 308 "fallback": text,
308 309 "color": "#427cc9",
309 310 "pretext": title,
310 311 #"author_name": "Bobby Tables",
311 312 #"author_link": "http://flickr.com/bobby/",
312 313 #"author_icon": "http://flickr.com/icons/bobby.jpg",
313 314 #"title": "Slack API Documentation",
314 315 #"title_link": "https://api.slack.com/",
315 316 "text": text,
316 317 "fields": fields,
317 318 #"image_url": "http://my-website.com/path/to/image.jpg",
318 319 #"thumb_url": "http://example.com/path/to/thumb.png",
319 320 "footer": "RhodeCode",
320 321 #"footer_icon": "",
321 322 "ts": time.time(),
322 323 "mrkdwn_in": ["pretext", "text"]
323 324 }
324 325 message_data.update(overrides)
325 326 json_message = {
326 327 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:'),
327 328 "channel": settings.get('channel', ''),
328 329 "username": settings.get('username', 'Rhodecode'),
329 330 "attachments": [message_data]
330 331 }
331 332
332 333 resp = requests.post(settings['service'], json=json_message)
333 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