diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-02-08 21:13:59 -0500 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-02-08 21:13:59 -0500 |
commit | 6f94ce6af3072543ee62d64016931ed424f800a7 (patch) | |
tree | 348d250d786dba40c14828cef3fb347116a0fa1a | |
parent | 02f7c8d33d60b41f5c2a5335f9f6ee56f236a7fe (diff) |
Add Bitbucket webhook
24 files changed, 279 insertions, 1 deletions
diff --git a/README.markdown b/README.markdown index 21f2ebe9..9b012ad0 100644 --- a/README.markdown +++ b/README.markdown @@ -86,6 +86,7 @@ Documentation ### Integrations +- [Bitbucket webhooks](docs/bitbucket-webhooks.markdown) - [Github webhooks](docs/github-webhooks.markdown) - [Gitlab webhooks](docs/gitlab-webhooks.markdown) diff --git a/app/Action/TaskClose.php b/app/Action/TaskClose.php index 760dfd84..b7cd4dbf 100644 --- a/app/Action/TaskClose.php +++ b/app/Action/TaskClose.php @@ -4,6 +4,7 @@ namespace Action; use Integration\GitlabWebhook; use Integration\GithubWebhook; +use Integration\BitbucketWebhook; use Model\Task; /** @@ -28,6 +29,7 @@ class TaskClose extends Base GithubWebhook::EVENT_ISSUE_CLOSED, GitlabWebhook::EVENT_COMMIT, GitlabWebhook::EVENT_ISSUE_CLOSED, + BitbucketWebhook::EVENT_COMMIT, ); } @@ -44,6 +46,7 @@ class TaskClose extends Base case GithubWebhook::EVENT_ISSUE_CLOSED: case GitlabWebhook::EVENT_COMMIT: case GitlabWebhook::EVENT_ISSUE_CLOSED: + case BitbucketWebhook::EVENT_COMMIT: return array(); default: return array('column_id' => t('Column')); @@ -63,6 +66,7 @@ class TaskClose extends Base case GithubWebhook::EVENT_ISSUE_CLOSED: case GitlabWebhook::EVENT_COMMIT: case GitlabWebhook::EVENT_ISSUE_CLOSED: + case BitbucketWebhook::EVENT_COMMIT: return array('task_id'); default: return array('task_id', 'column_id'); @@ -95,6 +99,7 @@ class TaskClose extends Base case GithubWebhook::EVENT_ISSUE_CLOSED: case GitlabWebhook::EVENT_COMMIT: case GitlabWebhook::EVENT_ISSUE_CLOSED: + case BitbucketWebhook::EVENT_COMMIT: return true; default: return $data['column_id'] == $this->getParam('column_id'); diff --git a/app/Controller/Webhook.php b/app/Controller/Webhook.php index 1ae3b0a4..ef79379f 100644 --- a/app/Controller/Webhook.php +++ b/app/Controller/Webhook.php @@ -82,4 +82,22 @@ class Webhook extends Base echo $result ? 'PARSED' : 'IGNORED'; } + + /** + * Handle Bitbucket webhooks + * + * @access public + */ + public function bitbucket() + { + if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) { + $this->response->text('Not Authorized', 401); + } + + $this->bitbucketWebhook->setProjectId($this->request->getIntegerParam('project_id')); + + $result = $this->bitbucketWebhook->parsePayload(json_decode(@$_POST['payload'], true)); + + echo $result ? 'PARSED' : 'IGNORED'; + } } diff --git a/app/Integration/BitbucketWebhook.php b/app/Integration/BitbucketWebhook.php new file mode 100644 index 00000000..9f82d5c0 --- /dev/null +++ b/app/Integration/BitbucketWebhook.php @@ -0,0 +1,98 @@ +<?php + +namespace Integration; + +use Event\GenericEvent; +use Event\TaskEvent; +use Model\Task; + +/** + * Bitbucket Webhook + * + * @package integration + * @author Frederic Guillot + */ +class BitbucketWebhook extends Base +{ + /** + * Events + * + * @var string + */ + const EVENT_COMMIT = 'bitbucket.webhook.commit'; + + /** + * Project id + * + * @access private + * @var integer + */ + private $project_id = 0; + + /** + * Set the project id + * + * @access public + * @param integer $project_id Project id + */ + public function setProjectId($project_id) + { + $this->project_id = $project_id; + } + + /** + * Parse events + * + * @access public + * @param array $payload Gitlab event + * @return boolean + */ + public function parsePayload(array $payload) + { + if (! empty($payload['commits'])) { + + foreach ($payload['commits'] as $commit) { + + if ($this->handleCommit($commit)) { + return true; + } + } + } + + return false; + } + + /** + * Parse commit + * + * @access public + * @param array $commit Gitlab commit + * @return boolean + */ + public function handleCommit(array $commit) + { + $task_id = $this->task->getTaskIdFromText($commit['message']); + + if (! $task_id) { + return false; + } + + $task = $this->taskFinder->getById($task_id); + + if (! $task) { + return false; + } + + if ($task['is_active'] == Task::STATUS_OPEN && $task['project_id'] == $this->project_id) { + + $this->container['dispatcher']->dispatch( + self::EVENT_COMMIT, + new TaskEvent(array('task_id' => $task_id) + $task) + ); + + return true; + } + + return false; + } +} diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 07c05b89..fbd7b3ab 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index af15c6b2..b3a9c04b 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index b39e9793..8cfa91b8 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 9adc19e4..39676bf1 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 2542a6fe..5df39809 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -680,4 +680,7 @@ return array( 'Disable login form' => 'Désactiver le formulaire d\'authentification', 'Show/hide calendar' => 'Afficher/cacher le calendrier', 'User calendar' => 'Calendrier de l\'utilisateur', + 'Bitbucket commit received' => '« Commit » reçu via Bitbucket', + 'Bitbucket webhooks' => 'Webhook Bitbucket', + 'Help on Bitbucket webhooks' => 'Aide sur les webhooks Bitbucket', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 8cbe6d8d..b7fbd04e 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 98ed4a91..2a974629 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 229622c5..2647f9fd 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index e2cc3ab8..c83e130a 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index db640901..0cd80f8f 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 513436af..ed68a447 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index 4958189f..1d793b35 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 962971f4..cf5302f6 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index 4c76d7ab..6dbd1676 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -678,4 +678,7 @@ return array( // 'Disable login form' => '', // 'Show/hide calendar' => '', // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', ); diff --git a/app/Model/Action.php b/app/Model/Action.php index 6aef81a3..6fb2a2f1 100644 --- a/app/Model/Action.php +++ b/app/Model/Action.php @@ -4,6 +4,7 @@ namespace Model; use Integration\GitlabWebhook; use Integration\GithubWebhook; +use Integration\BitbucketWebhook; use SimpleValidator\Validator; use SimpleValidator\Validators; @@ -85,6 +86,7 @@ class Action extends Base GitlabWebhook::EVENT_COMMIT => t('Gitlab commit received'), GitlabWebhook::EVENT_ISSUE_OPENED => t('Gitlab issue opened'), GitlabWebhook::EVENT_ISSUE_CLOSED => t('Gitlab issue closed'), + BitbucketWebhook::EVENT_COMMIT => t('Bitbucket commit received'), ); asort($values); diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 48157991..bee03184 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -62,6 +62,7 @@ class ClassProvider implements ServiceProviderInterface 'Integration' => array( 'GitlabWebhook', 'GithubWebhook', + 'BitbucketWebhook', ) ); diff --git a/app/Template/project/integrations.php b/app/Template/project/integrations.php index 8ec43f90..194bd672 100644 --- a/app/Template/project/integrations.php +++ b/app/Template/project/integrations.php @@ -12,4 +12,10 @@ <div class="listing"> <input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('webhook', 'gitlab', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/> <p class="form-help"><a href="http://kanboard.net/documentation/gitlab-webhooks" target="_blank"><?= t('Help on Gitlab webhooks') ?></a></p> +</div> + +<h3><i class="fa fa-bitbucket fa-fw"></i> <?= t('Bitbucket webhooks') ?></h3> +<div class="listing"> +<input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('webhook', 'bitbucket', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/> +<p class="form-help"><a href="http://kanboard.net/documentation/bitbucket-webhooks" target="_blank"><?= t('Help on Bitbucket webhooks') ?></a></p> </div>
\ No newline at end of file diff --git a/docs/bitbucket-webhooks.markdown b/docs/bitbucket-webhooks.markdown new file mode 100644 index 00000000..fbb80d12 --- /dev/null +++ b/docs/bitbucket-webhooks.markdown @@ -0,0 +1,40 @@ +Bitbucket webhooks +================== + +Bitbucket events can be connected to Kanboard automatic actions. + +List of supported events +------------------------ + +- Bitbucket commit received + +List of supported actions +------------------------- + +- Close a task + +Configuration +------------- + +![Bitbucket configuration](http://kanboard.net/screenshots/documentation/bitbucket-webhooks.png) + +1. On Kanboard, go to the project settings and choose the section **Integrations** +2. Copy the Bitbucket webhook url +3. On Bitbucket, go to the project settings and go to the section **Hooks** +4. Select the service **POST** +5. Paste the url and save + +Examples +-------- + +### Close a Kanboard task when a commit pushed to Bitbucket + +- Choose the event: **Bitbucket commit received** +- Choose the action: **Close the task** + +When one or more commits are sent to Bitbucket, Kanboard will receive the information, each commit message with a task number included will be closed. + +Example: + +- Commit message: "Fix bug #1234" +- That will close the Kanboard task #1234 diff --git a/docs/gitlab-webhooks.markdown b/docs/gitlab-webhooks.markdown index 3369b223..9ef73f97 100644 --- a/docs/gitlab-webhooks.markdown +++ b/docs/gitlab-webhooks.markdown @@ -23,7 +23,7 @@ Configuration 1. On Kanboard, go to the project settings and choose the section **Integrations** 2. Copy the Gitlab webhook url -3. On Gitlab, go to the project settings and go the section **Webhooks** +3. On Gitlab, go to the project settings and go to the section **Webhooks** 4. Check the boxes **Push Events** and **Issues Events** 5. Paste the url and save diff --git a/tests/units/BitbucketWebhookTest.php b/tests/units/BitbucketWebhookTest.php new file mode 100644 index 00000000..cb33b595 --- /dev/null +++ b/tests/units/BitbucketWebhookTest.php @@ -0,0 +1,65 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Integration\BitbucketWebhook; +use Model\TaskCreation; +use Model\TaskFinder; +use Model\Project; + +class BitbucketWebhookTest extends Base +{ + private $post_payload = '{"repository": {"website": "", "fork": false, "name": "webhooks", "scm": "git", "owner": "minicoders", "absolute_url": "/minicoders/webhooks/", "slug": "webhooks", "is_private": true}, "truncated": false, "commits": [{"node": "28569937627f", "files": [{"type": "added", "file": "README.md"}], "raw_author": "Frederic Guillot <fred@localhost>", "utctimestamp": "2015-02-09 00:57:45+00:00", "author": "Frederic Guillot", "timestamp": "2015-02-09 01:57:45", "raw_node": "28569937627fb406eeda9376a02b39581a974d4f", "parents": [], "branch": "master", "message": "first commit\\n", "revision": null, "size": -1}, {"node": "285699376274", "files": [{"type": "added", "file": "README.md"}], "raw_author": "Frederic Guillot <fred@localhost>", "utctimestamp": "2015-02-09 00:57:45+00:00", "author": "Frederic Guillot", "timestamp": "2015-02-09 01:57:45", "raw_node": "28569937627fb406eeda9376a02b39581a974d4f", "parents": [], "branch": "master", "message": "Fix #2\\n", "revision": null, "size": -1}], "canon_url": "https://bitbucket.org", "user": "minicoders"}'; + + public function testHandleCommit() + { + $g = new BitbucketWebhook($this->container); + $p = new Project($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFinder($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test'))); + $g->setProjectId(1); + + $this->container['dispatcher']->addListener(BitbucketWebhook::EVENT_COMMIT, function() {}); + + $event = json_decode($this->post_payload, true); + + // No task + $this->assertFalse($g->handleCommit($event['commits'][0])); + + // Create task with the wrong id + $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1))); + $this->assertFalse($g->handleCommit($event['commits'][1])); + + // Create task with the right id + $this->assertEquals(2, $tc->create(array('title' => 'test', 'project_id' => 1))); + $this->assertTrue($g->handleCommit($event['commits'][1])); + + $called = $this->container['dispatcher']->getCalledListeners(); + $this->assertArrayHasKey(BitbucketWebhook::EVENT_COMMIT.'.closure', $called); + } + + public function testParsePayload() + { + $g = new BitbucketWebhook($this->container); + $p = new Project($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFinder($this->container); + + $this->container['dispatcher']->addListener(BitbucketWebhook::EVENT_COMMIT, function() {}); + + $this->assertEquals(1, $p->create(array('name' => 'test'))); + + $g->setProjectId(1); + + $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1))); + $this->assertEquals(2, $tc->create(array('title' => 'test', 'project_id' => 1))); + + $event = json_decode($this->post_payload, true); + $this->assertTrue($g->parsePayload($event)); + + $called = $this->container['dispatcher']->getCalledListeners(); + $this->assertArrayHasKey(BitbucketWebhook::EVENT_COMMIT.'.closure', $called); + } +} |