Send all Kanboard events to the webhook (breaking change)
This commit is contained in:
parent
db95e96f92
commit
6d5ffaa848
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Skaber',
|
||||
'Modification date' => 'Ændringsdato',
|
||||
'Completion date' => 'Afslutningsdato',
|
||||
'Webhook URL for task creation' => 'Webhook URL for opgave oprettelse',
|
||||
'Webhook URL for task modification' => 'Webhook URL opgave redigering',
|
||||
'Clone' => 'Kopier',
|
||||
'Clone Project' => 'Kopier projekt',
|
||||
'Project cloned successfully.' => 'Projektet er kopieret.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Erstellt von',
|
||||
'Modification date' => 'Änderungsdatum',
|
||||
'Completion date' => 'Abschlussdatum',
|
||||
'Webhook URL for task creation' => 'Webhook URL zur Aufgabenerstellung',
|
||||
'Webhook URL for task modification' => 'Webhook URL zur Aufgabenbearbeitung',
|
||||
'Clone' => 'duplizieren',
|
||||
'Clone Project' => 'Projekt duplizieren',
|
||||
'Project cloned successfully.' => 'Projekt wurde dupliziert.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Creador',
|
||||
'Modification date' => 'Fecha de modificación',
|
||||
'Completion date' => 'Fecha de terminación',
|
||||
'Webhook URL for task creation' => 'Disparador Web (Webhook) para la creación de tareas',
|
||||
'Webhook URL for task modification' => 'Disparador Web (Webhook) para la modificación de tareas',
|
||||
'Clone' => 'Clonar',
|
||||
'Clone Project' => 'Clonar proyecto',
|
||||
'Project cloned successfully.' => 'Proyecto clonado correctamente',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Luonut',
|
||||
'Modification date' => 'Muokkauspäivä',
|
||||
'Completion date' => 'Valmistumispäivä',
|
||||
'Webhook URL for task creation' => 'Webhook URL tehtävän luomiselle',
|
||||
'Webhook URL for task modification' => 'Webhook URL tehtävän muokkaamiselle',
|
||||
'Clone' => 'Kahdenna',
|
||||
'Clone Project' => 'Kahdenna projekti',
|
||||
'Project cloned successfully.' => 'Projekti kahdennettu onnistuneesti',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Créateur',
|
||||
'Modification date' => 'Date de modification',
|
||||
'Completion date' => 'Date de complétion',
|
||||
'Webhook URL for task creation' => 'URL du webhook pour la création de tâche',
|
||||
'Webhook URL for task modification' => 'URL du webhook pour la modification de tâche',
|
||||
'Clone' => 'Clone',
|
||||
'Clone Project' => 'Cloner le projet',
|
||||
'Project cloned successfully.' => 'Projet cloné avec succès.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Készítette',
|
||||
'Modification date' => 'Módosítás dátuma',
|
||||
'Completion date' => 'Befejezés határideje',
|
||||
'Webhook URL for task creation' => 'Webhook URL a feladat létrehozásakor',
|
||||
'Webhook URL for task modification' => 'Webhook URL a feladatot módosításakor',
|
||||
'Clone' => 'Másolat',
|
||||
'Clone Project' => 'Projekt másolása',
|
||||
'Project cloned successfully.' => 'A projekt sikeresen másolva.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Creatore',
|
||||
'Modification date' => 'Data di modifica',
|
||||
'Completion date' => 'Data di termine',
|
||||
'Webhook URL for task creation' => 'URL del Webhook per la creazione di compiti',
|
||||
'Webhook URL for task modification' => 'URL del Webhook per la modifica di compiti',
|
||||
'Clone' => 'Clona',
|
||||
'Clone Project' => 'Clona il progetto',
|
||||
'Project cloned successfully.' => 'Progetto clonato con successo.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => '作成者',
|
||||
'Modification date' => '変更日',
|
||||
'Completion date' => '完了日',
|
||||
'Webhook URL for task creation' => 'タスク作成の Webhook URL',
|
||||
'Webhook URL for task modification' => 'タスク変更の Webhook URL',
|
||||
'Clone' => '複製',
|
||||
'Clone Project' => 'プロジェクトの複製',
|
||||
'Project cloned successfully.' => 'プロジェクトを複製しました。',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Aangemaakt door',
|
||||
'Modification date' => 'Wijzigingsdatum',
|
||||
'Completion date' => 'Afgerond op',
|
||||
'Webhook URL for task creation' => 'Webhook URL voor aanmaken taak',
|
||||
'Webhook URL for task modification' => 'Webhook URL voor wijzigen taak',
|
||||
'Clone' => 'Kloon',
|
||||
'Clone Project' => 'Project klonen',
|
||||
'Project cloned successfully.' => 'Project succesvol gekloond.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Autor',
|
||||
'Modification date' => 'Data modyfyfikacji',
|
||||
'Completion date' => 'Data ukończenia',
|
||||
'Webhook URL for task creation' => 'Webhook URL do tworzenia zadań',
|
||||
'Webhook URL for task modification' => 'Webhook URL do modyfikacji zadań',
|
||||
'Clone' => 'Sklonuj',
|
||||
'Clone Project' => 'Sklonuj projekt',
|
||||
'Project cloned successfully.' => 'Projekt sklonowany pomyślnie.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Criado por',
|
||||
'Modification date' => 'Data da modificação',
|
||||
'Completion date' => 'Data da finalização',
|
||||
'Webhook URL for task creation' => 'Webhook URL para criação de tarefas',
|
||||
'Webhook URL for task modification' => 'Webhook URL para modificação de tarefa',
|
||||
'Clone' => 'Clonar',
|
||||
'Clone Project' => 'Clonar Projeto',
|
||||
'Project cloned successfully.' => 'Projeto clonado com sucesso.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Автор',
|
||||
'Modification date' => 'Дата изменения',
|
||||
'Completion date' => 'Дата завершения',
|
||||
'Webhook URL for task creation' => 'Webhook URL для создания задачи',
|
||||
'Webhook URL for task modification' => 'Webhook URL для изменения задачи',
|
||||
'Clone' => 'Клонировать',
|
||||
'Clone Project' => 'Клонировать проект',
|
||||
'Project cloned successfully.' => 'Проект клонирован.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Autor',
|
||||
'Modification date' => 'Datum izmene',
|
||||
'Completion date' => 'Datum kompletiranja',
|
||||
'Webhook URL for task creation' => 'Webhook URL zadatka za kreiranje',
|
||||
'Webhook URL for task modification' => 'Webhook URL zadatka za izmenu',
|
||||
'Clone' => 'Iskopiraj',
|
||||
'Clone Project' => 'Iskopiraj projekat',
|
||||
'Project cloned successfully.' => 'Projekat uspešno iskopiran.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Skapare',
|
||||
'Modification date' => 'Ändringsdatum',
|
||||
'Completion date' => 'Slutfört datum',
|
||||
'Webhook URL for task creation' => 'Webhook URL för att skapa uppgift',
|
||||
'Webhook URL for task modification' => 'Webhook URL för att ändra uppgift',
|
||||
'Clone' => 'Klona',
|
||||
'Clone Project' => 'Klona projekt',
|
||||
'Project cloned successfully.' => 'Projektet har klonats.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'ผู้สร้าง',
|
||||
'Modification date' => 'วันที่แก้ไข',
|
||||
'Completion date' => 'วันที่เสร็จสิ้น',
|
||||
'Webhook URL for task creation' => 'Webhook URL for task creation',
|
||||
'Webhook URL for task modification' => 'Webhook URL for task modification',
|
||||
'Clone' => 'เลียนแบบ',
|
||||
// 'Clone Project' => '',
|
||||
'Project cloned successfully.' => 'เลียนแบบโปรเจคเรียบร้อยแล้ว',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => 'Oluşturan',
|
||||
'Modification date' => 'Değişiklik tarihi',
|
||||
'Completion date' => 'Tamamlanma tarihi',
|
||||
// 'Webhook URL for task creation' => '',
|
||||
// 'Webhook URL for task modification' => '',
|
||||
'Clone' => 'Kopya oluştur',
|
||||
'Clone Project' => 'Projenin kopyasını oluştur',
|
||||
'Project cloned successfully.' => 'Proje kopyası başarıyla oluşturuldu.',
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ return array(
|
|||
'Creator' => '创建者',
|
||||
'Modification date' => '修改日期',
|
||||
'Completion date' => '完成日期',
|
||||
'Webhook URL for task creation' => '创建任务的Webhook URL',
|
||||
'Webhook URL for task modification' => '修改任务的Webhook URL',
|
||||
'Clone' => '克隆',
|
||||
'Clone Project' => '复制项目',
|
||||
'Project cloned successfully.' => '成功复制项目。',
|
||||
|
|
|
|||
|
|
@ -129,7 +129,9 @@ class Comment extends Base
|
|||
->eq('id', $values['id'])
|
||||
->update(array('comment' => $values['comment']));
|
||||
|
||||
$this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new CommentEvent($values));
|
||||
if ($result) {
|
||||
$this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new CommentEvent($values));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,20 +14,23 @@ class Webhook extends Base
|
|||
* Call the external URL
|
||||
*
|
||||
* @access public
|
||||
* @param string $url URL to call
|
||||
* @param array $task Task data
|
||||
* @param array $values Event payload
|
||||
*/
|
||||
public function notify($url, array $task)
|
||||
public function notify(array $values)
|
||||
{
|
||||
$url = $this->config->get('webhook_url');
|
||||
$token = $this->config->get('webhook_token');
|
||||
|
||||
if (strpos($url, '?') !== false) {
|
||||
$url .= '&token='.$token;
|
||||
}
|
||||
else {
|
||||
$url .= '?token='.$token;
|
||||
}
|
||||
if (! empty($url)) {
|
||||
|
||||
return $this->httpClient->post($url, $task);
|
||||
if (strpos($url, '?') !== false) {
|
||||
$url .= '&token='.$token;
|
||||
}
|
||||
else {
|
||||
$url .= '?token='.$token;
|
||||
}
|
||||
|
||||
return $this->httpClient->post($url, $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,16 @@ use PDO;
|
|||
use Core\Security;
|
||||
use Model\Link;
|
||||
|
||||
const VERSION = 70;
|
||||
const VERSION = 71;
|
||||
|
||||
function version_71($pdo)
|
||||
{
|
||||
$rq = $pdo->prepare('INSERT INTO `settings` VALUES (?, ?)');
|
||||
$rq->execute(array('webhook_url', ''));
|
||||
|
||||
$pdo->exec("DELETE FROM `settings` WHERE `option`='webhook_url_task_creation'");
|
||||
$pdo->exec("DELETE FROM `settings` WHERE `option`='webhook_url_task_modification'");
|
||||
}
|
||||
|
||||
function version_70($pdo)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,16 @@ use PDO;
|
|||
use Core\Security;
|
||||
use Model\Link;
|
||||
|
||||
const VERSION = 51;
|
||||
const VERSION = 52;
|
||||
|
||||
function version_52($pdo)
|
||||
{
|
||||
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
|
||||
$rq->execute(array('webhook_url', ''));
|
||||
|
||||
$pdo->exec("DELETE FROM settings WHERE option='webhook_url_task_creation'");
|
||||
$pdo->exec("DELETE FROM settings WHERE option='webhook_url_task_modification'");
|
||||
}
|
||||
|
||||
function version_51($pdo)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,16 @@ use Core\Security;
|
|||
use PDO;
|
||||
use Model\Link;
|
||||
|
||||
const VERSION = 69;
|
||||
const VERSION = 70;
|
||||
|
||||
function version_70($pdo)
|
||||
{
|
||||
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
|
||||
$rq->execute(array('webhook_url', ''));
|
||||
|
||||
$pdo->exec("DELETE FROM settings WHERE option='webhook_url_task_creation'");
|
||||
$pdo->exec("DELETE FROM settings WHERE option='webhook_url_task_modification'");
|
||||
}
|
||||
|
||||
function version_69($pdo)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
namespace Subscriber;
|
||||
|
||||
use Event\CommentEvent;
|
||||
use Event\GenericEvent;
|
||||
use Event\TaskEvent;
|
||||
use Model\Comment;
|
||||
use Model\Task;
|
||||
use Model\File;
|
||||
use Model\Subtask;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class WebhookSubscriber extends Base implements EventSubscriberInterface
|
||||
|
|
@ -11,32 +16,30 @@ class WebhookSubscriber extends Base implements EventSubscriberInterface
|
|||
public static function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
Task::EVENT_CREATE => array('onTaskCreation', 0),
|
||||
Task::EVENT_UPDATE => array('onTaskModification', 0),
|
||||
Task::EVENT_CLOSE => array('onTaskModification', 0),
|
||||
Task::EVENT_OPEN => array('onTaskModification', 0),
|
||||
Task::EVENT_MOVE_COLUMN => array('onTaskModification', 0),
|
||||
Task::EVENT_MOVE_POSITION => array('onTaskModification', 0),
|
||||
Task::EVENT_ASSIGNEE_CHANGE => array('onTaskModification', 0),
|
||||
Task::EVENT_CREATE => array('execute', 0),
|
||||
Task::EVENT_UPDATE => array('execute', 0),
|
||||
Task::EVENT_CLOSE => array('execute', 0),
|
||||
Task::EVENT_OPEN => array('execute', 0),
|
||||
Task::EVENT_MOVE_COLUMN => array('execute', 0),
|
||||
Task::EVENT_MOVE_POSITION => array('execute', 0),
|
||||
Task::EVENT_ASSIGNEE_CHANGE => array('execute', 0),
|
||||
Task::EVENT_MOVE_PROJECT => array('execute', 0),
|
||||
Task::EVENT_MOVE_SWIMLANE => array('execute', 0),
|
||||
Comment::EVENT_CREATE => array('execute', 0),
|
||||
Comment::EVENT_UPDATE => array('execute', 0),
|
||||
File::EVENT_CREATE => array('execute', 0),
|
||||
Subtask::EVENT_CREATE => array('execute', 0),
|
||||
Subtask::EVENT_UPDATE => array('execute', 0),
|
||||
);
|
||||
}
|
||||
|
||||
public function onTaskCreation(TaskEvent $event)
|
||||
public function execute(GenericEvent $event, $event_name)
|
||||
{
|
||||
$this->executeRequest('webhook_url_task_creation', $event);
|
||||
}
|
||||
$payload = array(
|
||||
'event_name' => $event_name,
|
||||
'event_data' => $event->getAll(),
|
||||
);
|
||||
|
||||
public function onTaskModification(TaskEvent $event)
|
||||
{
|
||||
$this->executeRequest('webhook_url_task_modification', $event);
|
||||
}
|
||||
|
||||
public function executeRequest($parameter, TaskEvent $event)
|
||||
{
|
||||
$url = $this->config->get($parameter);
|
||||
|
||||
if (! empty($url)) {
|
||||
$this->webhook->notify($url, $event->getAll());
|
||||
}
|
||||
$this->webhook->notify($payload);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<?= $this->formCsrf() ?>
|
||||
<?= $this->formHidden('id', $values) ?>
|
||||
<?= $this->formHidden('task_id', $values) ?>
|
||||
<?= $this->formHidden('user_id', $values) ?>
|
||||
|
||||
<div class="form-tabs">
|
||||
<ul class="form-tabs-nav">
|
||||
|
|
|
|||
|
|
@ -6,11 +6,8 @@
|
|||
|
||||
<?= $this->formCsrf() ?>
|
||||
|
||||
<?= $this->formLabel(t('Webhook URL for task creation'), 'webhook_url_task_creation') ?>
|
||||
<?= $this->formText('webhook_url_task_creation', $values, $errors) ?><br/>
|
||||
|
||||
<?= $this->formLabel(t('Webhook URL for task modification'), 'webhook_url_task_modification') ?>
|
||||
<?= $this->formText('webhook_url_task_modification', $values, $errors) ?><br/>
|
||||
<?= $this->formLabel(t('Webhook URL'), 'webhook_url') ?>
|
||||
<?= $this->formText('webhook_url', $values, $errors) ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
<div class="listing">
|
||||
<ul class="no-bullet">
|
||||
<li><strong><i class="fa fa-calendar"></i> <?= $this->a(t('iCalendar (iCal format, *.ics)'), 'ical', 'user', array('token' => $user['token']), false, '', '', true) ?></strong></li>
|
||||
<li><strong><i class="fa fa-calendar"></i> <?= $this->a(t('iCal feed'), 'ical', 'user', array('token' => $user['token']), false, '', '', true) ?></strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,269 @@ Webhooks
|
|||
|
||||
Webhooks are useful to perform actions with external applications.
|
||||
|
||||
- Webhooks can be used to create a task by calling a simple URL (You can also do that by using the API)
|
||||
- An external URL can be called automatically when a task is created or modified
|
||||
- Webhooks can be used to create a task by calling a simple URL (You can also do that with the API)
|
||||
- An external URL can be called automatically when an event occurs in Kanboard (task creation, comment updated, etc)
|
||||
|
||||
How to write a webhook receiver?
|
||||
--------------------------------
|
||||
|
||||
All internal events of Kanboard can be sent to an external URL.
|
||||
|
||||
- The webhook url have to be defined in **Settings > Webhooks > Webhook URL**.
|
||||
- When an event is triggered Kanboard call automatically the predefined URL
|
||||
- The data are encoded in JSON format and sent with a POST HTTP request
|
||||
- The webhook token is also sent as a query string parameter, so you can check if the request really come from Kanboard.
|
||||
- **Your custom URL must answer in less than 1 second**, those requests are synchronous (PHP limitation) and that can slow down the user interface if your script is too slow!
|
||||
|
||||
### List of supported events
|
||||
|
||||
- comment.create
|
||||
- comment.update
|
||||
- file.create
|
||||
- task.move.project
|
||||
- task.move.column
|
||||
- task.move.position
|
||||
- task.move.swimlane
|
||||
- task.update
|
||||
- task.create
|
||||
- task.close
|
||||
- task.open
|
||||
- task.assignee_change
|
||||
- subtask.update
|
||||
- subtask.create
|
||||
|
||||
### Example of HTTP request
|
||||
|
||||
```
|
||||
POST https://your_webhook_url/?token=WEBHOOK_TOKEN_HERE
|
||||
User-Agent: Kanboard Webhook
|
||||
Content-Type: application/json
|
||||
Connection: close
|
||||
|
||||
{
|
||||
"event_name": "task.move.column",
|
||||
"event_data": {
|
||||
"task_id": "1",
|
||||
"project_id": "1",
|
||||
"position": 1,
|
||||
"column_id": "1",
|
||||
"swimlane_id": "0",
|
||||
"src_column_id": "2",
|
||||
"dst_column_id": "1",
|
||||
"date_moved": "1431991532",
|
||||
"recurrence_status": "0",
|
||||
"recurrence_trigger": "0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All event payloads are in the following format:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "model.event_name",
|
||||
"event_data": {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `event_data` values are not necessary normalized across events.
|
||||
|
||||
### Examples of event payloads
|
||||
|
||||
Task creation:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "task.create",
|
||||
"event_data": {
|
||||
"title": "Demo",
|
||||
"description": "",
|
||||
"project_id": "1",
|
||||
"owner_id": "1",
|
||||
"category_id": 0,
|
||||
"swimlane_id": 0,
|
||||
"column_id": "2",
|
||||
"color_id": "yellow",
|
||||
"score": 0,
|
||||
"time_estimated": 0,
|
||||
"date_due": 0,
|
||||
"creator_id": 1,
|
||||
"date_creation": 1431991532,
|
||||
"date_modification": 1431991532,
|
||||
"date_moved": 1431991532,
|
||||
"position": 1,
|
||||
"task_id": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Task modification:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "task.update",
|
||||
"event_data": {
|
||||
"id": "1",
|
||||
"title": "Demo",
|
||||
"description": "",
|
||||
"date_creation": "1431991532",
|
||||
"color_id": "yellow",
|
||||
"project_id": "1",
|
||||
"column_id": "1",
|
||||
"owner_id": "1",
|
||||
"position": "1",
|
||||
"is_active": "1",
|
||||
"date_completed": null,
|
||||
"score": "0",
|
||||
"date_due": "0",
|
||||
"category_id": "0",
|
||||
"creator_id": "1",
|
||||
"date_modification": 1431991603,
|
||||
"reference": "",
|
||||
"date_started": 1431993600,
|
||||
"time_spent": 0,
|
||||
"time_estimated": 0,
|
||||
"swimlane_id": "0",
|
||||
"date_moved": "1431991572",
|
||||
"recurrence_status": "0",
|
||||
"recurrence_trigger": "0",
|
||||
"recurrence_factor": "0",
|
||||
"recurrence_timeframe": "0",
|
||||
"recurrence_basedate": "0",
|
||||
"recurrence_parent": null,
|
||||
"recurrence_child": null,
|
||||
"task_id": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Move a task to another column:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "task.move.column",
|
||||
"event_data": {
|
||||
"task_id": "1",
|
||||
"project_id": "1",
|
||||
"position": 1,
|
||||
"column_id": "1",
|
||||
"swimlane_id": "0",
|
||||
"src_column_id": "2",
|
||||
"dst_column_id": "1",
|
||||
"date_moved": "1431991532",
|
||||
"recurrence_status": "0",
|
||||
"recurrence_trigger": "0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Move a task to another position:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "task.move.position",
|
||||
"event_data": {
|
||||
"task_id": "2",
|
||||
"project_id": "1",
|
||||
"position": 1,
|
||||
"column_id": "1",
|
||||
"swimlane_id": "0",
|
||||
"src_column_id": "1",
|
||||
"dst_column_id": "1",
|
||||
"date_moved": "1431996905",
|
||||
"recurrence_status": "0",
|
||||
"recurrence_trigger": "0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Comment creation:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "comment.create",
|
||||
"event_data": {
|
||||
"id": 1,
|
||||
"task_id": "1",
|
||||
"user_id": "1",
|
||||
"comment": "test",
|
||||
"date": 1431991615
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Comment modification:
|
||||
|
||||
```
|
||||
{
|
||||
"event_name": "comment.update",
|
||||
"event_data": {
|
||||
"id": "1",
|
||||
"task_id": "1",
|
||||
"user_id": "1",
|
||||
"comment": "test edit"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Subtask creation:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "subtask.create",
|
||||
"event_data": {
|
||||
"id": 3,
|
||||
"task_id": "1",
|
||||
"title": "Test",
|
||||
"user_id": "1",
|
||||
"time_estimated": "2",
|
||||
"position": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Subtask modification:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "subtask.update",
|
||||
"event_data": {
|
||||
"id": "1",
|
||||
"status": 1,
|
||||
"task_id": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File upload:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "file.create",
|
||||
"event_data": {
|
||||
"task_id": "1",
|
||||
"name": "test.png"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Screenshot created:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_name": "file.create",
|
||||
"event_data": {
|
||||
"task_id": "2",
|
||||
"name": "Screenshot taken May 19, 2015 at 10:56 AM"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: Webhooks configuration and payload have changed since Kanboard >= 1.0.15
|
||||
|
||||
How to create a task with a webhook?
|
||||
------------------------------------
|
||||
|
|
@ -38,61 +299,3 @@ Base URL: `http://YOUR_SERVER_HOSTNAME/?controller=webhook&action=task`
|
|||
- `column_id`: Column on the board (Get the column id from the projects page, mouse over on the column name)
|
||||
|
||||
Only the token and the title parameters are mandatory. The different id can also be found in the database.
|
||||
|
||||
How to call an external URL when a task is created or updated?
|
||||
--------------------------------------------------------------
|
||||
|
||||
- There is two events available: **task creation** and **task modification**
|
||||
- External URLs can be defined on the settings page
|
||||
- When an event is triggered Kanboard call automatically the predefined URL
|
||||
- The task data encoded in JSON is sent with a POST HTTP request
|
||||
- The webhook token is also sent as a query string parameter, so you can check if the request is not usurped, it's also better if you use HTTPS.
|
||||
- **Your custom URL must answer in less than 1 second**, those requests are synchronous (PHP limitation) and that can slow down the application if your script is too slow!
|
||||
|
||||
### Quick example with PHP
|
||||
|
||||
Start by creating a basic PHP script `index.php`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$body = file_get_contents('php://input');
|
||||
file_put_contents('/tmp/webhook', $body);
|
||||
```
|
||||
|
||||
This script dump the task data to the file `/tmp/webhook`.
|
||||
|
||||
Now run a webserver from the command line:
|
||||
|
||||
```bash
|
||||
php -S 127.0.0.1:8081
|
||||
```
|
||||
|
||||
After that, go the settings page of Kanboard, enter the right URL here `http://127.0.0.1:8081/`.
|
||||
And finally, create a task and you should see the JSON payload in the file.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"task_id":"2",
|
||||
"title":"boo",
|
||||
"description":"",
|
||||
"project_id":"1",
|
||||
"owner_id":"0",
|
||||
"category_id":"0",
|
||||
"column_id":"2",
|
||||
"color_id":"yellow",
|
||||
"score":0,
|
||||
"date_due":0,
|
||||
"creator_id":1,
|
||||
"date_creation":1405981280,
|
||||
"position":0
|
||||
}
|
||||
```
|
||||
|
||||
For our example, Kanboard use this request to call your program:
|
||||
|
||||
```
|
||||
POST http:://127.0.0.1:8081/?token=RANDOM_TOKEN_HERE
|
||||
|
||||
{... JSON payload ...}
|
||||
```
|
||||
|
|
@ -11,6 +11,35 @@ use SimpleLogger\File;
|
|||
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
class FakeHttpClient
|
||||
{
|
||||
private $url = '';
|
||||
private $data = array();
|
||||
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function toPrettyJson()
|
||||
{
|
||||
return json_encode($this->data, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function post($url, array $data)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->data = $data;
|
||||
//echo $this->toPrettyJson();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Base extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $container;
|
||||
|
|
@ -43,6 +72,7 @@ abstract class Base extends PHPUnit_Framework_TestCase
|
|||
|
||||
$this->container['logger'] = new Logger;
|
||||
$this->container['logger']->setLogger(new File('/dev/null'));
|
||||
$this->container['httpClient'] = new FakeHttpClient;
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
|
|
|
|||
|
|
@ -138,15 +138,15 @@ class TimetableTest extends Base
|
|||
$this->assertNotFalse($w->create(1, 2, '09:30', '12:00'));
|
||||
$this->assertNotFalse($w->create(1, 2, '13:00', '17:00'));
|
||||
|
||||
$monday = new DateTime('Monday');
|
||||
$tuesday = new DateTime('Tuesday');
|
||||
$monday = new DateTime('next Monday');
|
||||
$tuesday = new DateTime('next Monday + 1 day');
|
||||
|
||||
$timetable = $t->calculate(1, new DateTime('Monday'), new DateTime('Monday + 6 days'));
|
||||
$this->assertNotEmpty($timetable);
|
||||
$this->assertCount(4, $timetable);
|
||||
|
||||
// Start to work before timetable
|
||||
$date = new DateTime('Monday');
|
||||
$date = clone($monday);
|
||||
$date->setTime(5, 02);
|
||||
|
||||
$slot = $t->findClosestTimeSlot($date, $timetable);
|
||||
|
|
@ -155,7 +155,7 @@ class TimetableTest extends Base
|
|||
$this->assertEquals($monday->format('Y-m-d').' 12:00', $slot[1]->format('Y-m-d H:i'));
|
||||
|
||||
// Start to work at the end of the timeslot
|
||||
$date = new DateTime('Monday');
|
||||
$date = clone($monday);
|
||||
$date->setTime(12, 02);
|
||||
|
||||
$slot = $t->findClosestTimeSlot($date, $timetable);
|
||||
|
|
@ -164,7 +164,7 @@ class TimetableTest extends Base
|
|||
$this->assertEquals($monday->format('Y-m-d').' 12:00', $slot[1]->format('Y-m-d H:i'));
|
||||
|
||||
// Start to work at lunch time
|
||||
$date = new DateTime('Monday');
|
||||
$date = clone($monday);
|
||||
$date->setTime(12, 32);
|
||||
|
||||
$slot = $t->findClosestTimeSlot($date, $timetable);
|
||||
|
|
@ -173,7 +173,7 @@ class TimetableTest extends Base
|
|||
$this->assertEquals($monday->format('Y-m-d').' 17:00', $slot[1]->format('Y-m-d H:i'));
|
||||
|
||||
// Start to work early in the morning
|
||||
$date = new DateTime('Tuesday');
|
||||
$date = clone($tuesday);
|
||||
$date->setTime(8, 02);
|
||||
|
||||
$slot = $t->findClosestTimeSlot($date, $timetable);
|
||||
|
|
@ -192,47 +192,50 @@ class TimetableTest extends Base
|
|||
$this->assertNotFalse($w->create(1, 2, '09:30', '12:00'));
|
||||
$this->assertNotFalse($w->create(1, 2, '13:00', '17:00'));
|
||||
|
||||
$monday = new DateTime('next Monday');
|
||||
$tuesday = new DateTime('next Monday + 1 day');
|
||||
|
||||
// Different day
|
||||
$start = new DateTime('Monday');
|
||||
$start = clone($monday);
|
||||
$start->setTime(16, 02);
|
||||
|
||||
$end = new DateTime('Tuesday');
|
||||
$end = clone($tuesday);
|
||||
$end->setTime(10, 03);
|
||||
|
||||
$this->assertEquals(1.5, $t->calculateEffectiveDuration(1, $start, $end));
|
||||
|
||||
// Same time slot
|
||||
$start = new DateTime('Monday');
|
||||
$start = clone($monday);
|
||||
$start->setTime(16, 02);
|
||||
|
||||
$end = new DateTime('Monday');
|
||||
$end = clone($monday);
|
||||
$end->setTime(17, 03);
|
||||
|
||||
$this->assertEquals(1, $t->calculateEffectiveDuration(1, $start, $end));
|
||||
|
||||
// Intermediate time slot
|
||||
$start = new DateTime('Monday');
|
||||
$start = clone($monday);
|
||||
$start->setTime(10, 02);
|
||||
|
||||
$end = new DateTime('Tuesday');
|
||||
$end = clone($tuesday);
|
||||
$end->setTime(16, 03);
|
||||
|
||||
$this->assertEquals(11.5, $t->calculateEffectiveDuration(1, $start, $end));
|
||||
|
||||
// Different day
|
||||
$start = new DateTime('Monday');
|
||||
$start = clone($monday);
|
||||
$start->setTime(9, 02);
|
||||
|
||||
$end = new DateTime('Tuesday');
|
||||
$end = clone($tuesday);
|
||||
$end->setTime(10, 03);
|
||||
|
||||
$this->assertEquals(7, $t->calculateEffectiveDuration(1, $start, $end));
|
||||
|
||||
// Start before first time slot
|
||||
$start = new DateTime('Monday');
|
||||
$start = clone($monday);
|
||||
$start->setTime(5, 32);
|
||||
|
||||
$end = new DateTime('Tuesday');
|
||||
$end = clone($tuesday);
|
||||
$end->setTime(11, 17);
|
||||
|
||||
$this->assertEquals(8.25, $t->calculateEffectiveDuration(1, $start, $end));
|
||||
|
|
@ -242,10 +245,10 @@ class TimetableTest extends Base
|
|||
{
|
||||
$t = new Timetable($this->container);
|
||||
|
||||
$start = new DateTime('Monday');
|
||||
$start = new DateTime('next Monday');
|
||||
$start->setTime(16, 02);
|
||||
|
||||
$end = new DateTime('Monday');
|
||||
$end = new DateTime('next Monday');
|
||||
$end->setTime(17, 03);
|
||||
|
||||
$this->assertEquals(1, $t->calculateEffectiveDuration(1, $start, $end));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/Base.php';
|
||||
|
||||
use Model\Config;
|
||||
use Model\Task;
|
||||
use Model\TaskCreation;
|
||||
use Model\TaskModification;
|
||||
use Model\Project;
|
||||
use Model\Comment;
|
||||
use Subscriber\WebhookSubscriber;
|
||||
|
||||
class WebhookTest extends Base
|
||||
{
|
||||
public function testTaskCreation()
|
||||
{
|
||||
$c = new Config($this->container);
|
||||
$p = new Project($this->container);
|
||||
$tc = new TaskCreation($this->container);
|
||||
$this->container['dispatcher']->addSubscriber(new WebhookSubscriber($this->container));
|
||||
|
||||
$c->save(array('webhook_url' => 'http://localhost/?task-creation'));
|
||||
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test')));
|
||||
$this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test')));
|
||||
|
||||
$this->assertStringStartsWith('http://localhost/?task-creation&token=', $this->container['httpClient']->getUrl());
|
||||
|
||||
$event = $this->container['httpClient']->getData();
|
||||
$this->assertNotEmpty($event);
|
||||
$this->assertArrayHasKey('event_name', $event);
|
||||
$this->assertArrayHasKey('event_data', $event);
|
||||
$this->assertEquals('task.create', $event['event_name']);
|
||||
$this->assertNotEmpty($event['event_data']);
|
||||
|
||||
$this->assertArrayHasKey('project_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('task_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('title', $event['event_data']);
|
||||
$this->assertArrayHasKey('column_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('color_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('swimlane_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('date_creation', $event['event_data']);
|
||||
$this->assertArrayHasKey('date_modification', $event['event_data']);
|
||||
$this->assertArrayHasKey('date_moved', $event['event_data']);
|
||||
$this->assertArrayHasKey('position', $event['event_data']);
|
||||
}
|
||||
|
||||
public function testTaskModification()
|
||||
{
|
||||
$c = new Config($this->container);
|
||||
$p = new Project($this->container);
|
||||
$tc = new TaskCreation($this->container);
|
||||
$tm = new TaskModification($this->container);
|
||||
$this->container['dispatcher']->addSubscriber(new WebhookSubscriber($this->container));
|
||||
|
||||
$c->save(array('webhook_url' => 'http://localhost/modif/'));
|
||||
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test')));
|
||||
$this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test')));
|
||||
$this->assertTrue($tm->update(array('id' => 1, 'title' => 'test update')));
|
||||
|
||||
$this->assertStringStartsWith('http://localhost/modif/?token=', $this->container['httpClient']->getUrl());
|
||||
|
||||
$event = $this->container['httpClient']->getData();
|
||||
$this->assertNotEmpty($event);
|
||||
$this->assertArrayHasKey('event_name', $event);
|
||||
$this->assertArrayHasKey('event_data', $event);
|
||||
$this->assertEquals('task.update', $event['event_name']);
|
||||
$this->assertNotEmpty($event['event_data']);
|
||||
|
||||
$this->assertArrayHasKey('project_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('task_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('title', $event['event_data']);
|
||||
$this->assertArrayHasKey('column_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('color_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('swimlane_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('date_creation', $event['event_data']);
|
||||
$this->assertArrayHasKey('date_modification', $event['event_data']);
|
||||
$this->assertArrayHasKey('date_moved', $event['event_data']);
|
||||
$this->assertArrayHasKey('position', $event['event_data']);
|
||||
}
|
||||
|
||||
public function testCommentCreation()
|
||||
{
|
||||
$c = new Config($this->container);
|
||||
$p = new Project($this->container);
|
||||
$tc = new TaskCreation($this->container);
|
||||
$cm = new Comment($this->container);
|
||||
$this->container['dispatcher']->addSubscriber(new WebhookSubscriber($this->container));
|
||||
|
||||
$c->save(array('webhook_url' => 'http://localhost/comment'));
|
||||
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test')));
|
||||
$this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test')));
|
||||
$this->assertEquals(1, $cm->create(array('task_id' => 1, 'comment' => 'test comment', 'user_id' => 1)));
|
||||
|
||||
$this->assertStringStartsWith('http://localhost/comment?token=', $this->container['httpClient']->getUrl());
|
||||
|
||||
$event = $this->container['httpClient']->getData();
|
||||
$this->assertNotEmpty($event);
|
||||
$this->assertArrayHasKey('event_name', $event);
|
||||
$this->assertArrayHasKey('event_data', $event);
|
||||
$this->assertEquals('comment.create', $event['event_name']);
|
||||
$this->assertNotEmpty($event['event_data']);
|
||||
|
||||
$this->assertArrayHasKey('task_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('user_id', $event['event_data']);
|
||||
$this->assertArrayHasKey('comment', $event['event_data']);
|
||||
$this->assertArrayHasKey('id', $event['event_data']);
|
||||
$this->assertEquals('test comment', $event['event_data']['comment']);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue