##// END OF EJS Templates
hipchat: added a case for SVN commits to trunk
marcink -
r2648:79c76515 default
parent child Browse files
Show More
@@ -1,247 +1,251 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 deform
22 import deform
23 import logging
23 import logging
24 import requests
24 import requests
25 import colander
25 import colander
26 import textwrap
26 import textwrap
27 from mako.template import Template
27 from mako.template import Template
28 from rhodecode import events
28 from rhodecode import events
29 from rhodecode.translation import _
29 from rhodecode.translation import _
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
31 from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
32 from rhodecode.lib.colander_utils import strip_whitespace
32 from rhodecode.lib.colander_utils import strip_whitespace
33 from rhodecode.integrations.types.base import (
33 from rhodecode.integrations.types.base import (
34 IntegrationTypeBase, CommitParsingDataHandler, render_with_traceback)
34 IntegrationTypeBase, CommitParsingDataHandler, render_with_traceback)
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class HipchatSettingsSchema(colander.Schema):
39 class HipchatSettingsSchema(colander.Schema):
40 color_choices = [
40 color_choices = [
41 ('yellow', _('Yellow')),
41 ('yellow', _('Yellow')),
42 ('red', _('Red')),
42 ('red', _('Red')),
43 ('green', _('Green')),
43 ('green', _('Green')),
44 ('purple', _('Purple')),
44 ('purple', _('Purple')),
45 ('gray', _('Gray')),
45 ('gray', _('Gray')),
46 ]
46 ]
47
47
48 server_url = colander.SchemaNode(
48 server_url = colander.SchemaNode(
49 colander.String(),
49 colander.String(),
50 title=_('Hipchat server URL'),
50 title=_('Hipchat server URL'),
51 description=_('Hipchat integration url.'),
51 description=_('Hipchat integration url.'),
52 default='',
52 default='',
53 preparer=strip_whitespace,
53 preparer=strip_whitespace,
54 validator=colander.url,
54 validator=colander.url,
55 widget=deform.widget.TextInputWidget(
55 widget=deform.widget.TextInputWidget(
56 placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
56 placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
57 ),
57 ),
58 )
58 )
59 notify = colander.SchemaNode(
59 notify = colander.SchemaNode(
60 colander.Bool(),
60 colander.Bool(),
61 title=_('Notify'),
61 title=_('Notify'),
62 description=_('Make a notification to the users in room.'),
62 description=_('Make a notification to the users in room.'),
63 missing=False,
63 missing=False,
64 default=False,
64 default=False,
65 )
65 )
66 color = colander.SchemaNode(
66 color = colander.SchemaNode(
67 colander.String(),
67 colander.String(),
68 title=_('Color'),
68 title=_('Color'),
69 description=_('Background color of message.'),
69 description=_('Background color of message.'),
70 missing='',
70 missing='',
71 validator=colander.OneOf([x[0] for x in color_choices]),
71 validator=colander.OneOf([x[0] for x in color_choices]),
72 widget=deform.widget.Select2Widget(
72 widget=deform.widget.Select2Widget(
73 values=color_choices,
73 values=color_choices,
74 ),
74 ),
75 )
75 )
76
76
77
77
78 repo_push_template = Template('''
78 repo_push_template = Template('''
79 <b>${data['actor']['username']}</b> pushed to repo <a href="${data['repo']['url']}">${data['repo']['repo_name']}</a>:
79 <b>${data['actor']['username']}</b> pushed to repo <a href="${data['repo']['url']}">${data['repo']['repo_name']}</a>:
80 <br>
80 <br>
81 <ul>
81 <ul>
82 %for branch, branch_commits in branches_commits.items():
82 %for branch, branch_commits in branches_commits.items():
83 <li>
83 <li>
84 % if branch:
84 <a href="${branch_commits['branch']['url']}">branch: ${branch_commits['branch']['name']}</a>
85 <a href="${branch_commits['branch']['url']}">branch: ${branch_commits['branch']['name']}</a>
86 % else:
87 to trunk
88 % endif
85 <ul>
89 <ul>
86 %for commit in branch_commits['commits']:
90 % for commit in branch_commits['commits']:
87 <li><a href="${commit['url']}">${commit['short_id']}</a> - ${commit['message_html']}</li>
91 <li><a href="${commit['url']}">${commit['short_id']}</a> - ${commit['message_html']}</li>
88 %endfor
92 % endfor
89 </ul>
93 </ul>
90 </li>
94 </li>
91 %endfor
95 %endfor
92 ''')
96 ''')
93
97
94
98
95 class HipchatIntegrationType(IntegrationTypeBase, CommitParsingDataHandler):
99 class HipchatIntegrationType(IntegrationTypeBase, CommitParsingDataHandler):
96 key = 'hipchat'
100 key = 'hipchat'
97 display_name = _('Hipchat')
101 display_name = _('Hipchat')
98 description = _('Send events such as repo pushes and pull requests to '
102 description = _('Send events such as repo pushes and pull requests to '
99 'your hipchat channel.')
103 'your hipchat channel.')
100
104
101 @classmethod
105 @classmethod
102 def icon(cls):
106 def icon(cls):
103 return '''<?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>'''
107 return '''<?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>'''
104
108
105 valid_events = [
109 valid_events = [
106 events.PullRequestCloseEvent,
110 events.PullRequestCloseEvent,
107 events.PullRequestMergeEvent,
111 events.PullRequestMergeEvent,
108 events.PullRequestUpdateEvent,
112 events.PullRequestUpdateEvent,
109 events.PullRequestCommentEvent,
113 events.PullRequestCommentEvent,
110 events.PullRequestReviewEvent,
114 events.PullRequestReviewEvent,
111 events.PullRequestCreateEvent,
115 events.PullRequestCreateEvent,
112 events.RepoPushEvent,
116 events.RepoPushEvent,
113 events.RepoCreateEvent,
117 events.RepoCreateEvent,
114 ]
118 ]
115
119
116 def send_event(self, event):
120 def send_event(self, event):
117 if event.__class__ not in self.valid_events:
121 if event.__class__ not in self.valid_events:
118 log.debug('event not valid: %r' % event)
122 log.debug('event not valid: %r' % event)
119 return
123 return
120
124
121 if event.name not in self.settings['events']:
125 if event.name not in self.settings['events']:
122 log.debug('event ignored: %r' % event)
126 log.debug('event ignored: %r' % event)
123 return
127 return
124
128
125 data = event.as_dict()
129 data = event.as_dict()
126
130
127 text = '<b>%s<b> caused a <b>%s</b> event' % (
131 text = '<b>%s<b> caused a <b>%s</b> event' % (
128 data['actor']['username'], event.name)
132 data['actor']['username'], event.name)
129
133
130 log.debug('handling hipchat event for %s' % event.name)
134 log.debug('handling hipchat event for %s' % event.name)
131
135
132 if isinstance(event, events.PullRequestCommentEvent):
136 if isinstance(event, events.PullRequestCommentEvent):
133 text = self.format_pull_request_comment_event(event, data)
137 text = self.format_pull_request_comment_event(event, data)
134 elif isinstance(event, events.PullRequestReviewEvent):
138 elif isinstance(event, events.PullRequestReviewEvent):
135 text = self.format_pull_request_review_event(event, data)
139 text = self.format_pull_request_review_event(event, data)
136 elif isinstance(event, events.PullRequestEvent):
140 elif isinstance(event, events.PullRequestEvent):
137 text = self.format_pull_request_event(event, data)
141 text = self.format_pull_request_event(event, data)
138 elif isinstance(event, events.RepoPushEvent):
142 elif isinstance(event, events.RepoPushEvent):
139 text = self.format_repo_push_event(data)
143 text = self.format_repo_push_event(data)
140 elif isinstance(event, events.RepoCreateEvent):
144 elif isinstance(event, events.RepoCreateEvent):
141 text = self.format_repo_create_event(data)
145 text = self.format_repo_create_event(data)
142 else:
146 else:
143 log.error('unhandled event type: %r' % event)
147 log.error('unhandled event type: %r' % event)
144
148
145 run_task(post_text_to_hipchat, self.settings, text)
149 run_task(post_text_to_hipchat, self.settings, text)
146
150
147 def settings_schema(self):
151 def settings_schema(self):
148 schema = HipchatSettingsSchema()
152 schema = HipchatSettingsSchema()
149 schema.add(colander.SchemaNode(
153 schema.add(colander.SchemaNode(
150 colander.Set(),
154 colander.Set(),
151 widget=deform.widget.CheckboxChoiceWidget(
155 widget=deform.widget.CheckboxChoiceWidget(
152 values=sorted(
156 values=sorted(
153 [(e.name, e.display_name) for e in self.valid_events]
157 [(e.name, e.display_name) for e in self.valid_events]
154 )
158 )
155 ),
159 ),
156 description="Events activated for this integration",
160 description="Events activated for this integration",
157 name='events'
161 name='events'
158 ))
162 ))
159
163
160 return schema
164 return schema
161
165
162 def format_pull_request_comment_event(self, event, data):
166 def format_pull_request_comment_event(self, event, data):
163 comment_text = data['comment']['text']
167 comment_text = data['comment']['text']
164 if len(comment_text) > 200:
168 if len(comment_text) > 200:
165 comment_text = '{comment_text}<a href="{comment_url}">...<a/>'.format(
169 comment_text = '{comment_text}<a href="{comment_url}">...<a/>'.format(
166 comment_text=h.html_escape(comment_text[:200]),
170 comment_text=h.html_escape(comment_text[:200]),
167 comment_url=data['comment']['url'],
171 comment_url=data['comment']['url'],
168 )
172 )
169
173
170 comment_status = ''
174 comment_status = ''
171 if data['comment']['status']:
175 if data['comment']['status']:
172 comment_status = '[{}]: '.format(data['comment']['status'])
176 comment_status = '[{}]: '.format(data['comment']['status'])
173
177
174 return (textwrap.dedent(
178 return (textwrap.dedent(
175 '''
179 '''
176 {user} commented on pull request <a href="{pr_url}">{number}</a> - {pr_title}:
180 {user} commented on pull request <a href="{pr_url}">{number}</a> - {pr_title}:
177 >>> {comment_status}{comment_text}
181 >>> {comment_status}{comment_text}
178 ''').format(
182 ''').format(
179 comment_status=comment_status,
183 comment_status=comment_status,
180 user=data['actor']['username'],
184 user=data['actor']['username'],
181 number=data['pullrequest']['pull_request_id'],
185 number=data['pullrequest']['pull_request_id'],
182 pr_url=data['pullrequest']['url'],
186 pr_url=data['pullrequest']['url'],
183 pr_status=data['pullrequest']['status'],
187 pr_status=data['pullrequest']['status'],
184 pr_title=h.html_escape(data['pullrequest']['title']),
188 pr_title=h.html_escape(data['pullrequest']['title']),
185 comment_text=h.html_escape(comment_text)
189 comment_text=h.html_escape(comment_text)
186 )
190 )
187 )
191 )
188
192
189 def format_pull_request_review_event(self, event, data):
193 def format_pull_request_review_event(self, event, data):
190 return (textwrap.dedent(
194 return (textwrap.dedent(
191 '''
195 '''
192 Status changed to {pr_status} for pull request <a href="{pr_url}">#{number}</a> - {pr_title}
196 Status changed to {pr_status} for pull request <a href="{pr_url}">#{number}</a> - {pr_title}
193 ''').format(
197 ''').format(
194 user=data['actor']['username'],
198 user=data['actor']['username'],
195 number=data['pullrequest']['pull_request_id'],
199 number=data['pullrequest']['pull_request_id'],
196 pr_url=data['pullrequest']['url'],
200 pr_url=data['pullrequest']['url'],
197 pr_status=data['pullrequest']['status'],
201 pr_status=data['pullrequest']['status'],
198 pr_title=h.html_escape(data['pullrequest']['title']),
202 pr_title=h.html_escape(data['pullrequest']['title']),
199 )
203 )
200 )
204 )
201
205
202 def format_pull_request_event(self, event, data):
206 def format_pull_request_event(self, event, data):
203 action = {
207 action = {
204 events.PullRequestCloseEvent: 'closed',
208 events.PullRequestCloseEvent: 'closed',
205 events.PullRequestMergeEvent: 'merged',
209 events.PullRequestMergeEvent: 'merged',
206 events.PullRequestUpdateEvent: 'updated',
210 events.PullRequestUpdateEvent: 'updated',
207 events.PullRequestCreateEvent: 'created',
211 events.PullRequestCreateEvent: 'created',
208 }.get(event.__class__, str(event.__class__))
212 }.get(event.__class__, str(event.__class__))
209
213
210 return ('Pull request <a href="{url}">#{number}</a> - {title} '
214 return ('Pull request <a href="{url}">#{number}</a> - {title} '
211 '{action} by <b>{user}</b>').format(
215 '{action} by <b>{user}</b>').format(
212 user=data['actor']['username'],
216 user=data['actor']['username'],
213 number=data['pullrequest']['pull_request_id'],
217 number=data['pullrequest']['pull_request_id'],
214 url=data['pullrequest']['url'],
218 url=data['pullrequest']['url'],
215 title=h.html_escape(data['pullrequest']['title']),
219 title=h.html_escape(data['pullrequest']['title']),
216 action=action
220 action=action
217 )
221 )
218
222
219 def format_repo_push_event(self, data):
223 def format_repo_push_event(self, data):
220 branches_commits = self.aggregate_branch_data(
224 branches_commits = self.aggregate_branch_data(
221 data['push']['branches'], data['push']['commits'])
225 data['push']['branches'], data['push']['commits'])
222
226
223 result = render_with_traceback(
227 result = render_with_traceback(
224 repo_push_template,
228 repo_push_template,
225 data=data,
229 data=data,
226 branches_commits=branches_commits,
230 branches_commits=branches_commits,
227 )
231 )
228 return result
232 return result
229
233
230 def format_repo_create_event(self, data):
234 def format_repo_create_event(self, data):
231 return '<a href="{}">{}</a> ({}) repository created by <b>{}</b>'.format(
235 return '<a href="{}">{}</a> ({}) repository created by <b>{}</b>'.format(
232 data['repo']['url'],
236 data['repo']['url'],
233 h.html_escape(data['repo']['repo_name']),
237 h.html_escape(data['repo']['repo_name']),
234 data['repo']['repo_type'],
238 data['repo']['repo_type'],
235 data['actor']['username'],
239 data['actor']['username'],
236 )
240 )
237
241
238
242
239 @async_task(ignore_result=True, base=RequestContextTask)
243 @async_task(ignore_result=True, base=RequestContextTask)
240 def post_text_to_hipchat(settings, text):
244 def post_text_to_hipchat(settings, text):
241 log.debug('sending %s to hipchat %s' % (text, settings['server_url']))
245 log.debug('sending %s to hipchat %s' % (text, settings['server_url']))
242 resp = requests.post(settings['server_url'], json={
246 resp = requests.post(settings['server_url'], json={
243 "message": text,
247 "message": text,
244 "color": settings.get('color', 'yellow'),
248 "color": settings.get('color', 'yellow'),
245 "notify": settings.get('notify', False),
249 "notify": settings.get('notify', False),
246 })
250 })
247 resp.raise_for_status() # raise exception on a failed request
251 resp.raise_for_status() # raise exception on a failed request
General Comments 0
You need to be logged in to leave comments. Login now