summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Controller/Config.php2
-rw-r--r--app/Controller/Subtask.php2
-rw-r--r--app/Controller/Timer.php35
-rw-r--r--app/Helper/Datetime.php33
-rw-r--r--app/Helper/Task.php27
-rw-r--r--app/Locale/da_DK/translations.php5
-rw-r--r--app/Locale/de_DE/translations.php5
-rw-r--r--app/Locale/es_ES/translations.php5
-rw-r--r--app/Locale/fi_FI/translations.php5
-rw-r--r--app/Locale/fr_FR/translations.php5
-rw-r--r--app/Locale/hu_HU/translations.php5
-rw-r--r--app/Locale/it_IT/translations.php5
-rw-r--r--app/Locale/ja_JP/translations.php5
-rw-r--r--app/Locale/nl_NL/translations.php5
-rw-r--r--app/Locale/pl_PL/translations.php5
-rw-r--r--app/Locale/pt_BR/translations.php5
-rw-r--r--app/Locale/ru_RU/translations.php5
-rw-r--r--app/Locale/sr_Latn_RS/translations.php5
-rw-r--r--app/Locale/sv_SE/translations.php5
-rw-r--r--app/Locale/th_TH/translations.php5
-rw-r--r--app/Locale/tr_TR/translations.php5
-rw-r--r--app/Locale/zh_CN/translations.php5
-rw-r--r--app/Model/Acl.php1
-rw-r--r--app/Model/Budget.php2
-rw-r--r--app/Model/Project.php2
-rw-r--r--app/Model/Subtask.php17
-rw-r--r--app/Model/SubtaskTimeTracking.php23
-rw-r--r--app/Model/TaskFilter.php2
-rw-r--r--app/Model/TaskFinder.php4
-rw-r--r--app/Schema/Mysql.php7
-rw-r--r--app/Schema/Postgres.php7
-rw-r--r--app/Schema/Sqlite.php7
-rw-r--r--app/ServiceProvider/EventDispatcherProvider.php4
-rw-r--r--app/Subscriber/SubtaskTimeTrackingSubscriber.php (renamed from app/Subscriber/SubtaskTimesheetSubscriber.php)4
-rw-r--r--app/Template/board/task_private.php4
-rw-r--r--app/Template/config/project.php1
-rw-r--r--app/Template/subtask/show.php28
-rw-r--r--composer.json2
-rw-r--r--composer.lock13
-rw-r--r--tests/units/DatetimeHelperTest.php12
-rw-r--r--tests/units/SubtaskTimeTrackingTest.php52
41 files changed, 297 insertions, 79 deletions
diff --git a/app/Controller/Config.php b/app/Controller/Config.php
index fbd374ab..19bc2767 100644
--- a/app/Controller/Config.php
+++ b/app/Controller/Config.php
@@ -42,7 +42,7 @@ class Config extends Base
switch ($redirect) {
case 'project':
- $values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0);
+ $values += array('subtask_restriction' => 0);
break;
case 'integrations':
$values += array('integration_slack_webhook' => 0, 'integration_hipchat' => 0, 'integration_gravatar' => 0, 'integration_jabber' => 0);
diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php
index 5baa6004..6ee94333 100644
--- a/app/Controller/Subtask.php
+++ b/app/Controller/Subtask.php
@@ -256,7 +256,7 @@ class Subtask extends Base
case 'dashboard':
$this->response->redirect($this->helper->url->to('app', 'index'));
default:
- $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#subtasks');
}
}
diff --git a/app/Controller/Timer.php b/app/Controller/Timer.php
new file mode 100644
index 00000000..2a4531ba
--- /dev/null
+++ b/app/Controller/Timer.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Time Tracking controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Timer extends Base
+{
+ /**
+ * Start/stop timer for subtasks
+ *
+ * @access public
+ */
+ public function subtask()
+ {
+ $project_id = $this->request->getIntegerParam('project_id');
+ $task_id = $this->request->getIntegerParam('task_id');
+ $subtask_id = $this->request->getIntegerParam('subtask_id');
+ $timer = $this->request->getStringParam('timer');
+
+ if ($timer === 'start') {
+ $this->subtaskTimeTracking->logStartTime($subtask_id, $this->userSession->getId());
+ }
+ else if ($timer === 'stop') {
+ $this->subtaskTimeTracking->logEndTime($subtask_id, $this->userSession->getId());
+ $this->subtaskTimeTracking->updateTaskTimeTracking($task_id);
+ }
+
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id)).'#subtasks');
+ }
+}
diff --git a/app/Helper/Datetime.php b/app/Helper/Datetime.php
index 3a9c4c48..74ea9bdd 100644
--- a/app/Helper/Datetime.php
+++ b/app/Helper/Datetime.php
@@ -11,6 +11,39 @@ namespace Helper;
class Datetime extends \Core\Base
{
/**
+ * Get the age of an item in quasi human readable format.
+ * It's in this format: <1h , NNh, NNd
+ *
+ * @access public
+ * @param integer $timestamp Unix timestamp of the artifact for which age will be calculated
+ * @param integer $now Compare with this timestamp (Default value is the current unix timestamp)
+ * @return string
+ */
+ public function age($timestamp, $now = null)
+ {
+ if ($now === null) {
+ $now = time();
+ }
+
+ $diff = $now - $timestamp;
+
+ if ($diff < 900) {
+ return t('<15m');
+ }
+ if ($diff < 1200) {
+ return t('<30m');
+ }
+ else if ($diff < 3600) {
+ return t('<1h');
+ }
+ else if ($diff < 86400) {
+ return '~'.t('%dh', $diff / 3600);
+ }
+
+ return t('%dd', ($now - $timestamp) / 86400);
+ }
+
+ /**
* Get all hours for day
*
* @access public
diff --git a/app/Helper/Task.php b/app/Helper/Task.php
index 13bdb07a..79c412e1 100644
--- a/app/Helper/Task.php
+++ b/app/Helper/Task.php
@@ -10,33 +10,6 @@ namespace Helper;
*/
class Task extends \Core\Base
{
- /**
- * Get the age of an item in quasi human readable format.
- * It's in this format: <1h , NNh, NNd
- *
- * @access public
- * @param integer $timestamp Unix timestamp of the artifact for which age will be calculated
- * @param integer $now Compare with this timestamp (Default value is the current unix timestamp)
- * @return string
- */
- public function age($timestamp, $now = null)
- {
- if ($now === null) {
- $now = time();
- }
-
- $diff = $now - $timestamp;
-
- if ($diff < 3600) {
- return t('<1h');
- }
- else if ($diff < 86400) {
- return t('%dh', $diff / 3600);
- }
-
- return t('%dd', ($now - $timestamp) / 86400);
- }
-
public function getColors()
{
return $this->color->getList();
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index 82f9534c..3adf63d8 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -614,7 +614,6 @@ return array(
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
- // 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index b004905b..20f30de0 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Benutzer Dashboard',
'Allow only one subtask in progress at the same time for a user' => 'Erlaube nur eine Teilaufgabe pro Benutzer zu bearbeiten',
'Edit column "%s"' => 'Spalte "%s" bearbeiten',
- 'Enable time tracking for subtasks' => 'Aktiviere Zeiterfassung für Teilaufgaben',
'Select the new status of the subtask: "%s"' => 'Wähle einen neuen Status für Teilaufgabe: "%s"',
'Subtask timesheet' => 'Teilaufgaben Zeiterfassung',
'There is nothing to show.' => 'Es ist nichts zum Anzeigen vorhanden.',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index 8aa56f6c..cfb50ba8 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Tablero de usuario',
'Allow only one subtask in progress at the same time for a user' => 'Permitir sólo una subtarea en progreso a la vez para cada usuario',
'Edit column "%s"' => 'Editar columna %s',
- 'Enable time tracking for subtasks' => 'Activar seguimiento temporal para subtareas',
'Select the new status of the subtask: "%s"' => 'Seleccionar el nuevo estado de la subtarea: "%s"',
'Subtask timesheet' => 'Hoja temporal de subtarea',
'There is nothing to show.' => 'Nada que mostrar',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index 601ab9fd..ae699514 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -614,7 +614,6 @@ return array(
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
- // 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index 246c2471..8d5ed670 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -616,7 +616,6 @@ return array(
'User dashboard' => 'Tableau de bord de l\'utilisateur',
'Allow only one subtask in progress at the same time for a user' => 'Autoriser une seule sous-tâche en progrès en même temps pour un utilisateur',
'Edit column "%s"' => 'Modifier la colonne « %s »',
- 'Enable time tracking for subtasks' => 'Activer la feuille de temps pour les sous-tâches',
'Select the new status of the subtask: "%s"' => 'Selectionnez le nouveau statut de la sous-tâche : « %s »',
'Subtask timesheet' => 'Feuille de temps des sous-tâches',
'There is nothing to show.' => 'Il n\'y a rien à montrer.',
@@ -948,4 +947,8 @@ return array(
'%%Y-%%m-%%d' => '%%d/%%m/%%Y',
'Total for all columns' => 'Total pour toutes les colonnes',
'You need at least 2 days of data to show the chart.' => 'Vous avez besoin d\'au minimum 2 jours de données pour afficher le graphique.',
+ '<15m' => '<15m',
+ '<30m' => '<30m',
+ 'Stop timer' => 'Stopper le chrono',
+ 'Start timer' => 'Démarrer le chrono',
);
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index 6c1861eb..98c8e793 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Felhasználói vezérlőpult',
'Allow only one subtask in progress at the same time for a user' => 'Egyszerre csak egy folyamatban levő részfeladat engedélyezése a felhasználóknak',
'Edit column "%s"' => 'Oszlop szerkesztés: %s',
- 'Enable time tracking for subtasks' => 'Idő követés engedélyezése a részfeladatokhoz',
'Select the new status of the subtask: "%s"' => 'Részfeladat állapot változtatás: %s',
'Subtask timesheet' => 'Részfeladat idővonal',
'There is nothing to show.' => 'Nincs megjelenítendő adat.',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index 7bfa506a..f870a162 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Bacheca utente',
'Allow only one subtask in progress at the same time for a user' => 'Permetti un solo sotto-compito in progresso per utente nello stesso tempo',
'Edit column "%s"' => 'Modifica la colonna "%s"',
- 'Enable time tracking for subtasks' => 'Abilita la gestione del tempo per i sotto-compiti',
'Select the new status of the subtask: "%s"' => 'Selziona il nuovo status per il sotto-compito: "%s"',
'Subtask timesheet' => 'Timesheet del sotto-compito',
'There is nothing to show.' => 'Nulla da mostrare.',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index 210621d0..19c2bf82 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'ユーザダッシュボード',
'Allow only one subtask in progress at the same time for a user' => '一人のユーザにつき一つのタスクのみ進行中にできます',
'Edit column "%s"' => 'カラム「%s」の編集',
- 'Enable time tracking for subtasks' => 'サブタスクのタイムトラッキングを有効',
'Select the new status of the subtask: "%s"' => 'サブタスク「%s」のステータスを選択',
'Subtask timesheet' => 'サブタスクタイムシート',
'There is nothing to show.' => '何も表示するものがありません。',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php
index 9e0cf6fe..7d59f5ec 100644
--- a/app/Locale/nl_NL/translations.php
+++ b/app/Locale/nl_NL/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Gebruiker dashboard',
'Allow only one subtask in progress at the same time for a user' => 'Sta maximaal één subtaak in behandeling toe per gebruiker',
'Edit column "%s"' => 'Kolom « %s » aanpassen',
- 'Enable time tracking for subtasks' => 'Activeer tijdschrijven voor subtaken',
'Select the new status of the subtask: "%s"' => 'Selecteer nieuwe status voor subtaak : « %s »',
'Subtask timesheet' => 'Subtaak timesheet',
'There is nothing to show.' => 'Er is niets om te laten zijn.',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index d2650a89..10d0c88a 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Panel użytkownika',
'Allow only one subtask in progress at the same time for a user' => 'Zezwalaj na tylko jedno pod-zadanie o statusie "w trakcie" jednocześnie',
'Edit column "%s"' => 'Zmień kolumnę "%s"',
- 'Enable time tracking for subtasks' => 'Włącz śledzenie czasu dla pod-zadań',
'Select the new status of the subtask: "%s"' => 'Wybierz nowy status dla pod-zadania: "%s"',
'Subtask timesheet' => 'Oś czasu pod-zadania',
'There is nothing to show.' => 'Nie nic do wyświetlenia',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index 041434a2..1fecc08c 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Painel de Controle do usuário',
'Allow only one subtask in progress at the same time for a user' => 'Permitir apenas uma subtarefa em andamento ao mesmo tempo para um usuário',
'Edit column "%s"' => 'Editar a coluna "%s"',
- 'Enable time tracking for subtasks' => 'Ativar a gestão de tempo par a subtarefa',
'Select the new status of the subtask: "%s"' => 'Selecionar um novo status para a subtarefa: "%s"',
'Subtask timesheet' => 'Gestão de tempo das subtarefas',
'There is nothing to show.' => 'Não há nada para mostrar',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index 7e94ecbb..0bfb33da 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Пользователь панели мониторинга',
'Allow only one subtask in progress at the same time for a user' => 'Разрешена только одна подзадача в разработке одновременно для одного пользователя',
'Edit column "%s"' => 'Редактировать колонку "%s"',
- 'Enable time tracking for subtasks' => 'Включить учет времени для подзадач',
'Select the new status of the subtask: "%s"' => 'Выбрать новый статус для подзадачи: "%s"',
'Subtask timesheet' => 'Табель времени подзадач',
'There is nothing to show.' => 'Здесь ничего нет.',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php
index 8ac8ea31..0eefa97c 100644
--- a/app/Locale/sr_Latn_RS/translations.php
+++ b/app/Locale/sr_Latn_RS/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Korisnički panel',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
- // 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
'There is nothing to show.' => 'Nema podataka',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index 770a68d4..df721590 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Användardashboard',
'Allow only one subtask in progress at the same time for a user' => 'Tillåt endast en deluppgift igång samtidigt för en användare',
'Edit column "%s"' => 'Ändra kolumn "%s"',
- 'Enable time tracking for subtasks' => 'Aktivera tidsbevakning för deluppgifter',
'Select the new status of the subtask: "%s"' => 'Välj ny status för deluppgiften: "%s"',
'Subtask timesheet' => 'Tidrapport för deluppgiften',
'There is nothing to show.' => 'Det finns inget att visa',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index b1fb0417..0eeaff11 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'ผู้ใช้แดชบอร์ด',
'Allow only one subtask in progress at the same time for a user' => 'อนุญาตให้ทำงานย่อยได้เพียงงานเดียวต่อหนึ่งคนในเวลาเดียวกัน',
'Edit column "%s"' => 'แก้ไขคอลัมน์ "%s"',
- 'Enable time tracking for subtasks' => 'สามารถติดตามเวลาของงานย่อย',
'Select the new status of the subtask: "%s"' => 'เลือกสถานะใหม่ของงานย่อย: "%s"',
'Subtask timesheet' => 'เวลางานย่อย',
'There is nothing to show.' => 'ไม่มีที่ต้องแสดง',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php
index cdd4d8cf..cb1249e7 100644
--- a/app/Locale/tr_TR/translations.php
+++ b/app/Locale/tr_TR/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => 'Kullanıcı Anasayfası',
'Allow only one subtask in progress at the same time for a user' => 'Bir kullanıcı için aynı anda yalnızca bir alt göreve izin ver',
'Edit column "%s"' => '"%s" sütununu değiştir',
- 'Enable time tracking for subtasks' => 'Alt görevler için zaman takibini etkinleştir',
'Select the new status of the subtask: "%s"' => '"%s" alt görevi için yeni durum seçin.',
'Subtask timesheet' => 'Alt görev için zaman takip tablosu',
'There is nothing to show.' => 'Gösterilecek hiçbir şey yok.',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index 60634686..e0d57494 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -614,7 +614,6 @@ return array(
'User dashboard' => '用户仪表板',
'Allow only one subtask in progress at the same time for a user' => '每用户同时仅有一个活动子任务',
'Edit column "%s"' => '编辑栏目"%s"',
- 'Enable time tracking for subtasks' => '启用子任务的时间记录',
'Select the new status of the subtask: "%s"' => '选择子任务的新状态:"%s"',
'Subtask timesheet' => '子任务时间',
'There is nothing to show.' => '无内容。',
@@ -946,4 +945,8 @@ return array(
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
+ // '<15m' => '',
+ // '<30m' => '',
+ // 'Stop timer' => '',
+ // 'Start timer' => '',
);
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index c9e155ed..684fae13 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -41,6 +41,7 @@ class Acl extends Base
'subtask' => '*',
'task' => '*',
'tasklink' => '*',
+ 'timer' => '*',
'calendar' => array('show', 'project'),
);
diff --git a/app/Model/Budget.php b/app/Model/Budget.php
index d74dd870..76c42ca9 100644
--- a/app/Model/Budget.php
+++ b/app/Model/Budget.php
@@ -75,7 +75,7 @@ class Budget extends Base
->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE)
->join(User::TABLE, 'id', 'user_id')
->eq(Task::TABLE.'.project_id', $project_id)
- ->filter(array($this, 'applyUserRate'));
+ ->callback(array($this, 'applyUserRate'));
}
/**
diff --git a/app/Model/Project.php b/app/Model/Project.php
index 71c660b9..ef7340ee 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -276,7 +276,7 @@ class Project extends Base
return $this->db
->table(Project::TABLE)
->in('id', $project_ids)
- ->filter(array($this, 'applyColumnStats'));
+ ->callback(array($this, 'applyColumnStats'));
}
/**
diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php
index ee000e32..b9cc6ded 100644
--- a/app/Model/Subtask.php
+++ b/app/Model/Subtask.php
@@ -78,6 +78,8 @@ class Subtask extends Base
foreach ($subtasks as &$subtask) {
$subtask['status_name'] = $status[$subtask['status']];
+ $subtask['timer_start_date'] = isset($subtask['timer_start_date']) ? $subtask['timer_start_date'] : 0;
+ $subtask['is_timer_started'] = ! empty($subtask['timer_start_date']);
}
return $subtasks;
@@ -101,12 +103,13 @@ class Subtask extends Base
Task::TABLE.'.title AS task_name',
Project::TABLE.'.name AS project_name'
)
+ ->subquery($this->subtaskTimeTracking->getTimerQuery($user_id), 'timer_start_date')
->eq('user_id', $user_id)
->eq(Project::TABLE.'.is_active', Project::ACTIVE)
->in(Subtask::TABLE.'.status', $status)
->join(Task::TABLE, 'id', 'task_id')
->join(Project::TABLE, 'id', 'project_id', Task::TABLE)
- ->filter(array($this, 'addStatusName'));
+ ->callback(array($this, 'addStatusName'));
}
/**
@@ -121,10 +124,15 @@ class Subtask extends Base
return $this->db
->table(self::TABLE)
->eq('task_id', $task_id)
- ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
+ ->columns(
+ self::TABLE.'.*',
+ User::TABLE.'.username',
+ User::TABLE.'.name'
+ )
+ ->subquery($this->subtaskTimeTracking->getTimerQuery($this->userSession->getId()), 'timer_start_date')
->join(User::TABLE, 'id', 'user_id')
->asc(self::TABLE.'.position')
- ->filter(array($this, 'addStatusName'))
+ ->callback(array($this, 'addStatusName'))
->findAll();
}
@@ -144,8 +152,9 @@ class Subtask extends Base
->table(self::TABLE)
->eq(self::TABLE.'.id', $subtask_id)
->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
+ ->subquery($this->subtaskTimeTracking->getTimerQuery($this->userSession->getId()), 'timer_start_date')
->join(User::TABLE, 'id', 'user_id')
- ->filter(array($this, 'addStatusName'))
+ ->callback(array($this, 'addStatusName'))
->findOne();
}
diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php
index 93a698b6..51743198 100644
--- a/app/Model/SubtaskTimeTracking.php
+++ b/app/Model/SubtaskTimeTracking.php
@@ -20,6 +20,27 @@ class SubtaskTimeTracking extends Base
const TABLE = 'subtask_time_tracking';
/**
+ * Get query to check if a timer is started for the given user and subtask
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return string
+ */
+ public function getTimerQuery($user_id)
+ {
+ return sprintf(
+ "SELECT %s FROM %s WHERE %s='%d' AND %s='0' AND %s=%s",
+ $this->db->escapeIdentifier('start'),
+ $this->db->escapeIdentifier(self::TABLE),
+ $this->db->escapeIdentifier('user_id'),
+ $user_id,
+ $this->db->escapeIdentifier('end'),
+ $this->db->escapeIdentifier('subtask_id'),
+ Subtask::TABLE.'.id'
+ );
+ }
+
+ /**
* Get query for user timesheet (pagination)
*
* @access public
@@ -217,7 +238,7 @@ class SubtaskTimeTracking extends Base
{
return $this->db
->table(self::TABLE)
- ->insert(array('subtask_id' => $subtask_id, 'user_id' => $user_id, 'start' => time()));
+ ->insert(array('subtask_id' => $subtask_id, 'user_id' => $user_id, 'start' => time(), 'end' => 0));
}
/**
diff --git a/app/Model/TaskFilter.php b/app/Model/TaskFilter.php
index 3d58c060..e99f36fb 100644
--- a/app/Model/TaskFilter.php
+++ b/app/Model/TaskFilter.php
@@ -301,7 +301,7 @@ class TaskFilter extends Base
*/
public function toAutoCompletion()
{
- return $this->query->columns('id', 'title')->filter(function(array $results) {
+ return $this->query->columns('id', 'title')->callback(function(array $results) {
foreach ($results as &$result) {
$result['value'] = $result['title'];
diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php
index 234101ec..ead60b4b 100644
--- a/app/Model/TaskFinder.php
+++ b/app/Model/TaskFinder.php
@@ -316,7 +316,7 @@ class TaskFinder extends Base
->table(Task::TABLE)
->eq('project_id', $project_id)
->eq('column_id', $column_id)
- ->in('is_active', 1)
+ ->eq('is_active', 1)
->count();
}
@@ -336,7 +336,7 @@ class TaskFinder extends Base
->eq('project_id', $project_id)
->eq('column_id', $column_id)
->eq('swimlane_id', $swimlane_id)
- ->in('is_active', 1)
+ ->eq('is_active', 1)
->count();
}
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 34a609cf..dd6af5a2 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,12 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 75;
+const VERSION = 76;
+
+function version_76($pdo)
+{
+ $pdo->exec("DELETE FROM `settings` WHERE `option`='subtask_time_tracking'");
+}
function version_75($pdo)
{
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index e32b0b9b..ac6ebf6f 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,12 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 55;
+const VERSION = 56;
+
+function version_56($pdo)
+{
+ $pdo->exec('DELETE FROM "settings" WHERE "option"=\'subtask_time_tracking\'');
+}
function version_55($pdo)
{
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index ebc3b064..feda8ec2 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,12 @@ use Core\Security;
use PDO;
use Model\Link;
-const VERSION = 72;
+const VERSION = 73;
+
+function version_73($pdo)
+{
+ $pdo->exec("DELETE FROM settings WHERE option='subtask_time_tracking'");
+}
function version_72($pdo)
{
diff --git a/app/ServiceProvider/EventDispatcherProvider.php b/app/ServiceProvider/EventDispatcherProvider.php
index f566ede8..aa71d61b 100644
--- a/app/ServiceProvider/EventDispatcherProvider.php
+++ b/app/ServiceProvider/EventDispatcherProvider.php
@@ -12,7 +12,7 @@ use Subscriber\ProjectActivitySubscriber;
use Subscriber\ProjectDailySummarySubscriber;
use Subscriber\ProjectModificationDateSubscriber;
use Subscriber\WebhookSubscriber;
-use Subscriber\SubtaskTimesheetSubscriber;
+use Subscriber\SubtaskTimeTrackingSubscriber;
use Subscriber\TaskMovedDateSubscriber;
use Subscriber\TransitionSubscriber;
use Subscriber\RecurringTaskSubscriber;
@@ -29,7 +29,7 @@ class EventDispatcherProvider implements ServiceProviderInterface
$container['dispatcher']->addSubscriber(new ProjectModificationDateSubscriber($container));
$container['dispatcher']->addSubscriber(new WebhookSubscriber($container));
$container['dispatcher']->addSubscriber(new NotificationSubscriber($container));
- $container['dispatcher']->addSubscriber(new SubtaskTimesheetSubscriber($container));
+ $container['dispatcher']->addSubscriber(new SubtaskTimeTrackingSubscriber($container));
$container['dispatcher']->addSubscriber(new TaskMovedDateSubscriber($container));
$container['dispatcher']->addSubscriber(new TransitionSubscriber($container));
$container['dispatcher']->addSubscriber(new RecurringTaskSubscriber($container));
diff --git a/app/Subscriber/SubtaskTimesheetSubscriber.php b/app/Subscriber/SubtaskTimeTrackingSubscriber.php
index fdaf442f..02f14c40 100644
--- a/app/Subscriber/SubtaskTimesheetSubscriber.php
+++ b/app/Subscriber/SubtaskTimeTrackingSubscriber.php
@@ -6,7 +6,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Model\Subtask;
use Event\SubtaskEvent;
-class SubtaskTimesheetSubscriber extends \Core\Base implements EventSubscriberInterface
+class SubtaskTimeTrackingSubscriber extends \Core\Base implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
@@ -28,7 +28,7 @@ class SubtaskTimesheetSubscriber extends \Core\Base implements EventSubscriberIn
public function logStartEnd(SubtaskEvent $event)
{
- if ($this->config->get('subtask_time_tracking') == 1 && isset($event['status'])) {
+ if (isset($event['status'])) {
$subtask = $this->subtask->getById($event['id']);
diff --git a/app/Template/board/task_private.php b/app/Template/board/task_private.php
index 088f47bc..77f3b958 100644
--- a/app/Template/board/task_private.php
+++ b/app/Template/board/task_private.php
@@ -32,8 +32,8 @@
</span>
<div class="task-board-days">
- <span title="<?= t('Task age in days')?>" class="task-days-age"><?= $this->task->age($task['date_creation']) ?></span>
- <span title="<?= t('Days in this column')?>" class="task-days-incolumn"><?= $this->task->age($task['date_moved']) ?></span>
+ <span title="<?= t('Task age in days')?>" class="task-days-age"><?= $this->datetime->age($task['date_creation']) ?></span>
+ <span title="<?= t('Days in this column')?>" class="task-days-incolumn"><?= $this->datetime->age($task['date_moved']) ?></span>
</div>
<div class="task-board-title">
diff --git a/app/Template/config/project.php b/app/Template/config/project.php
index 90dd9c8e..1ab69e26 100644
--- a/app/Template/config/project.php
+++ b/app/Template/config/project.php
@@ -15,7 +15,6 @@
<p class="form-help"><?= t('Example: "Bug, Feature Request, Improvement"') ?></p>
<?= $this->form->checkbox('subtask_restriction', t('Allow only one subtask in progress at the same time for a user'), 1, $values['subtask_restriction'] == 1) ?>
- <?= $this->form->checkbox('subtask_time_tracking', t('Enable time tracking for subtasks'), 1, $values['subtask_time_tracking'] == 1) ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
diff --git a/app/Template/subtask/show.php b/app/Template/subtask/show.php
index c9690f08..b91e830f 100644
--- a/app/Template/subtask/show.php
+++ b/app/Template/subtask/show.php
@@ -33,13 +33,29 @@
<?php endif ?>
</td>
<td>
- <?php if (! empty($subtask['time_spent'])): ?>
- <strong><?= $this->e($subtask['time_spent']).'h' ?></strong> <?= t('spent') ?>
- <?php endif ?>
+ <ul class="no-bullet">
+ <li>
+ <?php if (! empty($subtask['time_spent'])): ?>
+ <strong><?= $this->e($subtask['time_spent']).'h' ?></strong> <?= t('spent') ?>
+ <?php endif ?>
- <?php if (! empty($subtask['time_estimated'])): ?>
- <strong><?= $this->e($subtask['time_estimated']).'h' ?></strong> <?= t('estimated') ?>
- <?php endif ?>
+ <?php if (! empty($subtask['time_estimated'])): ?>
+ <strong><?= $this->e($subtask['time_estimated']).'h' ?></strong> <?= t('estimated') ?>
+ <?php endif ?>
+ </li>
+ <?php if (! isset($not_editable) && $subtask['user_id'] == $this->user->getId()): ?>
+ <li>
+ <?php if ($subtask['is_timer_started']): ?>
+ <i class="fa fa-pause"></i>
+ <?= $this->url->link(t('Stop timer'), 'timer', 'subtask', array('timer' => 'stop', 'project_id' => $task['project_id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'])) ?>
+ (<?= $this->datetime->age($subtask['timer_start_date']) ?>)
+ <?php else: ?>
+ <i class="fa fa-play-circle-o"></i>
+ <?= $this->url->link(t('Start timer'), 'timer', 'subtask', array('timer' => 'start', 'project_id' => $task['project_id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'])) ?>
+ <?php endif ?>
+ </li>
+ <?php endif ?>
+ </ul>
</td>
<?php if (! isset($not_editable)): ?>
<td>
diff --git a/composer.json b/composer.json
index 39585052..d04c8861 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,7 @@
"erusev/parsedown" : "1.5.1",
"fabiang/xmpp" : "0.6.1",
"fguillot/json-rpc" : "0.0.3",
- "fguillot/picodb" : "0.0.3",
+ "fguillot/picodb" : "dev-master",
"fguillot/simpleLogger" : "0.0.2",
"fguillot/simple-validator" : "0.0.3",
"lusitanian/oauth" : "0.3.5",
diff --git a/composer.lock b/composer.lock
index 35709f5b..a665b9ab 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "4aed9321378ab6e96c0fd0170b379c38",
+ "hash": "c5913f9f57295aae111f3a29b385dafb",
"packages": [
{
"name": "christian-riesen/base32",
@@ -297,16 +297,16 @@
},
{
"name": "fguillot/picodb",
- "version": "v0.0.3",
+ "version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
- "reference": "f65d11cb52de34e0fd236a34184ca1a310da244a"
+ "reference": "dd08649713c9d8330b3c5fa23220a11cb5da3e79"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fguillot/picoDb/zipball/f65d11cb52de34e0fd236a34184ca1a310da244a",
- "reference": "f65d11cb52de34e0fd236a34184ca1a310da244a",
+ "url": "https://api.github.com/repos/fguillot/picoDb/zipball/dd08649713c9d8330b3c5fa23220a11cb5da3e79",
+ "reference": "dd08649713c9d8330b3c5fa23220a11cb5da3e79",
"shasum": ""
},
"require": {
@@ -330,7 +330,7 @@
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb",
- "time": "2015-05-17 23:57:05"
+ "time": "2015-06-24 21:58:15"
},
{
"name": "fguillot/simple-validator",
@@ -820,6 +820,7 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
+ "fguillot/picodb": 20,
"swiftmailer/swiftmailer": 0,
"symfony/console": 0
},
diff --git a/tests/units/DatetimeHelperTest.php b/tests/units/DatetimeHelperTest.php
index 2746beed..216cf34c 100644
--- a/tests/units/DatetimeHelperTest.php
+++ b/tests/units/DatetimeHelperTest.php
@@ -6,6 +6,18 @@ use Helper\Datetime;
class DatetimeHelperTest extends Base
{
+ public function testAge()
+ {
+ $h = new Datetime($this->container);
+
+ $this->assertEquals('&lt;15m', $h->age(0, 30));
+ $this->assertEquals('&lt;30m', $h->age(0, 1000));
+ $this->assertEquals('&lt;1h', $h->age(0, 3000));
+ $this->assertEquals('~2h', $h->age(0, 2*3600));
+ $this->assertEquals('1d', $h->age(0, 30*3600));
+ $this->assertEquals('2d', $h->age(0, 65*3600));
+ }
+
public function testGetDayHours()
{
$h = new Datetime($this->container);
diff --git a/tests/units/SubtaskTimeTrackingTest.php b/tests/units/SubtaskTimeTrackingTest.php
index e15e60da..94359159 100644
--- a/tests/units/SubtaskTimeTrackingTest.php
+++ b/tests/units/SubtaskTimeTrackingTest.php
@@ -9,9 +9,61 @@ use Model\SubtaskTimeTracking;
use Model\Project;
use Model\Category;
use Model\User;
+use Core\Session;
class SubtaskTimeTrackingTest extends Base
{
+ public function testGetTimerStatus()
+ {
+ $tc = new TaskCreation($this->container);
+ $s = new Subtask($this->container);
+ $st = new SubtaskTimeTracking($this->container);
+ $p = new Project($this->container);
+ $ss = new Session;
+
+ $ss['user'] = array('id' => 1);
+
+ $this->assertEquals(1, $p->create(array('name' => 'test1')));
+ $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1)));
+ $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1, 'user_id' => 1)));
+
+ // Nothing started
+ $subtasks = $s->getAll(1);
+ $this->assertNotEmpty($subtasks);
+ $this->assertEquals(0, $subtasks[0]['timer_start_date']);
+ $this->assertFalse($subtasks[0]['is_timer_started']);
+
+ $subtask = $s->getbyId(1, true);
+ $this->assertNotEmpty($subtask);
+ $this->assertEquals(0, $subtask['timer_start_date']);
+ $this->assertFalse($subtask['is_timer_started']);
+
+ // Start the clock
+ $this->assertTrue($st->logStartTime(1, 1));
+
+ $subtasks = $s->getAll(1);
+ $this->assertNotEmpty($subtasks);
+ $this->assertEquals(time(), $subtasks[0]['timer_start_date'], '', 3);
+ $this->assertTrue($subtasks[0]['is_timer_started']);
+
+ $subtask = $s->getbyId(1, true);
+ $this->assertNotEmpty($subtask);
+ $this->assertEquals(time(), $subtask['timer_start_date'], '', 3);
+ $this->assertTrue($subtask['is_timer_started']);
+
+ // Stop the clock
+ $this->assertTrue($st->logEndTime(1, 1));
+ $subtasks = $s->getAll(1);
+ $this->assertNotEmpty($subtasks);
+ $this->assertEquals(0, $subtasks[0]['timer_start_date']);
+ $this->assertFalse($subtasks[0]['is_timer_started']);
+
+ $subtask = $s->getbyId(1, true);
+ $this->assertNotEmpty($subtask);
+ $this->assertEquals(0, $subtask['timer_start_date']);
+ $this->assertFalse($subtask['is_timer_started']);
+ }
+
public function testLogStartTime()
{
$tc = new TaskCreation($this->container);