Add Slack and Hipchat integrations for each projects

This commit is contained in:
Frederic Guillot 2015-04-18 18:44:45 -04:00
parent f53bb88d10
commit 370b5a0fd7
15 changed files with 368 additions and 81 deletions

View File

@ -43,6 +43,7 @@ use Symfony\Component\EventDispatcher\Event;
* @property \Model\ProjectAnalytic $projectAnalytic
* @property \Model\ProjectActivity $projectActivity
* @property \Model\ProjectDailySummary $projectDailySummary
* @property \Model\ProjectIntegration $projectIntegration
* @property \Model\Subtask $subtask
* @property \Model\SubtaskForecast $subtaskForecast
* @property \Model\Swimlane $swimlane

View File

@ -95,10 +95,21 @@ class Project extends Base
{
$project = $this->getProject();
if ($this->request->isPost()) {
$params = $this->request->getValues();
$params += array('hipchat' => 0, 'slack' => 0);
$this->projectIntegration->saveParameters($project['id'], $params);
}
$values = $this->projectIntegration->getParameters($project['id']);
$values += array('hipchat_api_url' => 'https://api.hipchat.com');
$this->response->html($this->projectLayout('project/integrations', array(
'project' => $project,
'title' => t('Integrations'),
'webhook_token' => $this->config->get('webhook_token'),
'values' => $values,
'errors' => array(),
)));
}

View File

@ -2,6 +2,8 @@
namespace Core;
use Pimple\Container;
/**
* HTTP client
*
@ -31,16 +33,34 @@ class HttpClient
*/
const HTTP_USER_AGENT = 'Kanboard Webhook';
/**
* Container instance
*
* @access protected
* @var \Pimple\Container
*/
protected $container;
/**
* Constructor
*
* @access public
* @param \Pimple\Container $container
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* Send a POST HTTP request
*
* @static
* @access public
* @param string $url
* @param array $data
* @return string
*/
public static function post($url, array $data)
public function post($url, array $data)
{
if (empty($url)) {
return '';
@ -63,6 +83,14 @@ class HttpClient
)
));
return @file_get_contents(trim($url), false, $context);
$response = @file_get_contents(trim($url), false, $context);
if (DEBUG) {
$this->container['logger']->debug($url);
$this->container['logger']->debug(var_export($data, true));
$this->container['logger']->debug($response);
}
return $response;
}
}

View File

@ -11,6 +11,7 @@ use Pimple\Container;
* @author Frederic Guillot
*
* @property \Model\ProjectActivity $projectActivity
* @property \Model\ProjectIntegration $projectIntegration
* @property \Model\Task $task
* @property \Model\TaskFinder $taskFinder
* @property \Model\User $user

View File

@ -3,7 +3,7 @@
namespace Integration;
/**
* Hipchat Webhook
* Hipchat
*
* @package integration
* @author Frederic Guillot
@ -11,7 +11,45 @@ namespace Integration;
class Hipchat extends Base
{
/**
* Send message to the Hipchat room
* Return true if Hipchat is enabled for this project or globally
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function isActivated($project_id)
{
return $this->config->get('integration_hipchat') == 1 || $this->projectIntegration->hasValue($project_id, 'hipchat', 1);
}
/**
* Get API parameters
*
* @access public
* @param integer $project_id
* @return array
*/
public function getParameters($project_id)
{
if ($this->config->get('integration_hipchat') == 1) {
return array(
'api_url' => $this->config->get('integration_hipchat_api_url'),
'room_id' => $this->config->get('integration_hipchat_room_id'),
'room_token' => $this->config->get('integration_hipchat_room_token'),
);
}
$options = $this->projectIntegration->getParameters($project_id);
return array(
'api_url' => $options['hipchat_api_url'],
'room_id' => $options['hipchat_room_id'],
'room_token' => $options['hipchat_room_token'],
);
}
/**
* Send the notification if activated
*
* @access public
* @param integer $project_id Project id
@ -21,33 +59,37 @@ class Hipchat extends Base
*/
public function notify($project_id, $task_id, $event_name, array $event)
{
$project = $this->project->getbyId($project_id);
if ($this->isActivated($project_id)) {
$event['event_name'] = $event_name;
$event['author'] = $this->user->getFullname($this->session['user']);
$params = $this->getParameters($project_id);
$project = $this->project->getbyId($project_id);
$html = '<img src="http://kanboard.net/assets/img/favicon-32x32.png"/>';
$html .= '<strong>'.$project['name'].'</strong><br/>';
$html .= $this->projectActivity->getTitle($event);
$event['event_name'] = $event_name;
$event['author'] = $this->user->getFullname($this->session['user']);
if ($this->config->get('application_url')) {
$html .= '<br/><a href="'.$this->config->get('application_url');
$html .= $this->helper->u('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id)).'">';
$html .= t('view the task on Kanboard').'</a>';
$html = '<img src="http://kanboard.net/assets/img/favicon-32x32.png"/>';
$html .= '<strong>'.$project['name'].'</strong>'.(isset($event['task']['title']) ? '<br/>'.$event['task']['title'] : '').'<br/>';
$html .= $this->projectActivity->getTitle($event);
if ($this->config->get('application_url')) {
$html .= '<br/><a href="'.$this->config->get('application_url');
$html .= $this->helper->u('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id)).'">';
$html .= t('view the task on Kanboard').'</a>';
}
$payload = array(
'message' => $html,
'color' => 'yellow',
);
$url = sprintf(
'%s/v2/room/%s/notification?auth_token=%s',
$params['api_url'],
$params['room_id'],
$params['room_token']
);
$this->httpClient->post($url, $payload);
}
$payload = array(
'message' => $html,
'color' => 'yellow',
);
$url = sprintf(
'%s/v2/room/%s/notification?auth_token=%s',
$this->config->get('integration_hipchat_api_url'),
$this->config->get('integration_hipchat_room_id'),
$this->config->get('integration_hipchat_room_token')
);
$this->httpClient->post($url, $payload);
}
}

View File

@ -10,6 +10,35 @@ namespace Integration;
*/
class SlackWebhook extends Base
{
/**
* Return true if Slack is enabled for this project or globally
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function isActivated($project_id)
{
return $this->config->get('integration_slack_webhook') == 1 || $this->projectIntegration->hasValue($project_id, 'slack', 1);
}
/**
* Get wehbook url
*
* @access public
* @param integer $project_id
* @return string
*/
public function getWebhookUrl($project_id)
{
if ($this->config->get('integration_slack_webhook') == 1) {
return $this->config->get('integration_slack_webhook_url');
}
$options = $this->projectIntegration->getParameters($project_id);
return $options['slack_webhook_url'];
}
/**
* Send message to the incoming Slack webhook
*
@ -21,23 +50,26 @@ class SlackWebhook extends Base
*/
public function notify($project_id, $task_id, $event_name, array $event)
{
$project = $this->project->getbyId($project_id);
if ($this->isActivated($project_id)) {
$event['event_name'] = $event_name;
$event['author'] = $this->user->getFullname($this->session['user']);
$project = $this->project->getbyId($project_id);
$payload = array(
'text' => '*['.$project['name'].']* '.str_replace('&quot;', '"', $this->projectActivity->getTitle($event)),
'username' => 'Kanboard',
'icon_url' => 'http://kanboard.net/assets/img/favicon.png',
);
$event['event_name'] = $event_name;
$event['author'] = $this->user->getFullname($this->session['user']);
if ($this->config->get('application_url')) {
$payload['text'] .= ' - <'.$this->config->get('application_url');
$payload['text'] .= $this->helper->u('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id));
$payload['text'] .= '|'.t('view the task on Kanboard').'>';
$payload = array(
'text' => '*['.$project['name'].']* '.str_replace('&quot;', '"', $this->projectActivity->getTitle($event)).(isset($event['task']['title']) ? ' ('.$event['task']['title'].')' : ''),
'username' => 'Kanboard',
'icon_url' => 'http://kanboard.net/assets/img/favicon.png',
);
if ($this->config->get('application_url')) {
$payload['text'] .= ' - <'.$this->config->get('application_url');
$payload['text'] .= $this->helper->u('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id));
$payload['text'] .= '|'.t('view the task on Kanboard').'>';
}
$this->httpClient->post($this->getWebhookUrl($project_id), $payload);
}
$this->httpClient->post($this->config->get('integration_slack_webhook_url'), $payload);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Model;
/**
* Project integration
*
* @package model
* @author Frederic Guillot
*/
class ProjectIntegration extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_integrations';
/**
* Get all parameters for a project
*
* @access public
* @param integer $project_id
* @return array
*/
public function getParameters($project_id)
{
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->findOne() ?: array();
}
/**
* Save parameters for a project
*
* @access public
* @param integer $project_id
* @param array $values
* @return boolean
*/
public function saveParameters($project_id, array $values)
{
if ($this->db->table(self::TABLE)->eq('project_id', $project_id)->count() === 1) {
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->update($values);
}
return $this->db->table(self::TABLE)->insert($values + array('project_id' => $project_id));
}
/**
* Check if a project has the given parameter/value
*
* @access public
* @param integer $project_id
* @param string $option
* @param string $value
* @return boolean
*/
public function hasValue($project_id, $option, $value)
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq($option, $value)
->count() === 1;
}
}

View File

@ -6,7 +6,25 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 64;
const VERSION = 65;
function version_65($pdo)
{
$pdo->exec("
CREATE TABLE project_integrations (
`id` INT NOT NULL AUTO_INCREMENT,
`project_id` INT NOT NULL UNIQUE,
`hipchat` TINYINT(1) DEFAULT 0,
`hipchat_api_url` VARCHAR(255) DEFAULT 'https://api.hipchat.com',
`hipchat_room_id` VARCHAR(255),
`hipchat_room_token` VARCHAR(255),
`slack` TINYINT(1) DEFAULT 0,
`slack_webhook_url` VARCHAR(255),
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
PRIMARY KEY(id)
) ENGINE=InnoDB CHARSET=utf8
");
}
function version_64($pdo)
{

View File

@ -6,7 +6,24 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 45;
const VERSION = 46;
function version_46($pdo)
{
$pdo->exec("
CREATE TABLE project_integrations (
id SERIAL PRIMARY KEY,
project_id INTEGER NOT NULL UNIQUE,
hipchat BOOLEAN DEFAULT '0',
hipchat_api_url VARCHAR(255) DEFAULT 'https://api.hipchat.com',
hipchat_room_id VARCHAR(255),
hipchat_room_token VARCHAR(255),
slack BOOLEAN DEFAULT '0',
slack_webhook_url VARCHAR(255),
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
)
");
}
function version_45($pdo)
{

View File

@ -6,7 +6,24 @@ use Core\Security;
use PDO;
use Model\Link;
const VERSION = 63;
const VERSION = 64;
function version_64($pdo)
{
$pdo->exec("
CREATE TABLE project_integrations (
id INTEGER PRIMARY KEY,
project_id INTEGER NOT NULL UNIQUE,
hipchat INTEGER DEFAULT 0,
hipchat_api_url TEXT DEFAULT 'https://api.hipchat.com',
hipchat_room_id TEXT,
hipchat_room_token TEXT,
slack INTEGER DEFAULT 0,
slack_webhook_url TEXT,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
)
");
}
function version_63($pdo)
{

View File

@ -34,6 +34,7 @@ class ClassProvider implements ServiceProviderInterface
'ProjectAnalytic',
'ProjectDuplication',
'ProjectDailySummary',
'ProjectIntegration',
'ProjectPermission',
'Subtask',
'SubtaskExport',

View File

@ -49,26 +49,22 @@ class ProjectActivitySubscriber extends Base implements EventSubscriberInterface
private function sendSlackNotification($event_name, array $values)
{
if ($this->config->get('integration_slack_webhook') == 1) {
$this->slackWebhook->notify(
$values['task']['project_id'],
$values['task']['id'],
$event_name,
$values
);
}
$this->slackWebhook->notify(
$values['task']['project_id'],
$values['task']['id'],
$event_name,
$values
);
}
private function sendHipchatNotification($event_name, array $values)
{
if ($this->config->get('integration_hipchat') == 1) {
$this->hipchat->notify(
$values['task']['project_id'],
$values['task']['id'],
$event_name,
$values
);
}
$this->hipchat->notify(
$values['task']['project_id'],
$values['task']['id'],
$event_name,
$values
);
}
private function getValues(GenericEvent $event)

View File

@ -2,20 +2,63 @@
<h2><?= t('Integration with third-party services') ?></h2>
</div>
<h3><i class="fa fa-github fa-fw"></i>&nbsp;<?= 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>
<form method="post" action="<?= $this->u('project', 'integration', array('project_id' => $project['id'])) ?>" autocomplete="off">
<?= $this->formCsrf() ?>
<h3><img src="assets/img/gitlab-icon.png"/>&nbsp;<?= 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>
<h3><i class="fa fa-bitbucket fa-fw"></i>&nbsp;<?= 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>
<h3><i class="fa fa-github fa-fw"></i>&nbsp;<?= 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><img src="assets/img/gitlab-icon.png"/>&nbsp;<?= 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>
<h3><i class="fa fa-bitbucket fa-fw"></i>&nbsp;<?= 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>
<h3><img src="assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3>
<div class="listing">
<?= $this->formCheckbox('hipchat', t('Send notifications to Hipchat'), 1, isset($values['hipchat']) && $values['hipchat'] == 1) ?>
<?= $this->formLabel(t('API URL'), 'hipchat_api_url') ?>
<?= $this->formText('hipchat_api_url', $values, $errors) ?>
<?= $this->formLabel(t('Room API ID or name'), 'hipchat_room_id') ?>
<?= $this->formText('hipchat_room_id', $values, $errors) ?>
<?= $this->formLabel(t('Room notification token'), 'hipchat_room_token') ?>
<?= $this->formText('hipchat_room_token', $values, $errors) ?>
<p class="form-help"><a href="http://kanboard.net/documentation/hipchat" target="_blank"><?= t('Help on Hipchat integration') ?></a></p>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</div>
<h3><i class="fa fa-slack fa-fw"></i>&nbsp;<?= t('Slack') ?></h3>
<div class="listing">
<?= $this->formCheckbox('slack', t('Send notifications to a Slack channel'), 1, isset($values['slack']) && $values['slack'] == 1) ?>
<?= $this->formLabel(t('Webhook URL'), 'slack_webhook_url') ?>
<?= $this->formText('slack_webhook_url', $values, $errors) ?>
<p class="form-help"><a href="http://kanboard.net/documentation/slack" target="_blank"><?= t('Help on Slack integration') ?></a></p>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</div>
</form>

View File

@ -1,6 +1,13 @@
Hipchat integration
===================
You can send notifications to Hipchat for all projects or only for specific projects.
- To send notifications for all projects, go to **Settings > Integrations > Hipchat**
- To send notifications for only some projects, go to **Project settings > Integrations > Hipchat**
Each project can send notifications to a separate room.
Send notifications to a room
-----------------------------
@ -23,9 +30,9 @@ This feature use the room notification token system of Hipchat.
![Hipchat settings](http://kanboard.net/screenshots/documentation/hipchat-settings.png)
1. Go to **Settings > Integrations > Hipchat**
1. Go to **Settings > Integrations > Hipchat** or **Project settings > Integrations > Hipchat**
2. Replace the API url if you use the self-hosted version of Hipchat
3. Set the room name or the room API ID
4. Copy and paste the token generated previously
Now, all Kanboard events will be sent to the Hipchat room.
Now, Kanboard events will be sent to the Hipchat room.

View File

@ -1,6 +1,13 @@
Slack integration
=================
You can send notifications to Slack for all projects or only for specific projects.
- To send notifications for all projects, go to **Settings > Integrations > Slack**
- To send notifications for only some projects, go to **Project settings > Integrations > Slack**
Each project can send notifications to a separate channel.
Send notifications to a channel
-------------------------------
@ -16,6 +23,6 @@ This feature use the [Incoming webhook](https://api.slack.com/incoming-webhooks)
1. Click on the Team dropdown and choose **Configure Integrations**
2. On the list of services, scroll-down and choose **DIY Integrations & Customizations > Incoming WebHooks**
3. Copy the webhook url to the Kanboard settings page: **Settings > Integrations > Slack**
3. Copy the webhook url to the Kanboard settings page: **Settings > Integrations > Slack** or **Project settings > Integrations > Slack**
Now, all Kanboard events will be sent to the Slack channel.
Now, Kanboard events will be sent to the Slack channel.