diff options
36 files changed, 597 insertions, 58 deletions
diff --git a/app/Action/CommentCreation.php b/app/Action/CommentCreation.php index 5dbe32f1..27ed011b 100644 --- a/app/Action/CommentCreation.php +++ b/app/Action/CommentCreation.php @@ -2,7 +2,7 @@ namespace Action; -use Model\GithubWebhook; +use Integration\GithubWebhook; /** * Create automatically a comment from a webhook diff --git a/app/Action/TaskAssignCategoryLabel.php b/app/Action/TaskAssignCategoryLabel.php index 19064526..1383d491 100644 --- a/app/Action/TaskAssignCategoryLabel.php +++ b/app/Action/TaskAssignCategoryLabel.php @@ -2,7 +2,7 @@ namespace Action; -use Model\GithubWebhook; +use Integration\GithubWebhook; /** * Set a category automatically according to a label diff --git a/app/Action/TaskAssignUser.php b/app/Action/TaskAssignUser.php index f24ff415..cf2a9a4b 100644 --- a/app/Action/TaskAssignUser.php +++ b/app/Action/TaskAssignUser.php @@ -2,7 +2,7 @@ namespace Action; -use Model\GithubWebhook; +use Integration\GithubWebhook; /** * Assign a task to someone diff --git a/app/Action/TaskClose.php b/app/Action/TaskClose.php index 6cf9be05..760dfd84 100644 --- a/app/Action/TaskClose.php +++ b/app/Action/TaskClose.php @@ -2,7 +2,8 @@ namespace Action; -use Model\GithubWebhook; +use Integration\GitlabWebhook; +use Integration\GithubWebhook; use Model\Task; /** @@ -25,6 +26,8 @@ class TaskClose extends Base Task::EVENT_MOVE_COLUMN, GithubWebhook::EVENT_COMMIT, GithubWebhook::EVENT_ISSUE_CLOSED, + GitlabWebhook::EVENT_COMMIT, + GitlabWebhook::EVENT_ISSUE_CLOSED, ); } @@ -39,6 +42,8 @@ class TaskClose extends Base switch ($this->event_name) { case GithubWebhook::EVENT_COMMIT: case GithubWebhook::EVENT_ISSUE_CLOSED: + case GitlabWebhook::EVENT_COMMIT: + case GitlabWebhook::EVENT_ISSUE_CLOSED: return array(); default: return array('column_id' => t('Column')); @@ -56,6 +61,8 @@ class TaskClose extends Base switch ($this->event_name) { case GithubWebhook::EVENT_COMMIT: case GithubWebhook::EVENT_ISSUE_CLOSED: + case GitlabWebhook::EVENT_COMMIT: + case GitlabWebhook::EVENT_ISSUE_CLOSED: return array('task_id'); default: return array('task_id', 'column_id'); @@ -86,6 +93,8 @@ class TaskClose extends Base switch ($this->event_name) { case GithubWebhook::EVENT_COMMIT: case GithubWebhook::EVENT_ISSUE_CLOSED: + case GitlabWebhook::EVENT_COMMIT: + case GitlabWebhook::EVENT_ISSUE_CLOSED: return true; default: return $data['column_id'] == $this->getParam('column_id'); diff --git a/app/Action/TaskCreation.php b/app/Action/TaskCreation.php index 9b7a0b13..1c093eee 100644 --- a/app/Action/TaskCreation.php +++ b/app/Action/TaskCreation.php @@ -2,7 +2,8 @@ namespace Action; -use Model\GithubWebhook; +use Integration\GithubWebhook; +use Integration\GitlabWebhook; /** * Create automatically a task from a webhook @@ -22,6 +23,7 @@ class TaskCreation extends Base { return array( GithubWebhook::EVENT_ISSUE_OPENED, + GitlabWebhook::EVENT_ISSUE_OPENED, ); } diff --git a/app/Action/TaskOpen.php b/app/Action/TaskOpen.php index fc29e9eb..73f1fad3 100644 --- a/app/Action/TaskOpen.php +++ b/app/Action/TaskOpen.php @@ -2,7 +2,7 @@ namespace Action; -use Model\GithubWebhook; +use Integration\GithubWebhook; /** * Open automatically a task diff --git a/app/Controller/Project.php b/app/Controller/Project.php index 9037a91a..a7e8a39b 100644 --- a/app/Controller/Project.php +++ b/app/Controller/Project.php @@ -52,7 +52,6 @@ class Project extends Base $this->response->html($this->projectLayout('project/show', array( 'project' => $project, 'stats' => $this->project->getStats($project['id']), - 'webhook_token' => $this->config->get('webhook_token'), 'title' => $project['name'], ))); } @@ -153,6 +152,22 @@ class Project extends Base } /** + * Integrations page + * + * @access public + */ + public function integration() + { + $project = $this->getProjectManagement(); + + $this->response->html($this->projectLayout('project/integrations', array( + 'project' => $project, + 'title' => t('Integrations'), + 'webhook_token' => $this->config->get('webhook_token'), + ))); + } + + /** * Display a form to edit a project * * @access public diff --git a/app/Controller/Webhook.php b/app/Controller/Webhook.php index dcd66a1a..1ae3b0a4 100644 --- a/app/Controller/Webhook.php +++ b/app/Controller/Webhook.php @@ -57,7 +57,27 @@ class Webhook extends Base $result = $this->githubWebhook->parsePayload( $this->request->getHeader('X-Github-Event'), - $this->request->getJson() + $this->request->getJson() ?: array() + ); + + echo $result ? 'PARSED' : 'IGNORED'; + } + + /** + * Handle Gitlab webhooks + * + * @access public + */ + public function gitlab() + { + if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) { + $this->response->text('Not Authorized', 401); + } + + $this->gitlabWebhook->setProjectId($this->request->getIntegerParam('project_id')); + + $result = $this->gitlabWebhook->parsePayload( + $this->request->getJson() ?: array() ); echo $result ? 'PARSED' : 'IGNORED'; diff --git a/app/Integration/Base.php b/app/Integration/Base.php new file mode 100644 index 00000000..babf8c8f --- /dev/null +++ b/app/Integration/Base.php @@ -0,0 +1,49 @@ +<?php + +namespace Integration; + +use Pimple\Container; + +/** + * Base class + * + * @package integration + * @author Frederic Guillot + * + * @property \Model\Task $task + * @property \Model\TaskFinder $taskFinder + * @property \Model\User $user + */ +abstract class Base +{ + /** + * Container instance + * + * @access protected + * @var \Pimple\Container + */ + protected $container; + + /** + * Constructor + * + * @access public + * @param \Pimple\Container $container + */ + public function __construct(Container $container) + { + $this->container = $container; + } + + /** + * Load automatically class from the container + * + * @access public + * @param string $name + * @return mixed + */ + public function __get($name) + { + return $this->container[$name]; + } +} diff --git a/app/Model/GithubWebhook.php b/app/Integration/GithubWebhook.php index f66358eb..fd0b49f6 100644 --- a/app/Model/GithubWebhook.php +++ b/app/Integration/GithubWebhook.php @@ -1,13 +1,14 @@ <?php -namespace Model; +namespace Integration; use Event\GenericEvent; +use Model\Task; /** - * Github Webhook model + * Github Webhook * - * @package model + * @package integration * @author Frederic Guillot */ class GithubWebhook extends Base @@ -89,7 +90,7 @@ class GithubWebhook extends Base continue; } - if ($task['is_active'] == Task::STATUS_OPEN) { + if ($task['is_active'] == Task::STATUS_OPEN && $task['project_id'] == $this->project_id) { $this->container['dispatcher']->dispatch( self::EVENT_COMMIT, new GenericEvent(array('task_id' => $task_id) + $task) diff --git a/app/Integration/GitlabWebhook.php b/app/Integration/GitlabWebhook.php new file mode 100644 index 00000000..f5df32a6 --- /dev/null +++ b/app/Integration/GitlabWebhook.php @@ -0,0 +1,213 @@ +<?php + +namespace Integration; + +use Event\GenericEvent; +use Event\TaskEvent; +use Model\Task; + +/** + * Gitlab Webhook + * + * @package integration + * @author Frederic Guillot + */ +class GitlabWebhook extends Base +{ + /** + * Events + * + * @var string + */ + const EVENT_ISSUE_OPENED = 'gitlab.webhook.issue.opened'; + const EVENT_ISSUE_CLOSED = 'gitlab.webhook.issue.closed'; + const EVENT_COMMIT = 'gitlab.webhook.commit'; + + /** + * Supported webhook events + * + * @var string + */ + const TYPE_PUSH = 'push'; + const TYPE_ISSUE = 'issue'; + + /** + * 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) + { + switch ($this->getType($payload)) { + case self::TYPE_PUSH: + return $this->handlePushEvent($payload); + case self::TYPE_ISSUE; + return $this->handleIssueEvent($payload); + } + + return false; + } + + /** + * Get event type + * + * @access public + * @param array $payload Gitlab event + * @return string + */ + public function getType(array $payload) + { + if (isset($payload['object_kind']) && $payload['object_kind'] === 'issue') { + return self::TYPE_ISSUE; + } + + if (isset($payload['commits'])) { + return self::TYPE_PUSH; + } + + return ''; + } + + /** + * Parse push event + * + * @access public + * @param array $payload Gitlab event + * @return boolean + */ + public function handlePushEvent(array $payload) + { + foreach ($payload['commits'] as $commit) { + $this->handleCommit($commit); + } + + return true; + } + + /** + * 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; + } + + /** + * Parse issue event + * + * @access public + * @param array $payload Gitlab event + * @return boolean + */ + public function handleIssueEvent(array $payload) + { + switch ($payload['object_attributes']['state']) { + case 'opened': + return $this->handleIssueOpened($payload['object_attributes']); + case 'closed': + return $this->handleIssueClosed($payload['object_attributes']); + } + + return false; + } + + /** + * Handle new issues + * + * @access public + * @param array $issue Issue data + * @return boolean + */ + public function handleIssueOpened(array $issue) + { + $event = array( + 'project_id' => $this->project_id, + 'reference' => $issue['id'], + 'title' => $issue['title'], + 'description' => $issue['description']."\n\n[".t('Gitlab Issue').']('.$issue['url'].')', + ); + + $this->container['dispatcher']->dispatch( + self::EVENT_ISSUE_OPENED, + new GenericEvent($event) + ); + + return true; + } + + /** + * Handle issue closing + * + * @access public + * @param array $issue Issue data + * @return boolean + */ + public function handleIssueClosed(array $issue) + { + $task = $this->taskFinder->getByReference($issue['id']); + + if ($task) { + $event = array( + 'project_id' => $this->project_id, + 'task_id' => $task['id'], + 'reference' => $issue['id'], + ); + + $this->container['dispatcher']->dispatch( + self::EVENT_ISSUE_CLOSED, + new GenericEvent($event) + ); + + return true; + } + + return false; + } +} diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index b6227771..a0c04610 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index f9d8e714..b03ef801 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -556,8 +556,8 @@ return array( 'Webhooks' => 'Webhooks', 'API' => 'API', 'Integration' => 'Integration', - 'Github webhook' => 'Github Webhook', - 'Help on Github webhook' => 'Hilfe bei einem Github Webhook', + 'Github webhooks' => 'Github Webhook', + 'Help on Github webhooks' => 'Hilfe bei einem Github Webhook', 'Create a comment from an external provider' => 'Kommentar eines externen Providers hinzufügen', 'Github issue comment created' => 'Github Fehler Kommentar hinzugefügt', 'Configure' => 'konfigurieren', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index 1f4a3d87..527e3b95 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 25cf67c3..98fcfdc8 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index fb281f13..ef52d333 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -556,8 +556,8 @@ return array( 'Webhooks' => 'Webhooks', 'API' => 'API', 'Integration' => 'Intégration', - 'Github webhook' => 'Webhook Github', - 'Help on Github webhook' => 'Aide sur les webhooks Github', + 'Github webhooks' => 'Webhook Github', + 'Help on Github webhooks' => 'Aide sur les webhooks Github', 'Create a comment from an external provider' => 'Créer un commentaire depuis un fournisseur externe', 'Github issue comment created' => 'Commentaire créé sur un ticket Github', 'Configure' => 'Configurer', @@ -626,4 +626,11 @@ return array( 'Your swimlane have been created successfully.' => 'Votre swimlane a été créée avec succès.', 'Example: "Bug, Feature Request, Improvement"' => 'Exemple: « Incident, Demande de fonctionnalité, Amélioration »', 'Default categories for new projects (Comma-separated)' => 'Catégories par défaut pour les nouveaux projets (séparé par des virgules)', + 'Gitlab commit received' => '« Commit » reçu via Gitlab', + 'Gitlab issue opened' => 'Ouverture d\'un ticket sur Gitlab', + 'Gitlab issue closed' => 'Fermeture d\'un ticket sur Gitlab', + 'Gitlab webhooks' => 'Webhook Gitlab', + 'Help on Gitlab webhooks' => 'Aide sur les webhooks Gitlab', + 'Integrations' => 'Intégrations', + 'Integration with third-party services' => 'Intégration avec des services externes', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 7188d197..5dbbe988 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -556,8 +556,8 @@ return array( 'Webhooks' => 'Webhook', 'API' => 'API', 'Integration' => 'Integráció', - 'Github webhook' => 'Github webhook', - 'Help on Github webhook' => 'Github Webhook súgó', + 'Github webhooks' => 'Github webhooks', + 'Help on Github webhooks' => 'Github Webhook súgó', 'Create a comment from an external provider' => 'Megjegyzés létrehozása külső felhasználótól', 'Github issue comment created' => 'Github issue megjegyzés létrehozva', 'Configure' => 'Konfigurál', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 3117a068..7bd48a2b 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 60ded536..b519d68c 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 1c0492ed..bcab4874 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 28858ec5..48c15f61 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', 'Integration' => 'Integração', - // 'Github webhook' => '', - 'Help on Github webhook' => 'Ajuda para o Github webhook', + // 'Github webhooks' => '', + 'Help on Github webhooks' => 'Ajuda para o Github webhooks', 'Create a comment from an external provider' => 'Criar um comentário de um provedor externo', // 'Github issue comment created' => '', 'Configure' => 'Configurar', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 5d58ca25..08fc35de 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index cd0bb2bf..b33983ca 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 7331d877..0dbe486c 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -556,8 +556,8 @@ return array( // 'Webhooks' => '', // 'API' => '', // 'Integration' => '', - // 'Github webhook' => '', - // 'Help on Github webhook' => '', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', // 'Create a comment from an external provider' => '', // 'Github issue comment created' => '', // 'Configure' => '', @@ -626,4 +626,11 @@ return array( // 'Your swimlane have been created successfully.' => '', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index 455e37ed..b77a21d0 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -556,8 +556,8 @@ return array( 'Webhooks' => '网络钩子', 'API' => '应用程序接口', 'Integration' => '整合', - 'Github webhook' => 'Github 网络钩子', - 'Help on Github webhook' => 'Github 网络钩子帮助', + 'Github webhooks' => 'Github 网络钩子', + 'Help on Github webhooks' => 'Github 网络钩子帮助', 'Create a comment from an external provider' => '从外部创建一个评论', 'Github issue comment created' => '已经创建了Github问题评论', 'Configure' => '配置', @@ -626,4 +626,11 @@ return array( 'Your swimlane have been created successfully.' => '已经成功创建泳道。', // 'Example: "Bug, Feature Request, Improvement"' => '', // 'Default categories for new projects (Comma-separated)' => '', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + // 'Integrations' => '', + // 'Integration with third-party services' => '', ); diff --git a/app/Model/Acl.php b/app/Model/Acl.php index 3f454885..d294197a 100644 --- a/app/Model/Acl.php +++ b/app/Model/Acl.php @@ -21,7 +21,7 @@ class Acl extends Base 'task' => array('readonly'), 'board' => array('readonly'), 'project' => array('feed'), - 'webhook' => array('task', 'github'), + 'webhook' => array('task', 'github', 'gitlab'), ); /** diff --git a/app/Model/Action.php b/app/Model/Action.php index 36e0aa62..905b8914 100644 --- a/app/Model/Action.php +++ b/app/Model/Action.php @@ -2,6 +2,8 @@ namespace Model; +use Integration\GitlabWebhook; +use Integration\GithubWebhook; use SimpleValidator\Validator; use SimpleValidator\Validators; @@ -79,6 +81,9 @@ class Action extends Base GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE => t('Github issue assignee change'), GithubWebhook::EVENT_ISSUE_LABEL_CHANGE => t('Github issue label change'), GithubWebhook::EVENT_ISSUE_COMMENT => t('Github issue comment created'), + GitlabWebhook::EVENT_COMMIT => t('Gitlab commit received'), + GitlabWebhook::EVENT_ISSUE_OPENED => t('Gitlab issue opened'), + GitlabWebhook::EVENT_ISSUE_CLOSED => t('Gitlab issue closed'), ); asort($values); diff --git a/app/Model/Base.php b/app/Model/Base.php index dfac12ae..db670969 100644 --- a/app/Model/Base.php +++ b/app/Model/Base.php @@ -29,6 +29,7 @@ use Pimple\Container; * @property \Model\ProjectPermission $projectPermission * @property \Model\SubTask $subTask * @property \Model\SubtaskHistory $subtaskHistory + * @property \Model\Swimlane $swimlane * @property \Model\Task $task * @property \Model\TaskCreation $taskCreation * @property \Model\TaskExport $taskExport diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index acba26d4..645a85ef 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -22,7 +22,6 @@ class ClassProvider implements ServiceProviderInterface 'Config', 'DateParser', 'File', - 'GithubWebhook', 'LastLogin', 'Notification', 'Project', @@ -53,6 +52,10 @@ class ClassProvider implements ServiceProviderInterface 'Template', 'Session', ), + 'Integration' => array( + 'GitlabWebhook', + 'GithubWebhook', + ) ); public function register(Container $container) diff --git a/app/Subscriber/ProjectModificationDateSubscriber.php b/app/Subscriber/ProjectModificationDateSubscriber.php index 3d5484f7..cd83f370 100644 --- a/app/Subscriber/ProjectModificationDateSubscriber.php +++ b/app/Subscriber/ProjectModificationDateSubscriber.php @@ -4,7 +4,7 @@ namespace Subscriber; use Event\GenericEvent; use Model\Task; -use Model\GithubWebhook; +use Integration\GithubWebhook; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class ProjectModificationDateSubscriber extends Base implements EventSubscriberInterface diff --git a/app/Template/project/integrations.php b/app/Template/project/integrations.php new file mode 100644 index 00000000..8ec43f90 --- /dev/null +++ b/app/Template/project/integrations.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Integration with third-party services') ?></h2> +</div> + +<h3><i class="fa fa-github fa-fw"></i> <?= t('Github webhooks') ?></h3> +<div class="listing"> +<input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('webhook', 'github', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/> +<p class="form-help"><a href="http://kanboard.net/documentation/github-webhooks" target="_blank"><?= t('Help on Github webhooks') ?></a></p> +</div> + +<h3><i class="fa fa-git fa-fw"></i> <?= t('Gitlab webhooks') ?></h3> +<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>
\ No newline at end of file diff --git a/app/Template/project/show.php b/app/Template/project/show.php index 22d710e6..bc622d0d 100644 --- a/app/Template/project/show.php +++ b/app/Template/project/show.php @@ -53,14 +53,3 @@ </tr> <?php endforeach ?> </table> - -<?php if ($this->acl->isAdminUser()): ?> -<div class="page-header"> - <h2><?= t('Integration') ?></h2> -</div> - -<h3><i class="fa fa-github fa-fw"></i><?= t('Github webhook') ?></h3> -<input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('webhook', 'github', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/> -<p class="form-help"><a href="http://kanboard.net/documentation/github-webhooks" target="_blank"><?= t('Help on Github webhook') ?></a></p> - -<?php endif ?> diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php index 35019fdb..52a971d6 100644 --- a/app/Template/project/sidebar.php +++ b/app/Template/project/sidebar.php @@ -10,6 +10,9 @@ <?= $this->a(t('Public access'), 'project', 'share', array('project_id' => $project['id'])) ?> </li> <li> + <?= $this->a(t('Integrations'), 'project', 'integration', array('project_id' => $project['id'])) ?> + </li> + <li> <?= $this->a(t('Edit project'), 'project', 'edit', array('project_id' => $project['id'])) ?> </li> <?php endif ?> diff --git a/tests/units/ActionTaskCloseTest.php b/tests/units/ActionTaskCloseTest.php index a5087af0..7f2c42de 100644 --- a/tests/units/ActionTaskCloseTest.php +++ b/tests/units/ActionTaskCloseTest.php @@ -7,7 +7,7 @@ use Model\Task; use Model\TaskCreation; use Model\TaskFinder; use Model\Project; -use Model\GithubWebhook; +use Integration\GithubWebhook; class ActionTaskCloseTest extends Base { diff --git a/tests/units/ActionTest.php b/tests/units/ActionTest.php index 77a939e0..429a181a 100644 --- a/tests/units/ActionTest.php +++ b/tests/units/ActionTest.php @@ -10,10 +10,10 @@ use Model\TaskPosition; use Model\TaskCreation; use Model\TaskFinder; use Model\Category; -use Model\GithubWebhook; +use Integration\GithubWebhook; class ActionTest extends Base -{/* +{ public function testSingleAction() { $tp = new TaskPosition($this->container); @@ -61,7 +61,7 @@ class ActionTest extends Base $this->assertEquals(4, $t1['column_id']); $this->assertEquals(0, $t1['is_active']); } -*/ + public function testMultipleActions() { $tp = new TaskPosition($this->container); diff --git a/tests/units/GitlabWebhookTest.php b/tests/units/GitlabWebhookTest.php new file mode 100644 index 00000000..0f2a5c12 --- /dev/null +++ b/tests/units/GitlabWebhookTest.php @@ -0,0 +1,116 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Integration\GitlabWebhook; +use Model\TaskCreation; +use Model\TaskFinder; +use Model\Project; + +class GitlabWebhookTest extends Base +{ + private $push_payload = '{"before":"9187f41ba34a2b40d41c50ed4b624ce374c5e583","after":"b3caaee62ad27dc31497946065ac18299784aee4","ref":"refs/heads/master","user_id":74067,"user_name":"Fred","project_id":124474,"repository":{"name":"kanboard","url":"git@gitlab.com:minicoders/kanboard.git","description":"Test repo","homepage":"https://gitlab.com/minicoders/kanboard"},"commits":[{"id":"b3caaee62ad27dc31497946065ac18299784aee4","message":"Fix bug #2\n","timestamp":"2014-12-28T20:31:48-05:00","url":"https://gitlab.com/minicoders/kanboard/commit/b3caaee62ad27dc31497946065ac18299784aee4","author":{"name":"Frédéric Guillot","email":"git@localhost"}}],"total_commits_count":1}'; + private $issue_open_payload = '{"object_kind":"issue","user":{"name":"Fred","username":"minicoders","avatar_url":"https://secure.gravatar.com/avatar/3c44936e5a56f80711bff14987d2733f?s=40\u0026d=identicon"},"object_attributes":{"id":103356,"title":"Test Webhook","assignee_id":null,"author_id":74067,"project_id":124474,"created_at":"2014-12-29 01:24:24 UTC","updated_at":"2014-12-29 01:24:24 UTC","position":0,"branch_name":null,"description":"- test1\r\n- test2","milestone_id":null,"state":"opened","iid":1,"url":"https://gitlab.com/minicoders/kanboard/issues/1","action":"open"}}'; + private $issue_closed_payload = '{"object_kind":"issue","user":{"name":"Fred","username":"minicoders","avatar_url":"https://secure.gravatar.com/avatar/3c44936e5a56f80711bff14987d2733f?s=40\u0026d=identicon"},"object_attributes":{"id":103361,"title":"uu","assignee_id":null,"author_id":74067,"project_id":124474,"created_at":"2014-12-29 01:28:44 UTC","updated_at":"2014-12-29 01:34:47 UTC","position":0,"branch_name":null,"description":"","milestone_id":null,"state":"closed","iid":4,"url":"https://gitlab.com/minicoders/kanboard/issues/4","action":"update"}}'; + + public function testGetEventType() + { + $g = new GitlabWebhook($this->container); + + $this->assertEquals(GitlabWebhook::TYPE_PUSH, $g->getType(json_decode($this->push_payload, true))); + $this->assertEquals(GitlabWebhook::TYPE_ISSUE, $g->getType(json_decode($this->issue_open_payload, true))); + $this->assertEquals(GitlabWebhook::TYPE_ISSUE, $g->getType(json_decode($this->issue_closed_payload, true))); + $this->assertEquals('', $g->getType(array())); + } + + public function testHandleCommit() + { + $g = new GitlabWebhook($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(GitlabWebhook::EVENT_COMMIT, function() {}); + + $event = json_decode($this->push_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'][0])); + + // Create task with the right id + $this->assertEquals(2, $tc->create(array('title' => 'test', 'project_id' => 1))); + $this->assertTrue($g->handleCommit($event['commits'][0])); + + $called = $this->container['dispatcher']->getCalledListeners(); + $this->assertArrayHasKey(GitlabWebhook::EVENT_COMMIT.'.closure', $called); + } + + public function testHandleIssueOpened() + { + $g = new GitlabWebhook($this->container); + $g->setProjectId(1); + + $this->container['dispatcher']->addListener(GitlabWebhook::EVENT_ISSUE_OPENED, array($this, 'onOpen')); + + $event = json_decode($this->issue_open_payload, true); + $this->assertTrue($g->handleIssueOpened($event['object_attributes'])); + + $called = $this->container['dispatcher']->getCalledListeners(); + $this->assertArrayHasKey(GitlabWebhook::EVENT_ISSUE_OPENED.'.GitlabWebhookTest::onOpen', $called); + } + + public function testHandleIssueClosed() + { + $g = new GitlabWebhook($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(GitlabWebhook::EVENT_ISSUE_CLOSED, array($this, 'onClose')); + + $event = json_decode($this->issue_closed_payload, true); + + // Issue not there + $this->assertFalse($g->handleIssueClosed($event['object_attributes'])); + + $called = $this->container['dispatcher']->getCalledListeners(); + $this->assertEmpty($called); + + // Create a task with the issue reference + $this->assertEquals(1, $tc->create(array('title' => 'A', 'project_id' => 1, 'reference' => 103361))); + $task = $tf->getByReference(103361); + $this->assertNotEmpty($task); + + $this->assertTrue($g->handleIssueClosed($event['object_attributes'])); + + $called = $this->container['dispatcher']->getCalledListeners(); + $this->assertArrayHasKey(GitlabWebhook::EVENT_ISSUE_CLOSED.'.GitlabWebhookTest::onClose', $called); + } + + public function onOpen($event) + { + $data = $event->getAll(); + $this->assertEquals(1, $data['project_id']); + $this->assertEquals(103356, $data['reference']); + $this->assertEquals('Test Webhook', $data['title']); + $this->assertEquals("- test1\r\n- test2\n\n[Gitlab Issue](https://gitlab.com/minicoders/kanboard/issues/1)", $data['description']); + } + + public function onClose($event) + { + $data = $event->getAll(); + $this->assertEquals(1, $data['project_id']); + $this->assertEquals(1, $data['task_id']); + $this->assertEquals(103361, $data['reference']); + } +} |