# -*- coding: utf-8 -*-
# Copyright (C) 2012-2019 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
from __future__ import unicode_literals
import deform
import logging
import requests
import colander
import textwrap
from mako.template import Template
from rhodecode import events
from rhodecode.translation import _
from rhodecode.lib import helpers as h
from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
from rhodecode.lib.colander_utils import strip_whitespace
from rhodecode.integrations.types.base import (
IntegrationTypeBase, CommitParsingDataHandler, render_with_traceback,
requests_retry_call)
log = logging.getLogger(__name__)
REPO_PUSH_TEMPLATE = Template('''
${data['actor']['username']} pushed to repo ${data['repo']['repo_name']} :
%for branch, branch_commits in branches_commits.items():
% if branch:
branch: ${branch_commits['branch']['name']}
% else:
to trunk
% endif
%endfor
''')
class HipchatSettingsSchema(colander.Schema):
color_choices = [
('yellow', _('Yellow')),
('red', _('Red')),
('green', _('Green')),
('purple', _('Purple')),
('gray', _('Gray')),
]
server_url = colander.SchemaNode(
colander.String(),
title=_('Hipchat server URL'),
description=_('Hipchat integration url.'),
default='',
preparer=strip_whitespace,
validator=colander.url,
widget=deform.widget.TextInputWidget(
placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
),
)
notify = colander.SchemaNode(
colander.Bool(),
title=_('Notify'),
description=_('Make a notification to the users in room.'),
missing=False,
default=False,
)
color = colander.SchemaNode(
colander.String(),
title=_('Color'),
description=_('Background color of message.'),
missing='',
validator=colander.OneOf([x[0] for x in color_choices]),
widget=deform.widget.Select2Widget(
values=color_choices,
),
)
class HipchatIntegrationType(IntegrationTypeBase, CommitParsingDataHandler):
key = 'hipchat'
display_name = _('Hipchat')
description = _('Send events such as repo pushes and pull requests to '
'your hipchat channel.')
@classmethod
def icon(cls):
return ''' '''
valid_events = [
events.PullRequestCloseEvent,
events.PullRequestMergeEvent,
events.PullRequestUpdateEvent,
events.PullRequestCommentEvent,
events.PullRequestReviewEvent,
events.PullRequestCreateEvent,
events.RepoPushEvent,
events.RepoCreateEvent,
]
def send_event(self, event):
if event.__class__ not in self.valid_events:
log.debug('event not valid: %r', event)
return
if event.name not in self.settings['events']:
log.debug('event ignored: %r', event)
return
data = event.as_dict()
text = '%s caused a %s event' % (
data['actor']['username'], event.name)
log.debug('handling hipchat event for %s', event.name)
if isinstance(event, events.PullRequestCommentEvent):
text = self.format_pull_request_comment_event(event, data)
elif isinstance(event, events.PullRequestReviewEvent):
text = self.format_pull_request_review_event(event, data)
elif isinstance(event, events.PullRequestEvent):
text = self.format_pull_request_event(event, data)
elif isinstance(event, events.RepoPushEvent):
text = self.format_repo_push_event(data)
elif isinstance(event, events.RepoCreateEvent):
text = self.format_repo_create_event(data)
else:
log.error('unhandled event type: %r', event)
run_task(post_text_to_hipchat, self.settings, text)
def settings_schema(self):
schema = HipchatSettingsSchema()
schema.add(colander.SchemaNode(
colander.Set(),
widget=deform.widget.CheckboxChoiceWidget(
values=sorted(
[(e.name, e.display_name) for e in self.valid_events]
)
),
description="Events activated for this integration",
name='events'
))
return schema
def format_pull_request_comment_event(self, event, data):
comment_text = data['comment']['text']
if len(comment_text) > 200:
comment_text = '{comment_text}... '.format(
comment_text=h.html_escape(comment_text[:200]),
comment_url=data['comment']['url'],
)
comment_status = ''
if data['comment']['status']:
comment_status = '[{}]: '.format(data['comment']['status'])
return (textwrap.dedent(
'''
{user} commented on pull request {number} - {pr_title}:
>>> {comment_status}{comment_text}
''').format(
comment_status=comment_status,
user=data['actor']['username'],
number=data['pullrequest']['pull_request_id'],
pr_url=data['pullrequest']['url'],
pr_status=data['pullrequest']['status'],
pr_title=h.html_escape(data['pullrequest']['title']),
comment_text=h.html_escape(comment_text)
)
)
def format_pull_request_review_event(self, event, data):
return (textwrap.dedent(
'''
Status changed to {pr_status} for pull request #{number} - {pr_title}
''').format(
user=data['actor']['username'],
number=data['pullrequest']['pull_request_id'],
pr_url=data['pullrequest']['url'],
pr_status=data['pullrequest']['status'],
pr_title=h.html_escape(data['pullrequest']['title']),
)
)
def format_pull_request_event(self, event, data):
action = {
events.PullRequestCloseEvent: 'closed',
events.PullRequestMergeEvent: 'merged',
events.PullRequestUpdateEvent: 'updated',
events.PullRequestCreateEvent: 'created',
}.get(event.__class__, str(event.__class__))
return ('Pull request #{number} - {title} '
'{action} by {user} ').format(
user=data['actor']['username'],
number=data['pullrequest']['pull_request_id'],
url=data['pullrequest']['url'],
title=h.html_escape(data['pullrequest']['title']),
action=action
)
def format_repo_push_event(self, data):
branches_commits = self.aggregate_branch_data(
data['push']['branches'], data['push']['commits'])
result = render_with_traceback(
REPO_PUSH_TEMPLATE,
data=data,
branches_commits=branches_commits,
)
return result
def format_repo_create_event(self, data):
return '{} ({}) repository created by {} '.format(
data['repo']['url'],
h.html_escape(data['repo']['repo_name']),
data['repo']['repo_type'],
data['actor']['username'],
)
@async_task(ignore_result=True, base=RequestContextTask)
def post_text_to_hipchat(settings, text):
log.debug('sending %s to hipchat %s', text, settings['server_url'])
json_message = {
"message": text,
"color": settings.get('color', 'yellow'),
"notify": settings.get('notify', False),
}
req_session = requests_retry_call()
resp = req_session.post(settings['server_url'], json=json_message, timeout=60)
resp.raise_for_status() # raise exception on a failed request