webhooks: Filter specific BitBucket Git branches.

This is the last of the Git integrations that didn't have this
feature.

Fixes #4327.
This commit is contained in:
Eeshan Garg
2017-04-04 22:22:31 -02:30
committed by Tim Abbott
parent 534c951ec4
commit ce0f1453b9
6 changed files with 192 additions and 14 deletions

View File

@@ -21,8 +21,14 @@
<p>
In the URL field, enter
<code>{{ external_uri_scheme }}bot_email:bot_api_key@{{ external_api_path_subdomain }}/v1/external/bitbucket</code>:
<code>{{ external_uri_scheme }}bot_email:bot_api_key@{{ external_api_path_subdomain }}/v1/external/bitbucket</code>
</p>
<p>
Or, you can also limit the notifications you receive to specific
branches by specifying them in a comma-separated list at the end
of the URL, like so:
</p>
<p><code>{{ external_uri_scheme }}bot_email:bot_api_key@{{ external_api_path_subdomain }}/v1/external/bitbucket?branches=master,development</code></p>
<img class="screenshot" src="/static/images/integrations/bitbucket/001.png"/>
<p>

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from six import text_type
from typing import Dict, Union
from mock import patch, MagicMock
from typing import Dict, Union, Text, Optional
from zerver.lib.test_classes import WebhookTestCase
class BitbucketHookTests(WebhookTestCase):
@@ -18,6 +19,15 @@ class BitbucketHookTests(WebhookTestCase):
expected_message = u"kolaszek pushed 1 commit to branch master. Commits by kolaszek(1)\n\n{}".format(commit_info)
self.send_and_test_stream_message(fixture_name, self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message, **self.api_auth(self.TEST_USER_EMAIL))
def test_bitbucket_on_push_event_filtered_by_branches(self):
# type: () -> None
fixture_name = 'push'
self.url = self.build_webhook_url(payload=self.get_body(fixture_name),
branches='master,development')
commit_info = u'* c ([25f93d2](https://bitbucket.org/kolaszek/repository-name/commits/25f93d22b719e2d678a7ad5ee0ef0d1fcdf39c12))'
expected_message = u"kolaszek pushed 1 commit to branch master. Commits by kolaszek(1)\n\n{}".format(commit_info)
self.send_and_test_stream_message(fixture_name, self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message, **self.api_auth(self.TEST_USER_EMAIL))
def test_bitbucket_on_push_commits_above_limit_event(self):
# type: () -> None
fixture_name = 'push_commits_above_limit'
@@ -26,6 +36,15 @@ class BitbucketHookTests(WebhookTestCase):
expected_message = u"kolaszek pushed 50 commits to branch master. Commits by kolaszek(50)\n\n{}[and 30 more commit(s)]".format(commit_info * 20)
self.send_and_test_stream_message(fixture_name, self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message, **self.api_auth(self.TEST_USER_EMAIL))
def test_bitbucket_on_push_commits_above_limit_event_filtered_by_branches(self):
# type: () -> None
fixture_name = 'push_commits_above_limit'
self.url = self.build_webhook_url(payload=self.get_body(fixture_name),
branches='master,development')
commit_info = u'* c ([25f93d2](https://bitbucket.org/kolaszek/repository-name/commits/25f93d22b719e2d678a7ad5ee0ef0d1fcdf39c12))\n'
expected_message = u"kolaszek pushed 50 commits to branch master. Commits by kolaszek(50)\n\n{}[and 30 more commit(s)]".format(commit_info * 20)
self.send_and_test_stream_message(fixture_name, self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message, **self.api_auth(self.TEST_USER_EMAIL))
def test_bitbucket_on_force_push_event(self):
# type: () -> None
fixture_name = 'force_push'
@@ -33,6 +52,33 @@ class BitbucketHookTests(WebhookTestCase):
expected_message = u"kolaszek [force pushed](https://bitbucket.org/kolaszek/repository-name)"
self.send_and_test_stream_message(fixture_name, self.EXPECTED_SUBJECT, expected_message, **self.api_auth(self.TEST_USER_EMAIL))
@patch('zerver.webhooks.bitbucket.view.check_send_message')
def test_bitbucket_on_push_event_filtered_by_branches_ignore(self, check_send_message_mock):
# type: (MagicMock) -> None
fixture_name = 'push'
payload = self.get_body(fixture_name)
self.url = self.build_webhook_url(payload=payload,
branches='changes,development')
result = self.client_post(self.url, payload,
content_type="application/json,",
**self.api_auth(self.TEST_USER_EMAIL))
self.assertFalse(check_send_message_mock.called)
self.assert_json_success(result)
@patch('zerver.webhooks.bitbucket.view.check_send_message')
def test_bitbucket_push_commits_above_limit_filtered_by_branches_ignore(
self, check_send_message_mock):
# type: (MagicMock) -> None
fixture_name = 'push_commits_above_limit'
payload = self.get_body(fixture_name)
self.url = self.build_webhook_url(payload=payload,
branches='changes,development')
result = self.client_post(self.url, payload,
content_type="application/json,",
**self.api_auth(self.TEST_USER_EMAIL))
self.assertFalse(check_send_message_mock.called)
self.assert_json_success(result)
def get_body(self, fixture_name):
# type: (text_type) -> Union[text_type, Dict[str, text_type]]
# type: (Text) -> Union[Text, Dict[str, Text]]
return self.fixture_data(self.FIXTURE_DIR_NAME, fixture_name)

View File

@@ -1,6 +1,6 @@
from __future__ import absolute_import
from typing import Any, Mapping, Text
from typing import Any, Mapping, Text, Optional
from django.http import HttpRequest, HttpResponse
@@ -15,8 +15,8 @@ from zerver.lib.webhooks.git import get_push_commits_event_message, SUBJECT_WITH
@authenticated_rest_api_view(is_webhook=True)
@has_request_variables
def api_bitbucket_webhook(request, user_profile, payload=REQ(validator=check_dict([])),
stream=REQ(default='commits')):
# type: (HttpRequest, UserProfile, Mapping[Text, Any], Text) -> HttpResponse
stream=REQ(default='commits'), branches=REQ(default=None)):
# type: (HttpRequest, UserProfile, Mapping[Text, Any], Text, Optional[Text]) -> HttpResponse
repository = payload['repository']
commits = [
@@ -41,6 +41,8 @@ def api_bitbucket_webhook(request, user_profile, payload=REQ(validator=check_dic
payload['canon_url'] + repository['absolute_url']))
else:
branch = payload['commits'][-1]['branch']
if branches is not None and branches.find(branch) == -1:
return json_success()
content = get_push_commits_event_message(payload.get('user'), None, branch, commits)
subject = SUBJECT_WITH_BRANCH_TEMPLATE.format(repo=repository['name'], branch=branch)

View File

@@ -14,12 +14,17 @@
<p>The URL you create will be in the following format:</p>
<p><code>{{ external_api_uri_subdomain }}/v1/external/bitbucket2?api_key=abcdefgh&amp;stream=bitbucket</code></p>
<p>
where <code>api_key</code> is the API key of your Zulip bot,
and <code>stream</code> is the stream name you want the
notifications sent to.
</p>
<p>
You can also limit the notifications you receive to specific
branches by specifying them in a comma-separated list at the end
of the URL, like so:
</p>
<p><code>{{ external_api_uri_subdomain }}/v1/external/bitbucket2?api_key=abcdefgh&amp;stream=bitbucket&amp;branches=master,development</code></p>
<p>
Next, from your repository's web page, go to the Settings page and choose Webhooks on the left-hand side.

View File

@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
from mock import patch, MagicMock
from typing import Optional, Text
from zerver.lib.test_classes import WebhookTestCase
class Bitbucket2HookTests(WebhookTestCase):
@@ -28,6 +31,27 @@ class Bitbucket2HookTests(WebhookTestCase):
expected_message = u"""kolaszek [pushed](https://bitbucket.org/kolaszek/repository-name/branch/master) 10 commits to branch master. Commits by Tomasz(4), James(3), Brendon(2) and others(1)\n\n{}* first commit ([84b96ad](https://bitbucket.org/kolaszek/repository-name/commits/84b96adc644a30fd6465b3d196369d880762afed))""".format(commit_info*9)
self.send_and_test_stream_message('v2_push_multiple_committers_with_others', self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message)
def test_bitbucket2_on_push_commits_multiple_committers_filtered_by_branches(self):
# type: () -> None
self.url = self.build_webhook_url(branches='master,development')
commit_info = u'* first commit ([84b96ad](https://bitbucket.org/kolaszek/repository-name/commits/84b96adc644a30fd6465b3d196369d880762afed))\n'
expected_message = u"""kolaszek [pushed](https://bitbucket.org/kolaszek/repository-name/branch/master) 3 commits to branch master. Commits by Benjamin(2) and Tomasz(1)\n\n{}* first commit ([84b96ad](https://bitbucket.org/kolaszek/repository-name/commits/84b96adc644a30fd6465b3d196369d880762afed))""".format(commit_info*2)
self.send_and_test_stream_message('v2_push_multiple_committers', self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message)
def test_bitbucket2_on_push_commits_multiple_committers_with_others_filtered_by_branches(self):
# type: () -> None
self.url = self.build_webhook_url(branches='master,development')
commit_info = u'* first commit ([84b96ad](https://bitbucket.org/kolaszek/repository-name/commits/84b96adc644a30fd6465b3d196369d880762afed))\n'
expected_message = u"""kolaszek [pushed](https://bitbucket.org/kolaszek/repository-name/branch/master) 10 commits to branch master. Commits by Tomasz(4), James(3), Brendon(2) and others(1)\n\n{}* first commit ([84b96ad](https://bitbucket.org/kolaszek/repository-name/commits/84b96adc644a30fd6465b3d196369d880762afed))""".format(commit_info*9)
self.send_and_test_stream_message('v2_push_multiple_committers_with_others', self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message)
def test_bitbucket2_on_push_event_filtered_by_branches(self):
# type: () -> None
self.url = self.build_webhook_url(branches='master,development')
commit_info = u'* first commit ([84b96ad](https://bitbucket.org/kolaszek/repository-name/commits/84b96adc644a30fd6465b3d196369d880762afed))'
expected_message = u"kolaszek [pushed](https://bitbucket.org/kolaszek/repository-name/branch/master) 1 commit to branch master. Commits by Tomasz(1)\n\n{}".format(commit_info)
self.send_and_test_stream_message('v2_push', self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message)
def test_bitbucket2_on_push_commits_above_limit_event(self):
# type: () -> None
commit_info = '* a ([6f161a7](https://bitbucket.org/kolaszek/repository-name/commits/6f161a7bced94430ac8947d87dbf45c6deee3fb0))\n'
@@ -36,11 +60,26 @@ class Bitbucket2HookTests(WebhookTestCase):
)
self.send_and_test_stream_message('v2_push_commits_above_limit', self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message)
def test_bitbucket2_on_push_commits_above_limit_filtered_by_branches(self):
# type: () -> None
self.url = self.build_webhook_url(branches='master,development')
commit_info = '* a ([6f161a7](https://bitbucket.org/kolaszek/repository-name/commits/6f161a7bced94430ac8947d87dbf45c6deee3fb0))\n'
expected_message = u"kolaszek [pushed](https://bitbucket.org/kolaszek/repository-name/branches/compare/6f161a7bced94430ac8947d87dbf45c6deee3fb0..1221f2fda6f1e3654b09f1f3a08390e4cb25bb48) 5 commits to branch master. Commits by Tomasz(5)\n\n{}[and more commit(s)]".format(
(commit_info * 5),
)
self.send_and_test_stream_message('v2_push_commits_above_limit', self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message)
def test_bitbucket2_on_force_push_event(self):
# type: () -> None
expected_message = u"kolaszek [force pushed](https://bitbucket.org/kolaszek/repository-name/branch/master) to branch master. Head is now 25f93d22b719e2d678a7ad5ee0ef0d1fcdf39c12"
self.send_and_test_stream_message('v2_force_push', self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message)
def test_bitbucket2_on_force_push_event_filtered_by_branches(self):
# type: () -> None
self.url = self.build_webhook_url(branches='master,development')
expected_message = u"kolaszek [force pushed](https://bitbucket.org/kolaszek/repository-name/branch/master) to branch master. Head is now 25f93d22b719e2d678a7ad5ee0ef0d1fcdf39c12"
self.send_and_test_stream_message('v2_force_push', self.EXPECTED_SUBJECT_BRANCH_EVENTS, expected_message)
def test_bitbucket2_on_remove_branch_event(self):
# type: () -> None
expected_message = u"kolaszek deleted branch master"
@@ -190,3 +229,78 @@ class Bitbucket2HookTests(WebhookTestCase):
msg = self.get_last_message()
self.do_test_message(msg, 'kolaszek pushed tag [a](https://bitbucket.org/kolaszek/repository-name/commits/tag/a)')
self.do_test_subject(msg, self.EXPECTED_SUBJECT)
def test_bitbucket2_on_more_than_one_push_event_filtered_by_branches(self):
# type: () -> None
self.url = self.build_webhook_url(branches='master,development')
kwargs = {
"HTTP_X_EVENT_KEY": 'pullrequest:push'
}
self.send_and_test_stream_message('v2_more_than_one_push_event', **kwargs)
msg = self.get_second_to_last_message()
self.do_test_message(msg, 'kolaszek [pushed](https://bitbucket.org/kolaszek/repository-name/branch/master) 1 commit to branch master. Commits by Tomasz(1)\n\n* first commit ([84b96ad](https://bitbucket.org/kolaszek/repository-name/commits/84b96adc644a30fd6465b3d196369d880762afed))')
self.do_test_subject(msg, self.EXPECTED_SUBJECT_BRANCH_EVENTS)
msg = self.get_last_message()
self.do_test_message(msg, 'kolaszek pushed tag [a](https://bitbucket.org/kolaszek/repository-name/commits/tag/a)')
self.do_test_subject(msg, self.EXPECTED_SUBJECT)
def test_bitbucket2_on_more_than_one_push_event_filtered_by_branches_ignore(self):
# type: () -> None
self.url = self.build_webhook_url(branches='changes,development')
kwargs = {
"HTTP_X_EVENT_KEY": 'pullrequest:push'
}
expected_message = u"kolaszek pushed tag [a](https://bitbucket.org/kolaszek/repository-name/commits/tag/a)"
self.send_and_test_stream_message('v2_more_than_one_push_event',
self.EXPECTED_SUBJECT,
expected_message, **kwargs)
@patch('zerver.webhooks.bitbucket2.view.check_send_message')
def test_bitbucket2_on_push_event_filtered_by_branches_ignore(
self, check_send_message_mock):
# type: (MagicMock) -> None
self.url = self.build_webhook_url(branches='changes,devlopment')
payload = self.get_body('v2_push')
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_message_mock.called)
self.assert_json_success(result)
@patch('zerver.webhooks.bitbucket2.view.check_send_message')
def test_bitbucket2_on_push_commits_above_limit_filtered_by_branches_ignore(
self, check_send_message_mock):
# type: (MagicMock) -> None
self.url = self.build_webhook_url(branches='changes,devlopment')
payload = self.get_body('v2_push_commits_above_limit')
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_message_mock.called)
self.assert_json_success(result)
@patch('zerver.webhooks.bitbucket2.view.check_send_message')
def test_bitbucket2_on_force_push_event_filtered_by_branches_ignore(
self, check_send_message_mock):
# type: (MagicMock) -> None
self.url = self.build_webhook_url(branches='changes,devlopment')
payload = self.get_body('v2_force_push')
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_message_mock.called)
self.assert_json_success(result)
@patch('zerver.webhooks.bitbucket2.view.check_send_message')
def test_bitbucket2_on_push_multiple_committers_filtered_by_branches_ignore(
self, check_send_message_mock):
# type: (MagicMock) -> None
self.url = self.build_webhook_url(branches='changes,devlopment')
payload = self.get_body('v2_push_multiple_committers')
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_message_mock.called)
self.assert_json_success(result)
@patch('zerver.webhooks.bitbucket2.view.check_send_message')
def test_bitbucket2_on_push_multiple_committers_with_others_filtered_by_branches_ignore(
self, check_send_message_mock):
# type: (MagicMock) -> None
self.url = self.build_webhook_url(branches='changes,devlopment')
payload = self.get_body('v2_push_multiple_committers_with_others')
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_message_mock.called)
self.assert_json_success(result)

View File

@@ -42,8 +42,8 @@ class UnknownTriggerType(Exception):
@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
stream=REQ(default='bitbucket'), branches=REQ(default=None)):
# type: (HttpRequest, UserProfile, Client, Dict[str, Any], str, Optional[Text]) -> HttpResponse
try:
type = get_type(request, payload)
if type != 'push':
@@ -51,6 +51,10 @@ def api_bitbucket2_webhook(request, user_profile, client, payload=REQ(argument_t
body = get_body_based_on_type(type)(payload)
check_send_message(user_profile, client, 'stream', [stream], subject, body)
else:
branch = get_branch_name_for_push_event(payload)
if branch and branches:
if branches.find(branch) == -1:
return json_success()
subjects = get_push_subjects(payload)
bodies_list = get_push_bodies(payload)
for body, subject in zip(bodies_list, subjects):
@@ -339,12 +343,13 @@ def get_user_username(payload):
return payload['actor']['username']
def get_branch_name_for_push_event(payload):
# type: (Dict[str, Any]) -> str
# type: (Dict[str, Any]) -> Optional[str]
change = payload['push']['changes'][-1]
if change.get('new'):
return change['new']['name']
potential_tag = (change['new'] or change['old'] or {}).get('type')
if potential_tag == 'tag':
return None
else:
return change['old']['name']
return (change['new'] or change['old']).get('name')
GET_SINGLE_MESSAGE_BODY_DEPENDING_ON_TYPE_MAPPER = {
'fork': get_fork_body,