##// END OF EJS Templates
integrations: add hipchat integration
dan -
r550:4857982e default
parent child Browse files
Show More
@@ -0,0 +1,242 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from __future__ import unicode_literals
22 import deform
23 import re
24 import logging
25 import requests
26 import colander
27 import textwrap
28 from celery.task import task
29 from mako.template import Template
30
31 from rhodecode import events
32 from rhodecode.translation import lazy_ugettext
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.celerylib import run_task
35 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.integrations.types.base import IntegrationTypeBase
37 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
38
39 log = logging.getLogger()
40
41
42 class HipchatSettingsSchema(IntegrationSettingsSchemaBase):
43 color_choices = [
44 ('yellow', lazy_ugettext('Yellow')),
45 ('red', lazy_ugettext('Red')),
46 ('green', lazy_ugettext('Green')),
47 ('purple', lazy_ugettext('Purple')),
48 ('gray', lazy_ugettext('Gray')),
49 ]
50
51 server_url = colander.SchemaNode(
52 colander.String(),
53 title=lazy_ugettext('Hipchat server URL'),
54 description=lazy_ugettext('Hipchat integration url.'),
55 default='',
56 preparer=strip_whitespace,
57 validator=colander.url,
58 widget=deform.widget.TextInputWidget(
59 placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
60 ),
61 )
62 notify = colander.SchemaNode(
63 colander.Bool(),
64 title=lazy_ugettext('Notify'),
65 description=lazy_ugettext('Make a notification to the users in room.'),
66 missing=False,
67 default=False,
68 )
69 color = colander.SchemaNode(
70 colander.String(),
71 title=lazy_ugettext('Color'),
72 description=lazy_ugettext('Background color of message.'),
73 missing='',
74 validator=colander.OneOf([x[0] for x in color_choices]),
75 widget=deform.widget.Select2Widget(
76 values=color_choices,
77 ),
78 )
79
80
81 repo_push_template = Template('''
82 <b>${data['actor']['username']}</b> pushed to
83 %if data['push']['branches']:
84 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'}
85 ${', '.join('<a href="%s">%s</a>' % (branch['url'], branch['name']) for branch in data['push']['branches'])}
86 %else:
87 unknown branch
88 %endif
89 in <a href="${data['repo']['url']}">${data['repo']['repo_name']}</a>
90 <br>
91 <ul>
92 %for commit in data['push']['commits']:
93 <li>
94 <a href="${commit['url']}">${commit['short_id']}</a> - ${commit['message_html']}
95 </li>
96 %endfor
97 </ul>
98 ''')
99
100
101
102 class HipchatIntegrationType(IntegrationTypeBase):
103 key = 'hipchat'
104 display_name = lazy_ugettext('Hipchat')
105 valid_events = [
106 events.PullRequestCloseEvent,
107 events.PullRequestMergeEvent,
108 events.PullRequestUpdateEvent,
109 events.PullRequestCommentEvent,
110 events.PullRequestReviewEvent,
111 events.PullRequestCreateEvent,
112 events.RepoPushEvent,
113 events.RepoCreateEvent,
114 ]
115
116 def send_event(self, event):
117 if event.__class__ not in self.valid_events:
118 log.debug('event not valid: %r' % event)
119 return
120
121 if event.name not in self.settings['events']:
122 log.debug('event ignored: %r' % event)
123 return
124
125 data = event.as_dict()
126
127 text = '<b>%s<b> caused a <b>%s</b> event' % (
128 data['actor']['username'], event.name)
129
130 log.debug('handling hipchat event for %s' % event.name)
131
132 if isinstance(event, events.PullRequestCommentEvent):
133 text = self.format_pull_request_comment_event(event, data)
134 elif isinstance(event, events.PullRequestReviewEvent):
135 text = self.format_pull_request_review_event(event, data)
136 elif isinstance(event, events.PullRequestEvent):
137 text = self.format_pull_request_event(event, data)
138 elif isinstance(event, events.RepoPushEvent):
139 text = self.format_repo_push_event(data)
140 elif isinstance(event, events.RepoCreateEvent):
141 text = self.format_repo_create_event(data)
142 else:
143 log.error('unhandled event type: %r' % event)
144
145 run_task(post_text_to_hipchat, self.settings, text)
146
147 def settings_schema(self):
148 schema = HipchatSettingsSchema()
149 schema.add(colander.SchemaNode(
150 colander.Set(),
151 widget=deform.widget.CheckboxChoiceWidget(
152 values=sorted(
153 [(e.name, e.display_name) for e in self.valid_events]
154 )
155 ),
156 description="Events activated for this integration",
157 name='events'
158 ))
159
160 return schema
161
162 def format_pull_request_comment_event(self, event, data):
163 comment_text = data['comment']['text']
164 if len(comment_text) > 200:
165 comment_text = '{comment_text}<a href="{comment_url}">...<a/>'.format(
166 comment_text=comment_text[:200],
167 comment_url=data['comment']['url'],
168 )
169
170 comment_status = ''
171 if data['comment']['status']:
172 comment_status = '[{}]: '.format(data['comment']['status'])
173
174 return (textwrap.dedent(
175 '''
176 {user} commented on pull request <a href="{pr_url}">{number}</a> - {pr_title}:
177 >>> {comment_status}{comment_text}
178 ''').format(
179 comment_status=comment_status,
180 user=data['actor']['username'],
181 number=data['pullrequest']['pull_request_id'],
182 pr_url=data['pullrequest']['url'],
183 pr_status=data['pullrequest']['status'],
184 pr_title=data['pullrequest']['title'],
185 comment_text=comment_text
186 )
187 )
188
189 def format_pull_request_review_event(self, event, data):
190 return (textwrap.dedent(
191 '''
192 Status changed to {pr_status} for pull request <a href="{pr_url}">#{number}</a> - {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
202 def format_pull_request_event(self, event, data):
203 action = {
204 events.PullRequestCloseEvent: 'closed',
205 events.PullRequestMergeEvent: 'merged',
206 events.PullRequestUpdateEvent: 'updated',
207 events.PullRequestCreateEvent: 'created',
208 }.get(event.__class__, str(event.__class__))
209
210 return ('Pull request <a href="{url}">#{number}</a> - {title} '
211 '{action} by {user}').format(
212 user=data['actor']['username'],
213 number=data['pullrequest']['pull_request_id'],
214 url=data['pullrequest']['url'],
215 title=data['pullrequest']['title'],
216 action=action
217 )
218
219 def format_repo_push_event(self, data):
220 result = repo_push_template.render(
221 data=data,
222 )
223 return result
224
225 def format_repo_create_event(self, data):
226 return '<a href="{}">{}</a> ({}) repository created by <b>{}</b>'.format(
227 data['repo']['url'],
228 data['repo']['repo_name'],
229 data['repo']['repo_type'],
230 data['actor']['username'],
231 )
232
233
234 @task(ignore_result=True)
235 def post_text_to_hipchat(settings, text):
236 log.debug('sending %s to hipchat %s' % (text, settings['server_url']))
237 resp = requests.post(settings['server_url'], json={
238 "message": text,
239 "color": settings.get('color', 'yellow'),
240 "notify": settings.get('notify', False),
241 })
242 resp.raise_for_status() # raise exception on a failed request
@@ -1,58 +1,60 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 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 import logging
21 import logging
22
22
23 from rhodecode.integrations.registry import IntegrationTypeRegistry
23 from rhodecode.integrations.registry import IntegrationTypeRegistry
24 from rhodecode.integrations.types import webhook, slack
24 from rhodecode.integrations.types import webhook, slack, hipchat
25
25
26 log = logging.getLogger(__name__)
26 log = logging.getLogger(__name__)
27
27
28
28
29 # TODO: dan: This is currently global until we figure out what to do about
29 # TODO: dan: This is currently global until we figure out what to do about
30 # VCS's not having a pyramid context - move it to pyramid app configuration
30 # VCS's not having a pyramid context - move it to pyramid app configuration
31 # includeme level later to allow per instance integration setup
31 # includeme level later to allow per instance integration setup
32 integration_type_registry = IntegrationTypeRegistry()
32 integration_type_registry = IntegrationTypeRegistry()
33
33
34 integration_type_registry.register_integration_type(
34 integration_type_registry.register_integration_type(
35 webhook.WebhookIntegrationType)
35 webhook.WebhookIntegrationType)
36 integration_type_registry.register_integration_type(
36 integration_type_registry.register_integration_type(
37 slack.SlackIntegrationType)
37 slack.SlackIntegrationType)
38 integration_type_registry.register_integration_type(
39 hipchat.HipchatIntegrationType)
38
40
39
41
40 def integrations_event_handler(event):
42 def integrations_event_handler(event):
41 """
43 """
42 Takes an event and passes it to all enabled integrations
44 Takes an event and passes it to all enabled integrations
43 """
45 """
44 from rhodecode.model.integration import IntegrationModel
46 from rhodecode.model.integration import IntegrationModel
45
47
46 integration_model = IntegrationModel()
48 integration_model = IntegrationModel()
47 integrations = integration_model.get_for_event(event)
49 integrations = integration_model.get_for_event(event)
48 for integration in integrations:
50 for integration in integrations:
49 try:
51 try:
50 integration_model.send_event(integration, event)
52 integration_model.send_event(integration, event)
51 except Exception:
53 except Exception:
52 log.exception(
54 log.exception(
53 'failure occured when sending event %s to integration %s' % (
55 'failure occured when sending event %s to integration %s' % (
54 event, integration))
56 event, integration))
55
57
56
58
57 def includeme(config):
59 def includeme(config):
58 config.include('rhodecode.integrations.routes')
60 config.include('rhodecode.integrations.routes')
General Comments 0
You need to be logged in to leave comments. Login now