Files
zulip/zerver/views/webhooks/bitbucket2.py
Tomasz Kolek d0066c37ff Add bitbucket2 integration.
Bitbucket changed the format of their API.  The old format is still
useful for BitBucket enterprise, but for the main cloud verison of
Bitbucket, we need a new BitBucket integration supporting the new API.
2016-07-12 17:34:34 -07:00

205 lines
8.3 KiB
Python

# Webhooks for external integrations.
from __future__ import absolute_import
import re
from functools import partial
from typing import Any, Callable
from django.http import HttpRequest, HttpResponse
from django.utils.translation import ugettext as _
from zerver.lib.actions import check_send_message
from zerver.lib.response import json_success, json_error
from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view
from zerver.models import Client, UserProfile
BITBUCKET_SUBJECT_TEMPLATE = '{repository_name}'
BITBUCKET_PUSH_BODY = 'User {display_name}(login: {username}) pushed [{number_of_commits} commit{s}]({commits_url}) into {branch} branch.'
BITBUCKET_FORK_BODY = 'User {display_name}(login: {username}) forked the repository into [{fork_name}]({fork_url}).'
BITBUCKET_COMMIT_COMMENT_BODY = 'User {display_name}(login: {username}) added [comment]({url_to_comment}) to commit.'
BITBUCKET_COMMIT_STATUS_CHANGED_BODY = '[System {key}]({system_url}) changed status of {commit_info} to {status}.'
BITBUCKET_ISSUE_ACTION_BODY = 'User {display_name}(login: {username}) {action} [an issue]({issue_url})'
BITBUCKET_PULL_REQUEST_ACTION_BODY = 'User {display_name}(login: {username}) {action} ["{title}" pull request]({pull_request_url})'
BITBUCKET_PULL_REQUEST_COMMENT_ACTION_BODY = 'User {display_name}(login: {username}) {action} [comment]({comment_url} in ["{title}" pull request]({pull_request_url})'
PULL_REQUEST_SUPPORTED_ACTIONS = [
'approved',
'unapproved',
'created',
'updated',
'rejected',
'merged',
'comment_created',
'comment_updated',
'comment_deleted',
]
class UnknownTriggerType(Exception):
pass
@api_key_only_webhook_view('Bitbucket2')
@has_request_variables
def api_bitbucket2_webhook(request, user_profile, client, payload=REQ(argument_type='body'), stream=REQ(default='bitbucket')):
# type: (HttpRequest, UserProfile, Client, Dict[str, Any], str) -> HttpResponse
try:
subject = get_subject(payload)
type = get_type(request, payload)
body = get_body_based_on_type(type)(payload)
except KeyError as e:
return json_error(_("Missing key {} in JSON").format(e.message))
check_send_message(user_profile, client, 'stream', [stream], subject, body)
return json_success()
def get_subject(payload):
# type: (Dict[str, Any]) -> str
return BITBUCKET_SUBJECT_TEMPLATE.format(repository_name=payload['repository']['name'])
def get_type(request, payload):
# type: (HttpRequest, Dict[str, Any]) -> str
event_key = request.META.get("HTTP_X_EVENT_KEY")
if payload.get('push'):
return 'push'
elif payload.get('fork'):
return 'fork'
elif payload.get('comment') and payload.get('commit'):
return 'commit_comment'
elif payload.get('commit_status'):
return 'change_commit_status'
elif payload.get('issue'):
if payload.get('changes'):
return "issue_updated"
if payload.get('comment'):
return 'issue_commented'
return "issue_created"
elif payload.get('pullrequest'):
pull_request_template = 'pull_request_{}'
action = re.match('pullrequest:(?P<action>.*)$', event_key)
if action:
action = action.group('action')
if action in PULL_REQUEST_SUPPORTED_ACTIONS:
return pull_request_template.format(action)
raise UnknownTriggerType()
def get_body_based_on_type(type):
# type: (str) -> Any
return GET_BODY_DEPENDING_ON_TYPE_MAPPER.get(type)
def get_push_body(payload):
# type: (Dict[str, Any]) -> str
change = payload['push']['changes'][-1]
number_of_commits = len(change['commits'])
return BITBUCKET_PUSH_BODY.format(
display_name=get_user_display_name(payload),
username=get_user_username(payload),
number_of_commits=number_of_commits if number_of_commits <= 5 else "more than 5",
s='' if number_of_commits == 1 else 's',
commits_url=change['links']['html']['href'],
branch=change['new']['name']
)
def get_fork_body(payload):
# type: (Dict[str, Any]) -> str
return BITBUCKET_FORK_BODY.format(
display_name=get_user_display_name(payload),
username=get_user_username(payload),
fork_name=get_repository_name(payload['fork']),
fork_url=get_repository_url(payload['fork'])
)
def get_commit_comment_body(payload):
# type: (Dict[str, Any]) -> str
return BITBUCKET_COMMIT_COMMENT_BODY.format(
display_name=get_user_display_name(payload),
username=get_user_username(payload),
url_to_comment=payload['comment']['links']['html']['href']
)
def get_commit_status_changed_body(payload):
# type: (Dict[str, Any]) -> str
commit_id = re.match('.*/commit/(?P<commit_id>[A-Za-z0-9]*$)', payload['commit_status']['links']['commit']['href'])
if commit_id:
commit_info = "{}/{}".format(get_repository_url(payload['repository']), commit_id.group('commit_id'))
else:
commit_info = 'commit'
return BITBUCKET_COMMIT_STATUS_CHANGED_BODY.format(
key=payload['commit_status']['key'],
system_url=payload['commit_status']['url'],
commit_info=commit_info,
status=payload['commit_status']['state']
)
def get_issue_action_body(payload, action):
# type: (Dict[str, Any], str) -> str
return BITBUCKET_ISSUE_ACTION_BODY.format(
display_name=get_user_display_name(payload),
username=get_user_username(payload),
action=action,
issue_url=payload['issue']['links']['html']['href']
)
def get_pull_request_action_body(payload, action):
# type: (Dict[str, Any], str) -> str
return BITBUCKET_PULL_REQUEST_ACTION_BODY.format(
display_name=get_user_display_name(payload),
username=get_user_username(payload),
action=action,
title=get_pull_request_title(payload['pullrequest']),
pull_request_url=get_pull_request_url(payload['pullrequest'])
)
def get_pull_request_comment_action_body(payload, action):
# type: (Dict[str, Any], str) -> str
return BITBUCKET_PULL_REQUEST_COMMENT_ACTION_BODY.format(
display_name=get_user_display_name(payload),
username=get_user_username(payload),
action=action,
comment_url=payload['comment']['links']['html']['href'],
title=get_pull_request_title(payload['pullrequest']),
pull_request_url=get_pull_request_url(payload['pullrequest'])
)
def get_pull_request_title(pullrequest_payload):
# type: (Dict[str, Any]) -> str
return pullrequest_payload['title']
def get_pull_request_url(pullrequest_payload):
# type: (Dict[str, Any]) -> str
return pullrequest_payload['links']['html']['href']
def get_repository_url(repository_payload):
# type: (Dict[str, Any]) -> str
return repository_payload['links']['html']['href']
def get_repository_name(repository_payload):
# type: (Dict[str, Any]) -> str
return repository_payload['full_name']
def get_user_display_name(payload):
# type: (Dict[str, Any]) -> str
return payload['actor']['display_name']
def get_user_username(payload):
# type: (Dict[str, Any]) -> str
return payload['actor']['username']
GET_BODY_DEPENDING_ON_TYPE_MAPPER = {
'push': get_push_body,
'fork': get_fork_body,
'commit_comment': get_commit_comment_body,
'change_commit_status': get_commit_status_changed_body,
'issue_updated': partial(get_issue_action_body, action='updated'),
'issue_created': partial(get_issue_action_body, action='created'),
'issue_commented': partial(get_issue_action_body, action='commented'),
'pull_request_created': partial(get_pull_request_action_body, action='created'),
'pull_request_updated': partial(get_pull_request_action_body, action='updated'),
'pull_request_approved': partial(get_pull_request_action_body, action='approved'),
'pull_request_unapproved': partial(get_pull_request_action_body, action='unapproved'),
'pull_request_merged': partial(get_pull_request_action_body, action='merged'),
'pull_request_rejected': partial(get_pull_request_action_body, action='rejected'),
'pull_request_comment_created': partial(get_pull_request_comment_action_body, action='created'),
'pull_request_comment_updated': partial(get_pull_request_comment_action_body, action='updated'),
'pull_request_comment_deleted': partial(get_pull_request_comment_action_body, action='deleted')
}