Store comment sorting direction in user metadata

This commit is contained in:
Frederic Guillot 2016-08-21 20:36:16 -04:00
parent 8e83e404fb
commit 1d16a53c48
No known key found for this signature in database
GPG Key ID: 92D77191BA7FBC99
13 changed files with 304 additions and 73 deletions

View File

@ -7,6 +7,7 @@ New features:
Improvements:
* Store comment sorting direction in the database
* Avoid tags overlapping on the board
* Show project name in notifications
* Allow priority changes for inverted priority scales

View File

@ -2,6 +2,8 @@
namespace Kanboard\Controller;
use Kanboard\Model\UserMetadataModel;
/**
* Board Tooltip
*
@ -75,10 +77,11 @@ class BoardTooltipController extends BaseController
public function comments()
{
$task = $this->getTask();
$commentSortingDirection = $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, 'ASC');
$this->response->html($this->template->render('board/tooltip_comments', array(
'task' => $task,
'comments' => $this->commentModel->getAll($task['id'], $this->userSession->getCommentSorting())
'comments' => $this->commentModel->getAll($task['id'], $commentSortingDirection)
)));
}

View File

@ -4,6 +4,7 @@ namespace Kanboard\Controller;
use Kanboard\Core\Controller\AccessForbiddenException;
use Kanboard\Core\Controller\PageNotFoundException;
use Kanboard\Model\UserMetadataModel;
/**
* Comment Controller
@ -82,10 +83,10 @@ class CommentController extends BaseController
$this->flash->failure(t('Unable to create your comment.'));
}
return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), 'comments'), true);
$this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), 'comments'), true);
} else {
$this->create($values, $errors);
}
return $this->create($values, $errors);
}
/**
@ -183,9 +184,16 @@ class CommentController extends BaseController
{
$task = $this->getTask();
$order = $this->userSession->getCommentSorting() === 'ASC' ? 'DESC' : 'ASC';
$this->userSession->setCommentSorting($order);
$oldDirection = $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, 'ASC');
$newDirection = $oldDirection === 'ASC' ? 'DESC' : 'ASC';
$this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), 'comments'));
$this->userMetadataCacheDecorator->set(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, $newDirection);
$this->response->redirect($this->helper->url->to(
'TaskViewController',
'show',
array('task_id' => $task['id'], 'project_id' => $task['project_id']),
'comments'
));
}
}

View File

@ -4,6 +4,7 @@ namespace Kanboard\Controller;
use Kanboard\Core\Controller\AccessForbiddenException;
use Kanboard\Core\Controller\PageNotFoundException;
use Kanboard\Model\UserMetadataModel;
/**
* Task Controller
@ -61,13 +62,14 @@ class TaskViewController extends BaseController
{
$task = $this->getTask();
$subtasks = $this->subtaskModel->getAll($task['id']);
$commentSortingDirection = $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, 'ASC');
$this->response->html($this->helper->layout->task('task/show', array(
'task' => $task,
'project' => $this->projectModel->getById($task['project_id']),
'files' => $this->taskFileModel->getAllDocuments($task['id']),
'images' => $this->taskFileModel->getAllImages($task['id']),
'comments' => $this->commentModel->getAll($task['id'], $this->userSession->getCommentSorting()),
'comments' => $this->commentModel->getAll($task['id'], $commentSortingDirection),
'subtasks' => $subtasks,
'internal_links' => $this->taskLinkModel->getAllGroupedByLabel($task['id']),
'external_links' => $this->taskExternalLinkModel->getAll($task['id']),

View File

@ -56,6 +56,7 @@ use Pimple\Container;
* @property \Kanboard\Core\Helper $helper
* @property \Kanboard\Core\Paginator $paginator
* @property \Kanboard\Core\Template $template
* @property \Kanboard\Decorator\MetadataCacheDecorator $userMetadataCacheDecorator
* @property \Kanboard\Model\ActionModel $actionModel
* @property \Kanboard\Model\ActionParameterModel $actionParameterModel
* @property \Kanboard\Model\AvatarFileModel $avatarFileModel

View File

@ -8,41 +8,8 @@ namespace Kanboard\Core\Cache;
* @package Kanboard\Core\Cache
* @author Frederic Guillot
*/
abstract class BaseCache
abstract class BaseCache implements CacheInterface
{
/**
* Store an item in the cache
*
* @access public
* @param string $key
* @param string $value
*/
abstract public function set($key, $value);
/**
* Retrieve an item from the cache by key
*
* @access public
* @param string $key
* @return mixed Null when not found, cached value otherwise
*/
abstract public function get($key);
/**
* Remove all items from the cache
*
* @access public
*/
abstract public function flush();
/**
* Remove an item from the cache
*
* @access public
* @param string $key
*/
abstract public function remove($key);
/**
* Proxy cache
*

View File

@ -0,0 +1,45 @@
<?php
namespace Kanboard\Core\Cache;
/**
* Interface CacheInterface
*
* @package Kanboard\Core\Cache
* @author Frederic Guillot
*/
interface CacheInterface
{
/**
* Store an item in the cache
*
* @access public
* @param string $key
* @param string $value
*/
public function set($key, $value);
/**
* Retrieve an item from the cache by key
*
* @access public
* @param string $key
* @return mixed Null when not found, cached value otherwise
*/
public function get($key);
/**
* Remove all items from the cache
*
* @access public
*/
public function flush();
/**
* Remove an item from the cache
*
* @access public
* @param string $key
*/
public function remove($key);
}

View File

@ -203,26 +203,4 @@ class UserSession extends Base
{
$this->sessionStorage->boardCollapsed[$project_id] = $is_collapsed;
}
/**
* Set comments sorting
*
* @access public
* @param string $order
*/
public function setCommentSorting($order)
{
$this->sessionStorage->commentSorting = $order;
}
/**
* Get comments sorting direction
*
* @access public
* @return string
*/
public function getCommentSorting()
{
return empty($this->sessionStorage->commentSorting) ? 'ASC' : $this->sessionStorage->commentSorting;
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace Kanboard\Decorator;
use Kanboard\Core\Cache\CacheInterface;
use Kanboard\Model\MetadataModel;
/**
* Class MetadataCacheDecorator
*
* @package Kanboard\Decorator
* @author Frederic Guillot
*/
class MetadataCacheDecorator
{
/**
* @var CacheInterface
*/
protected $cache;
/**
* @var MetadataModel
*/
protected $metadataModel;
/**
* @var string
*/
protected $cachePrefix;
/**
* @var int
*/
protected $entityId;
/**
* Constructor
*
* @param CacheInterface $cache
* @param MetadataModel $metadataModel
* @param string $cachePrefix
* @param integer $entityId
*/
public function __construct(CacheInterface $cache, MetadataModel $metadataModel, $cachePrefix, $entityId)
{
$this->cache = $cache;
$this->metadataModel = $metadataModel;
$this->cachePrefix = $cachePrefix;
$this->entityId = $entityId;
}
/**
* Get metadata value by key
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default)
{
$metadata = $this->cache->get($this->getCacheKey());
if ($metadata === null) {
$metadata = $this->metadataModel->getAll($this->entityId);
$this->cache->set($this->getCacheKey(), $metadata);
}
return isset($metadata[$key]) ? $metadata[$key] : $default;
}
/**
* Set new metadata value
*
* @param $key
* @param $value
*/
public function set($key, $value)
{
$this->metadataModel->save($this->entityId, array(
$key => $value,
));
$metadata = $this->metadataModel->getAll($this->entityId);
$this->cache->set($this->getCacheKey(), $metadata);
}
/**
* Get cache key
*
* @return string
*/
protected function getCacheKey()
{
return $this->cachePrefix.$this->entityId;
}
}

View File

@ -10,6 +10,8 @@ namespace Kanboard\Model;
*/
class UserMetadataModel extends MetadataModel
{
const KEY_COMMENT_SORTING_DIRECTION = 'comment.sorting.direction';
/**
* Get the table
*

View File

@ -4,6 +4,7 @@ namespace Kanboard\ServiceProvider;
use Kanboard\Core\Cache\FileCache;
use Kanboard\Core\Cache\MemoryCache;
use Kanboard\Decorator\MetadataCacheDecorator;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
@ -36,6 +37,15 @@ class CacheProvider implements ServiceProviderInterface
$container['cacheDriver'] = $container['memoryCache'];
}
$container['userMetadataCacheDecorator'] = function($c) {
return new MetadataCacheDecorator(
$c['cacheDriver'],
$c['userMetadataModel'],
'user.metadata.',
$c['userSession']->getId()
);
};
return $container;
}
}

View File

@ -83,15 +83,6 @@ class UserSessionTest extends Base
$this->assertFalse($us->isAdmin());
}
public function testCommentSorting()
{
$us = new UserSession($this->container);
$this->assertEquals('ASC', $us->getCommentSorting());
$us->setCommentSorting('DESC');
$this->assertEquals('DESC', $us->getCommentSorting());
}
public function testBoardCollapseMode()
{
$us = new UserSession($this->container);

View File

@ -0,0 +1,127 @@
<?php
use Kanboard\Decorator\MetadataCacheDecorator;
require_once __DIR__.'/../Base.php';
class MetadataCacheDecoratorTest extends Base
{
protected $cachePrefix = 'cache_prefix';
protected $entityId = 123;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $cacheMock;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $metadataModelMock;
/**
* @var MetadataCacheDecorator
*/
protected $metadataCacheDecorator;
public function setUp()
{
parent::setUp();
$this->cacheMock = $this
->getMockBuilder('\Kanboard\Core\Cache\MemoryCache')
->setMethods(array(
'set',
'get',
))
->getMock();
$this->metadataModelMock = $this
->getMockBuilder('\Kanboard\Model\UserMetadataModel')
->setConstructorArgs(array($this->container))
->setMethods(array(
'getAll',
'save',
))
->getMock()
;
$this->metadataCacheDecorator = new MetadataCacheDecorator(
$this->cacheMock,
$this->metadataModelMock,
$this->cachePrefix,
$this->entityId
);
}
public function testSet()
{
$this->cacheMock
->expects($this->once())
->method('set');
$this->metadataModelMock
->expects($this->at(0))
->method('save');
$this->metadataModelMock
->expects($this->at(1))
->method('getAll')
->with($this->entityId)
;
$this->metadataCacheDecorator->set('key', 'value');
}
public function testGetWithCache()
{
$this->cacheMock
->expects($this->once())
->method('get')
->with($this->cachePrefix.$this->entityId)
->will($this->returnValue(array('key' => 'foobar')))
;
$this->assertEquals('foobar', $this->metadataCacheDecorator->get('key', 'default'));
}
public function testGetWithCacheAndDefaultValue()
{
$this->cacheMock
->expects($this->once())
->method('get')
->with($this->cachePrefix.$this->entityId)
->will($this->returnValue(array('key1' => 'foobar')))
;
$this->assertEquals('default', $this->metadataCacheDecorator->get('key', 'default'));
}
public function testGetWithoutCache()
{
$this->cacheMock
->expects($this->at(0))
->method('get')
->with($this->cachePrefix.$this->entityId)
->will($this->returnValue(null))
;
$this->cacheMock
->expects($this->at(1))
->method('set')
->with(
$this->cachePrefix.$this->entityId,
array('key' => 'something')
)
;
$this->metadataModelMock
->expects($this->once())
->method('getAll')
->with($this->entityId)
->will($this->returnValue(array('key' => 'something')))
;
$this->assertEquals('something', $this->metadataCacheDecorator->get('key', 'default'));
}
}