Add task transitions history

This commit is contained in:
Frederic Guillot 2015-03-26 22:40:46 -04:00
parent 93fa9b5cba
commit 87d2c6d99e
29 changed files with 291 additions and 22 deletions

View File

@ -526,4 +526,19 @@ class Task extends Base
'subtask_paginator' => $subtask_paginator,
)));
}
/**
* Display the task transitions
*
* @access public
*/
public function transitions()
{
$task = $this->getTask();
$this->response->html($this->taskLayout('task/transitions', array(
'task' => $task,
'transitions' => $this->transition->getAllByTask($task['id']),
)));
}
}

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -811,4 +811,7 @@ return array(
'Move the task to another column when assignee is cleared' => 'Déplacer la tâche dans une autre colonne lorsque celle-ci n\'est plus assignée',
'Source column' => 'Colonne d\'origine',
'Show subtask estimates in the user calendar' => 'Afficher le temps estimé des sous-tâches dans le calendrier utilisateur',
'Transitions' => 'Transitions',
'Executer' => 'Exécutant',
'Time spent in the column' => 'Temps passé dans la colonne',
);

View File

@ -809,4 +809,7 @@ return array(
'Move the task to another column when assignee is cleared' => 'Feladat másik oszlopba helyezése felhasználóhoz rendélés törlésekor',
'Source column' => 'Forrás oszlop',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -809,4 +809,7 @@ return array(
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
// 'Show subtask estimates in the user calendar' => '',
// 'Transitions' => '',
// 'Executer' => '',
// 'Time spent in the column' => '',
);

View File

@ -143,6 +143,9 @@ class TaskPosition extends Base
'position' => $new_position,
'column_id' => $new_column_id,
'swimlane_id' => $new_swimlane_id,
'src_column_id' => $task['column_id'],
'dst_column_id' => $new_column_id,
'date_moved' => $task['date_moved'],
);
if ($task['swimlane_id'] != $new_swimlane_id) {

67
app/Model/Transition.php Normal file
View File

@ -0,0 +1,67 @@
<?php
namespace Model;
/**
* Transition model
*
* @package model
* @author Frederic Guillot
*/
class Transition extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'transitions';
/**
* Save transition event
*
* @access public
* @param integer $user_id
* @param array $task
* @return boolean
*/
public function save($user_id, array $task)
{
return $this->db->table(self::TABLE)->insert(array(
'user_id' => $user_id,
'project_id' => $task['project_id'],
'task_id' => $task['task_id'],
'src_column_id' => $task['src_column_id'],
'dst_column_id' => $task['dst_column_id'],
'date' => time(),
'time_spent' => time() - $task['date_moved']
));
}
/**
* Get all transitions by task
*
* @access public
* @param integer $task_id
* @return array
*/
public function getAllByTask($task_id)
{
return $this->db->table(self::TABLE)
->columns(
'src.title as src_column',
'dst.title as dst_column',
User::TABLE.'.name',
User::TABLE.'.username',
self::TABLE.'.user_id',
self::TABLE.'.date',
self::TABLE.'.time_spent'
)
->eq('task_id', $task_id)
->desc('date')
->join(User::TABLE, 'id', 'user_id')
->join(Board::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src')
->join(Board::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst')
->findAll();
}
}

View File

@ -6,7 +6,31 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 55;
const VERSION = 56;
function version_56($pdo)
{
$pdo->exec('CREATE TABLE transitions (
`id` INT NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL,
`project_id` INT NOT NULL,
`task_id` INT NOT NULL,
`src_column_id` INT NOT NULL,
`dst_column_id` INT NOT NULL,
`date` INT NOT NULL,
`time_spent` INT DEFAULT 0,
FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE,
FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
PRIMARY KEY(id)
) ENGINE=InnoDB CHARSET=utf8');
$pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)");
$pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)");
$pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)");
}
function version_55($pdo)
{

View File

@ -6,7 +6,30 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 36;
const VERSION = 37;
function version_37($pdo)
{
$pdo->exec('CREATE TABLE transitions (
"id" SERIAL PRIMARY KEY,
"user_id" INTEGER NOT NULL,
"project_id" INTEGER NOT NULL,
"task_id" INTEGER NOT NULL,
"src_column_id" INTEGER NOT NULL,
"dst_column_id" INTEGER NOT NULL,
"date" INTEGER NOT NULL,
"time_spent" INTEGER DEFAULT 0,
FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE,
FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
)');
$pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)");
$pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)");
$pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)");
}
function version_36($pdo)
{

View File

@ -6,7 +6,30 @@ use Core\Security;
use PDO;
use Model\Link;
const VERSION = 54;
const VERSION = 55;
function version_55($pdo)
{
$pdo->exec('CREATE TABLE transitions (
"id" INTEGER PRIMARY KEY,
"user_id" INTEGER NOT NULL,
"project_id" INTEGER NOT NULL,
"task_id" INTEGER NOT NULL,
"src_column_id" INTEGER NOT NULL,
"dst_column_id" INTEGER NOT NULL,
"date" INTEGER NOT NULL,
"time_spent" INTEGER DEFAULT 0,
FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE,
FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
)');
$pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)");
$pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)");
$pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)");
}
function version_54($pdo)
{

View File

@ -56,6 +56,7 @@ class ClassProvider implements ServiceProviderInterface
'TimetableWeek',
'TimetableOff',
'TimetableExtra',
'Transition',
'User',
'UserSession',
'Webhook',

View File

@ -14,6 +14,7 @@ use Subscriber\ProjectModificationDateSubscriber;
use Subscriber\WebhookSubscriber;
use Subscriber\SubtaskTimesheetSubscriber;
use Subscriber\TaskMovedDateSubscriber;
use Subscriber\TransitionSubscriber;
class EventDispatcherProvider implements ServiceProviderInterface
{
@ -29,6 +30,7 @@ class EventDispatcherProvider implements ServiceProviderInterface
$container['dispatcher']->addSubscriber(new NotificationSubscriber($container));
$container['dispatcher']->addSubscriber(new SubtaskTimesheetSubscriber($container));
$container['dispatcher']->addSubscriber(new TaskMovedDateSubscriber($container));
$container['dispatcher']->addSubscriber(new TransitionSubscriber($container));
// Automatic actions
$container['action']->attachEvents();

View File

@ -0,0 +1,26 @@
<?php
namespace Subscriber;
use Event\TaskEvent;
use Model\Task;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class TransitionSubscriber extends Base implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
Task::EVENT_MOVE_COLUMN => array('execute', 0),
);
}
public function execute(TaskEvent $event)
{
$user_id = $this->userSession->getId();
if (! empty($user_id)) {
$this->transition->save($user_id, $event->getAll());
}
}
}

View File

@ -4,6 +4,9 @@
<li>
<?= $this->a(t('Summary'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<li>
<?= $this->a(t('Transitions'), 'task', 'transitions', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<?php if ($task['time_estimated'] > 0 || $task['time_spent'] > 0): ?>
<li>
<?= $this->a(t('Time tracking'), 'task', 'timesheet', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>

View File

@ -0,0 +1,26 @@
<div class="page-header">
<h2><?= t('Transitions') ?></h2>
</div>
<?php if (empty($transitions)): ?>
<p class="alert"><?= t('There is nothing to show.') ?></p>
<?php else: ?>
<table class="table-stripped">
<tr>
<th><?= t('Date') ?></th>
<th><?= t('Source column') ?></th>
<th><?= t('Destination column') ?></th>
<th><?= t('Executer') ?></th>
<th><?= t('Time spent in the column') ?></th>
</tr>
<?php foreach ($transitions as $transition): ?>
<tr>
<td><?= dt('%B %e, %Y at %k:%M %p', $transition['date']) ?></td>
<td><?= $this->e($transition['src_column']) ?></td>
<td><?= $this->e($transition['dst_column']) ?></td>
<td><?= $this->a($this->e($transition['name'] ?: $transition['username']), 'user', 'show', array('user_id' => $transition['user_id'])) ?></td>
<td><?= n(round($transition['time_spent'] / 3600, 2)).' '.t('hours') ?></td>
</tr>
<?php endforeach ?>
</table>
<?php endif ?>

43
composer.lock generated
View File

@ -88,12 +88,12 @@
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
"reference": "d7ef5561d6d76c50717492822813125f9699700a"
"reference": "cd6a571d2de5c0b30d538d7cd6603dc16b25b844"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/d7ef5561d6d76c50717492822813125f9699700a",
"reference": "d7ef5561d6d76c50717492822813125f9699700a",
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/cd6a571d2de5c0b30d538d7cd6603dc16b25b844",
"reference": "cd6a571d2de5c0b30d538d7cd6603dc16b25b844",
"shasum": ""
},
"require": {
@ -117,7 +117,7 @@
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb",
"time": "2015-03-15 21:03:40"
"time": "2015-03-27 02:21:18"
},
{
"name": "fguillot/simple-validator",
@ -393,17 +393,17 @@
},
{
"name": "symfony/console",
"version": "v2.6.4",
"version": "v2.6.5",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "e44154bfe3e41e8267d7a3794cd9da9a51cfac34"
"reference": "53f86497ccd01677e22435cfb7262599450a90d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/e44154bfe3e41e8267d7a3794cd9da9a51cfac34",
"reference": "e44154bfe3e41e8267d7a3794cd9da9a51cfac34",
"url": "https://api.github.com/repos/symfony/Console/zipball/53f86497ccd01677e22435cfb7262599450a90d1",
"reference": "53f86497ccd01677e22435cfb7262599450a90d1",
"shasum": ""
},
"require": {
@ -412,6 +412,7 @@
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1",
"symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.1"
},
"suggest": {
@ -446,21 +447,21 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2015-01-25 04:39:26"
"time": "2015-03-13 17:37:22"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.6.4",
"version": "v2.6.5",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
"reference": "f75989f3ab2743a82fe0b03ded2598a2b1546813"
"reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/f75989f3ab2743a82fe0b03ded2598a2b1546813",
"reference": "f75989f3ab2743a82fe0b03ded2598a2b1546813",
"url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/70f7c8478739ad21e3deef0d977b38c77f1fb284",
"reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284",
"shasum": ""
},
"require": {
@ -471,6 +472,7 @@
"symfony/config": "~2.0,>=2.0.5",
"symfony/dependency-injection": "~2.6",
"symfony/expression-language": "~2.6",
"symfony/phpunit-bridge": "~2.7",
"symfony/stopwatch": "~2.3"
},
"suggest": {
@ -504,28 +506,31 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "http://symfony.com",
"time": "2015-02-01 16:10:57"
"time": "2015-03-13 17:37:22"
}
],
"packages-dev": [
{
"name": "symfony/stopwatch",
"version": "v2.6.4",
"version": "v2.6.5",
"target-dir": "Symfony/Component/Stopwatch",
"source": {
"type": "git",
"url": "https://github.com/symfony/Stopwatch.git",
"reference": "e8da5286132ba75ce4b4275fbf0f4cd369bfd71c"
"reference": "ba4e774f71e2ce3e3f65cabac4031b9029972af5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Stopwatch/zipball/e8da5286132ba75ce4b4275fbf0f4cd369bfd71c",
"reference": "e8da5286132ba75ce4b4275fbf0f4cd369bfd71c",
"url": "https://api.github.com/repos/symfony/Stopwatch/zipball/ba4e774f71e2ce3e3f65cabac4031b9029972af5",
"reference": "ba4e774f71e2ce3e3f65cabac4031b9029972af5",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -553,7 +558,7 @@
],
"description": "Symfony Stopwatch Component",
"homepage": "http://symfony.com",
"time": "2015-01-03 08:01:59"
"time": "2015-02-24 11:52:21"
}
],
"aliases": [],