diff options
-rw-r--r-- | app/Controller/Subtask.php | 20 | ||||
-rw-r--r-- | app/Model/Subtask.php | 82 | ||||
-rw-r--r-- | app/Schema/Mysql.php | 24 | ||||
-rw-r--r-- | app/Schema/Postgres.php | 24 | ||||
-rw-r--r-- | app/Schema/Sqlite.php | 26 | ||||
-rw-r--r-- | app/Template/subtask/show.php | 12 | ||||
-rw-r--r-- | app/Template/task/show.php | 2 | ||||
-rw-r--r-- | tests/units/SubtaskTest.php | 75 |
8 files changed, 256 insertions, 9 deletions
diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php index c7ec00d1..385785a1 100644 --- a/app/Controller/Subtask.php +++ b/app/Controller/Subtask.php @@ -185,7 +185,7 @@ class Subtask extends Base if ($redirect === 'board') { $this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId()); - + $this->response->html($this->template->render('board/subtasks', array( 'subtasks' => $this->subtask->getAll($task['id']), 'task' => $task, @@ -259,4 +259,22 @@ class Subtask extends Base $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); } } + + /** + * Move subtask position + * + * @access public + */ + public function movePosition() + { + $this->checkCSRFParam(); + $project_id = $this->request->getIntegerParam('project_id'); + $task_id = $this->request->getIntegerParam('task_id'); + $subtask_id = $this->request->getIntegerParam('subtask_id'); + $direction = $this->request->getStringParam('direction'); + $method = $direction === 'up' ? 'moveUp' : 'moveDown'; + + $this->subtask->$method($task_id, $subtask_id); + $this->response->redirect($this->helper->url('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id)).'#subtasks'); + } } diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php index aa4a6f81..2d108eab 100644 --- a/app/Model/Subtask.php +++ b/app/Model/Subtask.php @@ -122,7 +122,7 @@ class Subtask extends Base ->eq('task_id', $task_id) ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name') ->join(User::TABLE, 'id', 'user_id') - ->asc(self::TABLE.'.id') + ->asc(self::TABLE.'.position') ->filter(array($this, 'addStatusName')) ->findAll(); } @@ -164,6 +164,22 @@ class Subtask extends Base } /** + * Get the position of the last column for a given project + * + * @access public + * @param integer $task_id Task id + * @return integer + */ + public function getLastPosition($task_id) + { + return (int) $this->db + ->table(self::TABLE) + ->eq('task_id', $task_id) + ->desc('position') + ->findOneColumn('position'); + } + + /** * Create a new subtask * * @access public @@ -173,6 +189,8 @@ class Subtask extends Base public function create(array $values) { $this->prepare($values); + $values['position'] = $this->getLastPosition($values['task_id']) + 1; + $subtask_id = $this->persist(self::TABLE, $values); if ($subtask_id) { @@ -209,6 +227,64 @@ class Subtask extends Base } /** + * Move a subtask down, increment the position value + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @return boolean + */ + public function moveDown($task_id, $subtask_id) + { + $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position'); + $positions = array_flip($subtasks); + + if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) { + + $position = ++$subtasks[$subtask_id]; + $subtasks[$positions[$position]]--; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $subtasks[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** + * Move a subtask up, decrement the position value + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @return boolean + */ + public function moveUp($task_id, $subtask_id) + { + $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position'); + $positions = array_flip($subtasks); + + if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] > 1) { + + $position = --$subtasks[$subtask_id]; + $subtasks[$positions[$position]]++; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $subtasks[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** * Change the status of subtask * * Todo -> In progress -> Done -> Todo -> etc... @@ -286,9 +362,9 @@ class Subtask extends Base return $this->db->transaction(function ($db) use ($src_task_id, $dst_task_id) { $subtasks = $db->table(Subtask::TABLE) - ->columns('title', 'time_estimated') + ->columns('title', 'time_estimated', 'position') ->eq('task_id', $src_task_id) - ->asc('id') // Explicit sorting for postgresql + ->asc('position') ->findAll(); foreach ($subtasks as &$subtask) { diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 1c5cdc3a..21c304c1 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,29 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 48; +const VERSION = 49; + +function version_49($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} function version_48($pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index aa3f60f7..df003267 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,29 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 29; +const VERSION = 30; + +function version_30($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} function version_29($pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 6fc2d07e..9134760f 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,31 @@ use Core\Security; use PDO; use Model\Link; -const VERSION = 47; +const VERSION = 48; + +function version_48($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + // Migrate all subtasks position + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} function version_47($pdo) { diff --git a/app/Template/subtask/show.php b/app/Template/subtask/show.php index 1d55d1ee..c7ac652a 100644 --- a/app/Template/subtask/show.php +++ b/app/Template/subtask/show.php @@ -20,7 +20,7 @@ <?php if (! isset($not_editable)): ?> <?= $this->toggleSubtaskStatus($subtask, 'task') ?> <?php else: ?> - <?= $this->render('subtask/icons', array('subtask' => $subtask)) . $this->e($subtask['status_name']) ?> + <?= $this->render('subtask/icons', array('subtask' => $subtask)) . $this->e($subtask['title']) ?> <?php endif ?> </td> <td> @@ -40,6 +40,16 @@ <?php if (! isset($not_editable)): ?> <td> <ul> + <?php if ($subtask['position'] > 1): ?> + <li> + <?= $this->a(t('Move Up'), 'subtask', 'movePosition', array('project_id' => $project['id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'direction' => 'up'), true) ?> + </li> + <?php endif ?> + <?php if ($subtask['position'] != 0 && $subtask['position'] != count($subtasks)): ?> + <li> + <?= $this->a(t('Move Down'), 'subtask', 'movePosition', array('project_id' => $project['id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'direction' => 'down'), true) ?> + </li> + <?php endif ?> <li> <?= $this->a(t('Edit'), 'subtask', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'subtask_id' => $subtask['id'])) ?> </li> diff --git a/app/Template/task/show.php b/app/Template/task/show.php index 1ff2ef43..d51b5542 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -2,7 +2,7 @@ <?= $this->render('task/time', array('task' => $task, 'values' => $values, 'date_format' => $date_format, 'date_formats' => $date_formats)) ?> <?= $this->render('task/show_description', array('task' => $task)) ?> <?= $this->render('tasklink/show', array('task' => $task, 'links' => $links)) ?> -<?= $this->render('subtask/show', array('task' => $task, 'subtasks' => $subtasks)) ?> +<?= $this->render('subtask/show', array('task' => $task, 'subtasks' => $subtasks, 'project' => $project)) ?> <?= $this->render('task/timesheet', array('task' => $task)) ?> <?= $this->render('file/show', array('task' => $task, 'files' => $files)) ?> <?= $this->render('task/comments', array('task' => $task, 'comments' => $comments, 'project' => $project)) ?> diff --git a/tests/units/SubtaskTest.php b/tests/units/SubtaskTest.php index 62475186..eb1a3fd3 100644 --- a/tests/units/SubtaskTest.php +++ b/tests/units/SubtaskTest.php @@ -11,6 +11,78 @@ use Model\User; class SubTaskTest extends Base { + public function testMoveUp() + { + $tc = new TaskCreation($this->container); + $s = new Subtask($this->container); + $p = new Project($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + + $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1))); + $this->assertEquals(3, $s->create(array('title' => 'subtask #3', 'task_id' => 1))); + + $subtask = $s->getById(1); + $this->assertNotEmpty($subtask); + $this->assertEquals(1, $subtask['position']); + + $subtask = $s->getById(2); + $this->assertNotEmpty($subtask); + $this->assertEquals(2, $subtask['position']); + + $subtask = $s->getById(3); + $this->assertNotEmpty($subtask); + $this->assertEquals(3, $subtask['position']); + + $this->assertTrue($s->moveUp(1, 2)); + + $subtask = $s->getById(1); + $this->assertNotEmpty($subtask); + $this->assertEquals(2, $subtask['position']); + + $subtask = $s->getById(2); + $this->assertNotEmpty($subtask); + $this->assertEquals(1, $subtask['position']); + + $subtask = $s->getById(3); + $this->assertNotEmpty($subtask); + $this->assertEquals(3, $subtask['position']); + + $this->assertFalse($s->moveUp(1, 2)); + } + + public function testMoveDown() + { + $tc = new TaskCreation($this->container); + $s = new Subtask($this->container); + $p = new Project($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + + $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1))); + $this->assertEquals(3, $s->create(array('title' => 'subtask #3', 'task_id' => 1))); + + $this->assertTrue($s->moveDown(1, 1)); + + $subtask = $s->getById(1); + $this->assertNotEmpty($subtask); + $this->assertEquals(2, $subtask['position']); + + $subtask = $s->getById(2); + $this->assertNotEmpty($subtask); + $this->assertEquals(1, $subtask['position']); + + $subtask = $s->getById(3); + $this->assertNotEmpty($subtask); + $this->assertEquals(3, $subtask['position']); + + $this->assertFalse($s->moveDown(1, 3)); + } + public function testDuplicate() { $tc = new TaskCreation($this->container); @@ -53,5 +125,8 @@ class SubTaskTest extends Base $this->assertEquals(0, $subtasks[0]['user_id']); $this->assertEquals(0, $subtasks[1]['user_id']); + + $this->assertEquals(1, $subtasks[0]['position']); + $this->assertEquals(2, $subtasks[1]['position']); } } |