##// END OF EJS Templates
dependencies: Adding Bower out of nixpkgs...
dependencies: Adding Bower out of nixpkgs Usually bower would be installed globally, so it seems to be appropriate to use the one out of the nixpkgs so that we don't have to pin a specific version ourselves.

File last commit:

r518:9f9ffd3a default
r709:a198b78f default
Show More
slack.py
253 lines | 8.9 KiB | text/x-python | PythonLexer
dan
integrations: add integration support...
r411 # -*- coding: utf-8 -*-
# Copyright (C) 2012-2016 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 <http://www.gnu.org/licenses/>.
#
# 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
dan
forms: add deform for integration settings forms
r518 import deform
dan
integrations: add integration support...
r411 import re
import logging
import requests
import colander
dan
events: add an event for pull request comments with review status
r443 import textwrap
dan
integrations: add integration support...
r411 from celery.task import task
from mako.template import Template
from rhodecode import events
from rhodecode.translation import lazy_ugettext
from rhodecode.lib import helpers as h
from rhodecode.lib.celerylib import run_task
from rhodecode.lib.colander_utils import strip_whitespace
from rhodecode.integrations.types.base import IntegrationTypeBase
from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
Martin Bornhold
logging: Use __name__ when requesting a logger
r504 log = logging.getLogger(__name__)
dan
integrations: add integration support...
r411
class SlackSettingsSchema(IntegrationSettingsSchemaBase):
service = colander.SchemaNode(
colander.String(),
title=lazy_ugettext('Slack service URL'),
description=h.literal(lazy_ugettext(
'This can be setup at the '
'<a href="https://my.slack.com/services/new/incoming-webhook/">'
'slack app manager</a>')),
default='',
preparer=strip_whitespace,
validator=colander.url,
dan
forms: add deform for integration settings forms
r518 widget=deform.widget.TextInputWidget(
placeholder='https://hooks.slack.com/services/...',
),
dan
integrations: add integration support...
r411 )
username = colander.SchemaNode(
colander.String(),
title=lazy_ugettext('Username'),
description=lazy_ugettext('Username to show notifications coming from.'),
missing='Rhodecode',
preparer=strip_whitespace,
dan
forms: add deform for integration settings forms
r518 widget=deform.widget.TextInputWidget(
placeholder='Rhodecode'
),
dan
integrations: add integration support...
r411 )
channel = colander.SchemaNode(
colander.String(),
title=lazy_ugettext('Channel'),
description=lazy_ugettext('Channel to send notifications to.'),
missing='',
preparer=strip_whitespace,
dan
forms: add deform for integration settings forms
r518 widget=deform.widget.TextInputWidget(
placeholder='#general'
),
dan
integrations: add integration support...
r411 )
icon_emoji = colander.SchemaNode(
colander.String(),
title=lazy_ugettext('Emoji'),
description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
missing='',
preparer=strip_whitespace,
dan
forms: add deform for integration settings forms
r518 widget=deform.widget.TextInputWidget(
placeholder=':studio_microphone:'
),
dan
integrations: add integration support...
r411 )
repo_push_template = Template(r'''
*${data['actor']['username']}* pushed to \
%if data['push']['branches']:
${len(data['push']['branches']) > 1 and 'branches' or 'branch'} \
${', '.join('<%s|%s>' % (branch['url'], branch['name']) for branch in data['push']['branches'])} \
%else:
unknown branch \
%endif
in <${data['repo']['url']}|${data['repo']['repo_name']}>
>>>
%for commit in data['push']['commits']:
<${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
%endfor
''')
class SlackIntegrationType(IntegrationTypeBase):
key = 'slack'
display_name = lazy_ugettext('Slack')
SettingsSchema = SlackSettingsSchema
valid_events = [
events.PullRequestCloseEvent,
events.PullRequestMergeEvent,
events.PullRequestUpdateEvent,
dan
events: add an event for pull request comments with review status
r443 events.PullRequestCommentEvent,
dan
integrations: add integration support...
r411 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)
dan
slack: add log messages for successful events
r414 log.debug('handling slack event for %s' % event.name)
dan
events: add an event for pull request comments with review status
r443 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):
dan
integrations: add integration support...
r411 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_slack, self.settings, text)
dan
forms: add deform for integration settings forms
r518 def settings_schema(self):
dan
integrations: add integration support...
r411 schema = SlackSettingsSchema()
schema.add(colander.SchemaNode(
colander.Set(),
dan
forms: add deform for integration settings forms
r518 widget=deform.widget.CheckboxChoiceWidget(
values=sorted(
[(e.name, e.display_name) for e in self.valid_events]
)
),
dan
integrations: add integration support...
r411 description="Events activated for this integration",
name='events'
))
dan
forms: add deform for integration settings forms
r518
dan
integrations: add integration support...
r411 return schema
dan
events: add an event for pull request comments with review status
r443 def format_pull_request_comment_event(self, event, data):
comment_text = data['comment']['text']
if len(comment_text) > 200:
comment_text = '<{comment_url}|{comment_text}...>'.format(
comment_text=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 <{pr_url}|#{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=data['pullrequest']['title'],
comment_text=comment_text
)
)
def format_pull_request_review_event(self, event, data):
return (textwrap.dedent(
'''
Status changed to {pr_status} for pull request <{pr_url}|#{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=data['pullrequest']['title'],
)
)
dan
integrations: add integration support...
r411 def format_pull_request_event(self, event, data):
action = {
events.PullRequestCloseEvent: 'closed',
events.PullRequestMergeEvent: 'merged',
events.PullRequestUpdateEvent: 'updated',
events.PullRequestCreateEvent: 'created',
dan
events: add an event for pull request comments with review status
r443 }.get(event.__class__, str(event.__class__))
dan
integrations: add integration support...
r411
dan
events: add an event for pull request comments with review status
r443 return ('Pull request <{url}|#{number}> - {title} '
dan
integrations: add integration support...
r411 '{action} by {user}').format(
user=data['actor']['username'],
number=data['pullrequest']['pull_request_id'],
url=data['pullrequest']['url'],
title=data['pullrequest']['title'],
action=action
)
def format_repo_push_event(self, data):
result = repo_push_template.render(
data=data,
html_to_slack_links=html_to_slack_links,
)
return result
dan
slack: fix wrong named function
r417 def format_repo_create_event(self, data):
dan
integrations: add integration support...
r411 return '<{}|{}> ({}) repository created by *{}*'.format(
data['repo']['url'],
data['repo']['repo_name'],
data['repo']['repo_type'],
data['actor']['username'],
)
def html_to_slack_links(message):
return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
r'<\1|\2>', message)
@task(ignore_result=True)
def post_text_to_slack(settings, text):
slack: fix log typo
r415 log.debug('sending %s to slack %s' % (text, settings['service']))
dan
integrations: add integration support...
r411 resp = requests.post(settings['service'], json={
"channel": settings.get('channel', ''),
"username": settings.get('username', 'Rhodecode'),
"text": text,
"icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
})
slack: fix log typo
r415 resp.raise_for_status() # raise exception on a failed request