mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 22:13:26 +00:00
Add github webhook integration.
This commit is contained in:
399
zerver/views/webhooks/github_webhook.py
Normal file
399
zerver/views/webhooks/github_webhook.py
Normal file
@@ -0,0 +1,399 @@
|
||||
from __future__ import absolute_import
|
||||
import re
|
||||
from functools import partial
|
||||
from six import text_type
|
||||
from typing import Any, Callable
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from zerver.lib.actions import check_send_message
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.models import Client, UserProfile
|
||||
from zerver.decorator import api_key_only_webhook_view, REQ, has_request_variables
|
||||
|
||||
from zerver.lib.webhooks.git import get_issue_event_message, SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE,\
|
||||
get_pull_request_event_message, SUBJECT_WITH_BRANCH_TEMPLATE,\
|
||||
get_push_commits_event_message, CONTENT_MESSAGE_TEMPLATE,\
|
||||
get_commits_comment_action_message, get_push_tag_event_message
|
||||
|
||||
class UnknownEventType(Exception):
|
||||
pass
|
||||
|
||||
def get_opened_or_update_pull_request_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
pull_request = payload['pull_request']
|
||||
action = payload['action']
|
||||
if action == 'synchronized':
|
||||
action = 'updated'
|
||||
assignee = None
|
||||
if pull_request.get('assignee'):
|
||||
assignee = pull_request['assignee']['login']
|
||||
|
||||
return get_pull_request_event_message(
|
||||
get_sender_name(payload),
|
||||
action,
|
||||
pull_request['html_url'],
|
||||
target_branch=pull_request['head']['ref'],
|
||||
base_branch=pull_request['base']['ref'],
|
||||
message=pull_request['body'],
|
||||
assignee=assignee
|
||||
)
|
||||
|
||||
def get_closed_pull_request_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
pull_request = payload['pull_request']
|
||||
action = 'merged' if pull_request['merged'] else 'closed without merge'
|
||||
return get_pull_request_event_message(
|
||||
get_sender_name(payload),
|
||||
action,
|
||||
pull_request['html_url'],
|
||||
)
|
||||
|
||||
def get_membership_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
action = payload['action']
|
||||
member = payload['member']
|
||||
scope = payload['scope']
|
||||
scope_object = payload[scope]
|
||||
|
||||
return u"{} {} [{}]({}) to {} {}".format(
|
||||
get_sender_name(payload),
|
||||
action,
|
||||
member['login'],
|
||||
member['html_url'],
|
||||
scope_object['name'],
|
||||
scope
|
||||
)
|
||||
|
||||
def get_member_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return u"{} {} [{}]({}) to [{}]({})".format(
|
||||
get_sender_name(payload),
|
||||
payload['action'],
|
||||
payload['member']['login'],
|
||||
payload['member']['html_url'],
|
||||
get_repository_name(payload),
|
||||
payload['repository']['html_url']
|
||||
)
|
||||
|
||||
def get_issue_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
action = payload['action']
|
||||
issue = payload['issue']
|
||||
assignee = issue['assignee']
|
||||
return get_issue_event_message(
|
||||
get_sender_name(payload),
|
||||
action,
|
||||
issue['html_url'],
|
||||
issue['number'],
|
||||
issue['body'],
|
||||
assignee=assignee['login'] if assignee else None
|
||||
)
|
||||
|
||||
def get_issue_comment_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
action = payload['action']
|
||||
comment = payload['comment']
|
||||
issue = payload['issue']
|
||||
|
||||
if action == 'created':
|
||||
action = '[commented]'
|
||||
else:
|
||||
action = '{} a [comment]'
|
||||
action += '({}) on'.format(comment['html_url'])
|
||||
|
||||
return get_issue_event_message(
|
||||
get_sender_name(payload),
|
||||
action,
|
||||
issue['html_url'],
|
||||
issue['number'],
|
||||
comment['body'],
|
||||
)
|
||||
|
||||
def get_fork_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
forkee = payload['forkee']
|
||||
return u"{} forked [{}]({})".format(
|
||||
get_sender_name(payload),
|
||||
forkee['name'],
|
||||
forkee['html_url']
|
||||
)
|
||||
|
||||
def get_deployment_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return u'{} created new deployment'.format(
|
||||
get_sender_name(payload),
|
||||
)
|
||||
|
||||
def get_change_deployment_status_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return u'Deployment changed status to {}'.format(
|
||||
payload['deployment_status']['state'],
|
||||
)
|
||||
|
||||
def get_create_or_delete_body(payload, action):
|
||||
# type: (Dict[str, Any], text_type) -> text_type
|
||||
ref_type = payload['ref_type']
|
||||
return u'{} {} {} {}'.format(
|
||||
get_sender_name(payload),
|
||||
action,
|
||||
ref_type,
|
||||
payload['ref']
|
||||
).rstrip()
|
||||
|
||||
def get_commit_comment_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
comment = payload['comment']
|
||||
comment_url = comment['html_url']
|
||||
commit_url = comment_url.split('#', 1)[0]
|
||||
action = u'[commented]({})'.format(comment_url)
|
||||
return get_commits_comment_action_message(
|
||||
get_sender_name(payload),
|
||||
action,
|
||||
commit_url,
|
||||
comment.get('commit_id'),
|
||||
comment['body'],
|
||||
)
|
||||
|
||||
def get_push_tags_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return get_push_tag_event_message(
|
||||
get_sender_name(payload),
|
||||
get_tag_name_from_ref(payload['ref']),
|
||||
action='pushed' if payload.get('created') else 'removed'
|
||||
)
|
||||
|
||||
def get_push_commits_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
commits_data = [{
|
||||
'sha': commit['id'],
|
||||
'url': commit['url'],
|
||||
'message': commit['message']
|
||||
} for commit in payload['commits']]
|
||||
return get_push_commits_event_message(
|
||||
get_sender_name(payload),
|
||||
payload['compare'],
|
||||
get_branch_name_from_ref(payload['ref']),
|
||||
commits_data
|
||||
)
|
||||
|
||||
def get_public_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return u"{} made [the repository]({}) public".format(
|
||||
get_sender_name(payload),
|
||||
payload['repository']['html_url'],
|
||||
)
|
||||
|
||||
def get_wiki_pages_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
wiki_page_info_template = u"* {action} [{title}]({url})\n"
|
||||
wiki_info = u''
|
||||
for page in payload['pages']:
|
||||
wiki_info += wiki_page_info_template.format(
|
||||
action=page['action'],
|
||||
title=page['title'],
|
||||
url=page['html_url'],
|
||||
)
|
||||
return u"{}:\n{}".format(get_sender_name(payload), wiki_info.rstrip())
|
||||
|
||||
def get_watch_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return u"{} starred [the repository]({})".format(
|
||||
get_sender_name(payload),
|
||||
payload['repository']['html_url']
|
||||
)
|
||||
|
||||
def get_repository_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return u"{} {} [the repository]({})".format(
|
||||
get_sender_name(payload),
|
||||
payload.get('action'),
|
||||
payload['repository']['html_url']
|
||||
)
|
||||
|
||||
def get_add_team_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return u"[The repository]({}) was added to team {}".format(
|
||||
payload['repository']['html_url'],
|
||||
payload['team']['name']
|
||||
)
|
||||
|
||||
def get_release_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return u"{} published [the release]({})".format(
|
||||
get_sender_name(payload),
|
||||
payload['release']['html_url'],
|
||||
)
|
||||
|
||||
def get_page_build_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
build = payload['build']
|
||||
action = build['status']
|
||||
if action == 'null':
|
||||
action = u'has yet to be built'
|
||||
elif action == 'building':
|
||||
action = u'is being building'
|
||||
elif action == 'errored':
|
||||
action = u'is errored{}'.format(
|
||||
CONTENT_MESSAGE_TEMPLATE.format(message=build['error']['message'])
|
||||
)
|
||||
else:
|
||||
action = u'is {}'.format(action)
|
||||
return u"Github Pages build, trigerred by {}, {}".format(
|
||||
payload['build']['pusher']['login'],
|
||||
action
|
||||
)
|
||||
|
||||
def get_status_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
if payload['target_url']:
|
||||
status = '[{}]({})'.format(
|
||||
payload['state'],
|
||||
payload['target_url']
|
||||
)
|
||||
else:
|
||||
status = payload['state']
|
||||
return u"[{}]({}) changed it's status to {}".format(
|
||||
payload['sha'][:7], # TODO
|
||||
payload['commit']['html_url'],
|
||||
status
|
||||
)
|
||||
|
||||
def get_pull_request_review_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return get_pull_request_event_message(
|
||||
get_sender_name(payload),
|
||||
'submitted',
|
||||
payload['review']['html_url'],
|
||||
type='PR Review'
|
||||
)
|
||||
|
||||
def get_pull_request_review_comment_body(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
action = payload['action']
|
||||
message = None
|
||||
if action == 'created':
|
||||
message = payload['comment']['body']
|
||||
|
||||
return get_pull_request_event_message(
|
||||
get_sender_name(payload),
|
||||
action,
|
||||
payload['comment']['html_url'],
|
||||
message=message,
|
||||
type='PR Review Comment'
|
||||
)
|
||||
|
||||
def get_repository_name(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return payload['repository']['name']
|
||||
|
||||
def get_sender_name(payload):
|
||||
# type: (Dict[str, Any]) -> text_type
|
||||
return payload['sender']['login']
|
||||
|
||||
def get_branch_name_from_ref(ref_string):
|
||||
# type: (text_type) -> text_type
|
||||
return re.sub(r'^refs/heads/', '', ref_string)
|
||||
|
||||
def get_tag_name_from_ref(ref_string):
|
||||
# type: (text_type) -> text_type
|
||||
return re.sub(r'^refs/tags/', '', ref_string)
|
||||
|
||||
def is_commit_push_event(payload):
|
||||
# type: (Dict[str, Any]) -> bool
|
||||
return bool(re.match(r'^refs/heads/', payload['ref']))
|
||||
|
||||
def get_subject_based_on_type(payload, event):
|
||||
# type: (Dict[str, Any], text_type) -> text_type
|
||||
if 'pull_request' in event:
|
||||
return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
||||
repo=get_repository_name(payload),
|
||||
type='PR',
|
||||
id=payload['pull_request']['number'],
|
||||
title=payload['pull_request']['title']
|
||||
)
|
||||
elif event.startswith('issue'):
|
||||
return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
||||
repo=get_repository_name(payload),
|
||||
type='Issue',
|
||||
id=payload['issue']['number'],
|
||||
title=payload['issue']['title']
|
||||
)
|
||||
elif event.startswith('deployment'):
|
||||
return u"{} / Deployment on {}".format(
|
||||
get_repository_name(payload),
|
||||
payload['deployment']['environment']
|
||||
)
|
||||
elif event == 'membership':
|
||||
return u"{} organization".format(payload['organization']['login'])
|
||||
elif event == 'push_commits':
|
||||
return SUBJECT_WITH_BRANCH_TEMPLATE.format(
|
||||
repo=get_repository_name(payload),
|
||||
branch=get_branch_name_from_ref(payload['ref'])
|
||||
)
|
||||
elif event == 'gollum':
|
||||
return SUBJECT_WITH_BRANCH_TEMPLATE.format(
|
||||
repo=get_repository_name(payload),
|
||||
branch='Wiki Pages'
|
||||
)
|
||||
return get_repository_name(payload)
|
||||
|
||||
EVENT_FUNCTION_MAPPER = {
|
||||
'team_add': get_add_team_body,
|
||||
'commit_comment': get_commit_comment_body,
|
||||
'closed_pull_request': get_closed_pull_request_body,
|
||||
'create': partial(get_create_or_delete_body, action='created'),
|
||||
'delete': partial(get_create_or_delete_body, action='deleted'),
|
||||
'deployment': get_deployment_body,
|
||||
'deployment_status': get_change_deployment_status_body,
|
||||
'fork': get_fork_body,
|
||||
'gollum': get_wiki_pages_body,
|
||||
'issue_comment': get_issue_comment_body,
|
||||
'issue': get_issue_body,
|
||||
'member': get_member_body,
|
||||
'membership': get_membership_body,
|
||||
'opened_or_update_pull_request': get_opened_or_update_pull_request_body,
|
||||
'page_build': get_page_build_body,
|
||||
'public': get_public_body,
|
||||
'pull_request_review': get_pull_request_review_body,
|
||||
'pull_request_review_comment': get_pull_request_review_comment_body,
|
||||
'push_commits': get_push_commits_body,
|
||||
'push_tags': get_push_tags_body,
|
||||
'release': get_release_body,
|
||||
'repository': get_repository_body,
|
||||
'status': get_status_body,
|
||||
'watch': get_watch_body,
|
||||
}
|
||||
|
||||
@api_key_only_webhook_view('Github')
|
||||
@has_request_variables
|
||||
def api_github_webhook(
|
||||
request, user_profile, client,
|
||||
payload=REQ(argument_type='body'), stream=REQ(default='github')):
|
||||
# type: (HttpRequest, UserProfile, Client, Dict[str, Any], text_type) -> HttpResponse
|
||||
event = get_event(request, payload)
|
||||
subject = get_subject_based_on_type(payload, event)
|
||||
body = get_body_function_based_on_type(event)(payload)
|
||||
check_send_message(user_profile, client, 'stream', [stream], subject, body)
|
||||
return json_success()
|
||||
|
||||
def get_event(request, payload):
|
||||
# type: (HttpRequest, Dict[str, Any]) -> str
|
||||
event = request.META['HTTP_X_GITHUB_EVENT']
|
||||
if event == 'pull_request':
|
||||
action = payload['action']
|
||||
if action == 'opened' or action == 'synchronized':
|
||||
return 'opened_or_update_pull_request'
|
||||
if action == 'closed':
|
||||
return 'closed_pull_request'
|
||||
raise UnknownEventType(u'Event pull_request with {} action is unsupported'.format(action))
|
||||
if event == 'push':
|
||||
if is_commit_push_event(payload):
|
||||
return "push_commits"
|
||||
else:
|
||||
return "push_tags"
|
||||
elif event in list(EVENT_FUNCTION_MAPPER.keys()):
|
||||
return event
|
||||
raise UnknownEventType(u'Event {} is unknown and cannot be handled'.format(event))
|
||||
|
||||
def get_body_function_based_on_type(type):
|
||||
# type: (str) -> Any
|
||||
return EVENT_FUNCTION_MAPPER.get(type)
|
||||
Reference in New Issue
Block a user