From 9f0166502b8b8886156bcb4ad0497cd9ee5a60b2 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 10 Apr 2016 15:18:20 -0400 Subject: Added search in activity stream --- ChangeLog | 1 + app/Controller/Search.php | 18 ++++ app/Filter/BaseDateFilter.php | 103 ++++++++++++++++++ app/Filter/BaseFilter.php | 44 -------- app/Filter/ProjectActivityCreationDateFilter.php | 38 +++++++ app/Filter/ProjectActivityCreatorFilter.php | 65 ++++++++++++ app/Filter/ProjectActivityProjectIdsFilter.php | 2 +- app/Filter/ProjectActivityProjectNameFilter.php | 38 +++++++ app/Filter/ProjectActivityTaskStatusFilter.php | 43 ++++++++ app/Filter/ProjectActivityTaskTitleFilter.php | 15 +-- app/Filter/TaskCompletionDateFilter.php | 2 +- app/Filter/TaskCreationDateFilter.php | 2 +- app/Filter/TaskDueDateFilter.php | 2 +- app/Filter/TaskModificationDateFilter.php | 2 +- app/Filter/TaskProjectsFilter.php | 7 +- app/Filter/TaskStartDateFilter.php | 2 +- app/Helper/ProjectActivityHelper.php | 27 +++++ app/Locale/bs_BA/translations.php | 10 ++ app/Locale/cs_CZ/translations.php | 10 ++ app/Locale/da_DK/translations.php | 10 ++ app/Locale/de_DE/translations.php | 10 ++ app/Locale/el_GR/translations.php | 10 ++ app/Locale/es_ES/translations.php | 10 ++ app/Locale/fi_FI/translations.php | 10 ++ app/Locale/fr_FR/translations.php | 10 ++ app/Locale/hu_HU/translations.php | 10 ++ app/Locale/id_ID/translations.php | 10 ++ app/Locale/it_IT/translations.php | 10 ++ app/Locale/ja_JP/translations.php | 10 ++ app/Locale/ko_KR/translations.php | 10 ++ app/Locale/my_MY/translations.php | 10 ++ app/Locale/nb_NO/translations.php | 10 ++ app/Locale/nl_NL/translations.php | 10 ++ app/Locale/pl_PL/translations.php | 10 ++ app/Locale/pt_BR/translations.php | 10 ++ app/Locale/pt_PT/translations.php | 10 ++ app/Locale/ru_RU/translations.php | 12 ++- app/Locale/sr_Latn_RS/translations.php | 10 ++ app/Locale/sv_SE/translations.php | 10 ++ app/Locale/th_TH/translations.php | 10 ++ app/Locale/tr_TR/translations.php | 10 ++ app/Locale/zh_CN/translations.php | 10 ++ app/Model/ProjectActivity.php | 1 + app/ServiceProvider/FilterProvider.php | 30 +++++- app/ServiceProvider/RouteProvider.php | 3 +- app/Template/activity/filter_dropdown.php | 14 +++ app/Template/search/activity.php | 39 +++++++ app/Template/search/index.php | 4 +- doc/search.markdown | 83 +++++++++------ .../ProjectActivityCreationDateFilterTest.php | 117 +++++++++++++++++++++ .../Filter/ProjectActivityCreatorFilterTest.php | 91 ++++++++++++++++ .../ProjectActivityProjectNameFilterTest.php | 35 ++++++ .../Filter/ProjectActivityTaskStatusFilterTest.php | 49 +++++++++ .../Filter/ProjectActivityTaskTitleFilterTest.php | 47 ++++++++- 54 files changed, 1066 insertions(+), 110 deletions(-) create mode 100644 app/Filter/BaseDateFilter.php create mode 100644 app/Filter/ProjectActivityCreationDateFilter.php create mode 100644 app/Filter/ProjectActivityCreatorFilter.php create mode 100644 app/Filter/ProjectActivityProjectNameFilter.php create mode 100644 app/Filter/ProjectActivityTaskStatusFilter.php create mode 100644 app/Template/activity/filter_dropdown.php create mode 100644 app/Template/search/activity.php create mode 100644 tests/units/Filter/ProjectActivityCreationDateFilterTest.php create mode 100644 tests/units/Filter/ProjectActivityCreatorFilterTest.php create mode 100644 tests/units/Filter/ProjectActivityProjectNameFilterTest.php create mode 100644 tests/units/Filter/ProjectActivityTaskStatusFilterTest.php diff --git a/ChangeLog b/ChangeLog index 1bbe8062..f4952b53 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,7 @@ Version 1.0.28 (unreleased) New features: +* Search in activity stream * Search in comments * Search by task creator diff --git a/app/Controller/Search.php b/app/Controller/Search.php index 840a90c8..a42e9d3d 100644 --- a/app/Controller/Search.php +++ b/app/Controller/Search.php @@ -46,4 +46,22 @@ class Search extends Base 'title' => t('Search tasks').($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '') ))); } + + public function activity() + { + $search = urldecode($this->request->getStringParam('search')); + $events = $this->helper->projectActivity->searchEvents($search); + $nb_events = count($events); + + $this->response->html($this->helper->layout->app('search/activity', array( + 'values' => array( + 'search' => $search, + 'controller' => 'search', + 'action' => 'activity', + ), + 'title' => t('Search in activity stream').($nb_events > 0 ? ' ('.$nb_events.')' : ''), + 'nb_events' => $nb_events, + 'events' => $events, + ))); + } } diff --git a/app/Filter/BaseDateFilter.php b/app/Filter/BaseDateFilter.php new file mode 100644 index 00000000..56fb2d78 --- /dev/null +++ b/app/Filter/BaseDateFilter.php @@ -0,0 +1,103 @@ +dateParser = $dateParser; + return $this; + } + + /** + * Parse operator in the input string + * + * @access protected + * @return string + */ + protected function parseOperator() + { + $operators = array( + '<=' => 'lte', + '>=' => 'gte', + '<' => 'lt', + '>' => 'gt', + ); + + foreach ($operators as $operator => $method) { + if (strpos($this->value, $operator) === 0) { + $this->value = substr($this->value, strlen($operator)); + return $method; + } + } + + return ''; + } + + /** + * Apply a date filter + * + * @access protected + * @param string $field + */ + protected function applyDateFilter($field) + { + $method = $this->parseOperator(); + $timestamp = $this->dateParser->getTimestampFromIsoFormat($this->value); + + if ($method !== '') { + $this->query->$method($field, $this->getTimestampFromOperator($method, $timestamp)); + } else { + $this->query->gte($field, $timestamp); + $this->query->lte($field, $timestamp + 86399); + } + } + + /** + * Get timestamp from the operator + * + * @access public + * @param string $method + * @param integer $timestamp + * @return integer + */ + protected function getTimestampFromOperator($method, $timestamp) + { + switch ($method) { + case 'lte': + return $timestamp + 86399; + case 'lt': + return $timestamp; + case 'gte': + return $timestamp; + case 'gt': + return $timestamp + 86400; + } + + return $timestamp; + } +} diff --git a/app/Filter/BaseFilter.php b/app/Filter/BaseFilter.php index a7e6a61a..79a664be 100644 --- a/app/Filter/BaseFilter.php +++ b/app/Filter/BaseFilter.php @@ -72,48 +72,4 @@ abstract class BaseFilter $this->value = $value; return $this; } - - /** - * Parse operator in the input string - * - * @access protected - * @return string - */ - protected function parseOperator() - { - $operators = array( - '<=' => 'lte', - '>=' => 'gte', - '<' => 'lt', - '>' => 'gt', - ); - - foreach ($operators as $operator => $method) { - if (strpos($this->value, $operator) === 0) { - $this->value = substr($this->value, strlen($operator)); - return $method; - } - } - - return ''; - } - - /** - * Apply a date filter - * - * @access protected - * @param string $field - */ - protected function applyDateFilter($field) - { - $timestamp = strtotime($this->value); - $method = $this->parseOperator(); - - if ($method !== '') { - $this->query->$method($field, $timestamp); - } else { - $this->query->gte($field, $timestamp); - $this->query->lte($field, $timestamp + 86399); - } - } } diff --git a/app/Filter/ProjectActivityCreationDateFilter.php b/app/Filter/ProjectActivityCreationDateFilter.php new file mode 100644 index 00000000..d0b7f754 --- /dev/null +++ b/app/Filter/ProjectActivityCreationDateFilter.php @@ -0,0 +1,38 @@ +applyDateFilter(ProjectActivity::TABLE.'.date_creation'); + return $this; + } +} diff --git a/app/Filter/ProjectActivityCreatorFilter.php b/app/Filter/ProjectActivityCreatorFilter.php new file mode 100644 index 00000000..c95569d6 --- /dev/null +++ b/app/Filter/ProjectActivityCreatorFilter.php @@ -0,0 +1,65 @@ +currentUserId = $userId; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('creator'); + } + + /** + * Apply filter + * + * @access public + * @return string + */ + public function apply() + { + if ($this->value === 'me') { + $this->query->eq(ProjectActivity::TABLE . '.creator_id', $this->currentUserId); + } else { + $this->query->beginOr(); + $this->query->ilike('uc.username', '%'.$this->value.'%'); + $this->query->ilike('uc.name', '%'.$this->value.'%'); + $this->query->closeOr(); + } + } +} diff --git a/app/Filter/ProjectActivityProjectIdsFilter.php b/app/Filter/ProjectActivityProjectIdsFilter.php index 4d7c9028..47cf0c25 100644 --- a/app/Filter/ProjectActivityProjectIdsFilter.php +++ b/app/Filter/ProjectActivityProjectIdsFilter.php @@ -21,7 +21,7 @@ class ProjectActivityProjectIdsFilter extends BaseFilter implements FilterInterf */ public function getAttributes() { - return array('project_ids'); + return array('projects'); } /** diff --git a/app/Filter/ProjectActivityProjectNameFilter.php b/app/Filter/ProjectActivityProjectNameFilter.php new file mode 100644 index 00000000..0cf73657 --- /dev/null +++ b/app/Filter/ProjectActivityProjectNameFilter.php @@ -0,0 +1,38 @@ +query->ilike(Project::TABLE.'.name', '%'.$this->value.'%'); + return $this; + } +} diff --git a/app/Filter/ProjectActivityTaskStatusFilter.php b/app/Filter/ProjectActivityTaskStatusFilter.php new file mode 100644 index 00000000..69e2c52d --- /dev/null +++ b/app/Filter/ProjectActivityTaskStatusFilter.php @@ -0,0 +1,43 @@ +value === 'open') { + $this->query->eq(Task::TABLE.'.is_active', Task::STATUS_OPEN); + } elseif ($this->value === 'closed') { + $this->query->eq(Task::TABLE.'.is_active', Task::STATUS_CLOSED); + } + + return $this; + } +} diff --git a/app/Filter/ProjectActivityTaskTitleFilter.php b/app/Filter/ProjectActivityTaskTitleFilter.php index ed3f36d6..bf2afa30 100644 --- a/app/Filter/ProjectActivityTaskTitleFilter.php +++ b/app/Filter/ProjectActivityTaskTitleFilter.php @@ -3,7 +3,6 @@ namespace Kanboard\Filter; use Kanboard\Core\Filter\FilterInterface; -use Kanboard\Model\Task; /** * Filter activity events by task title @@ -11,7 +10,7 @@ use Kanboard\Model\Task; * @package filter * @author Frederic Guillot */ -class ProjectActivityTaskTitleFilter extends BaseFilter implements FilterInterface +class ProjectActivityTaskTitleFilter extends TaskTitleFilter implements FilterInterface { /** * Get search attribute @@ -23,16 +22,4 @@ class ProjectActivityTaskTitleFilter extends BaseFilter implements FilterInterfa { return array('title'); } - - /** - * Apply filter - * - * @access public - * @return FilterInterface - */ - public function apply() - { - $this->query->ilike(Task::TABLE.'.title', '%'.$this->value.'%'); - return $this; - } } diff --git a/app/Filter/TaskCompletionDateFilter.php b/app/Filter/TaskCompletionDateFilter.php index 5166bebf..f206a3e2 100644 --- a/app/Filter/TaskCompletionDateFilter.php +++ b/app/Filter/TaskCompletionDateFilter.php @@ -11,7 +11,7 @@ use Kanboard\Model\Task; * @package filter * @author Frederic Guillot */ -class TaskCompletionDateFilter extends BaseFilter implements FilterInterface +class TaskCompletionDateFilter extends BaseDateFilter implements FilterInterface { /** * Get search attribute diff --git a/app/Filter/TaskCreationDateFilter.php b/app/Filter/TaskCreationDateFilter.php index 26318b3e..bb6efad6 100644 --- a/app/Filter/TaskCreationDateFilter.php +++ b/app/Filter/TaskCreationDateFilter.php @@ -11,7 +11,7 @@ use Kanboard\Model\Task; * @package filter * @author Frederic Guillot */ -class TaskCreationDateFilter extends BaseFilter implements FilterInterface +class TaskCreationDateFilter extends BaseDateFilter implements FilterInterface { /** * Get search attribute diff --git a/app/Filter/TaskDueDateFilter.php b/app/Filter/TaskDueDateFilter.php index 6ba55eb9..e36efdd0 100644 --- a/app/Filter/TaskDueDateFilter.php +++ b/app/Filter/TaskDueDateFilter.php @@ -11,7 +11,7 @@ use Kanboard\Model\Task; * @package filter * @author Frederic Guillot */ -class TaskDueDateFilter extends BaseFilter implements FilterInterface +class TaskDueDateFilter extends BaseDateFilter implements FilterInterface { /** * Get search attribute diff --git a/app/Filter/TaskModificationDateFilter.php b/app/Filter/TaskModificationDateFilter.php index d8838bce..5036e9c1 100644 --- a/app/Filter/TaskModificationDateFilter.php +++ b/app/Filter/TaskModificationDateFilter.php @@ -11,7 +11,7 @@ use Kanboard\Model\Task; * @package filter * @author Frederic Guillot */ -class TaskModificationDateFilter extends BaseFilter implements FilterInterface +class TaskModificationDateFilter extends BaseDateFilter implements FilterInterface { /** * Get search attribute diff --git a/app/Filter/TaskProjectsFilter.php b/app/Filter/TaskProjectsFilter.php index e0fc09cf..47636b1d 100644 --- a/app/Filter/TaskProjectsFilter.php +++ b/app/Filter/TaskProjectsFilter.php @@ -32,7 +32,12 @@ class TaskProjectsFilter extends BaseFilter implements FilterInterface */ public function apply() { - $this->query->in(Task::TABLE.'.project_id', $this->value); + if (empty($this->value)) { + $this->query->eq(Task::TABLE.'.project_id', 0); + } else { + $this->query->in(Task::TABLE.'.project_id', $this->value); + } + return $this; } } diff --git a/app/Filter/TaskStartDateFilter.php b/app/Filter/TaskStartDateFilter.php index d45bc0d4..dd30762b 100644 --- a/app/Filter/TaskStartDateFilter.php +++ b/app/Filter/TaskStartDateFilter.php @@ -11,7 +11,7 @@ use Kanboard\Model\Task; * @package filter * @author Frederic Guillot */ -class TaskStartDateFilter extends BaseFilter implements FilterInterface +class TaskStartDateFilter extends BaseDateFilter implements FilterInterface { /** * Get search attribute diff --git a/app/Helper/ProjectActivityHelper.php b/app/Helper/ProjectActivityHelper.php index 738fec66..0638a978 100644 --- a/app/Helper/ProjectActivityHelper.php +++ b/app/Helper/ProjectActivityHelper.php @@ -17,6 +17,33 @@ use Kanboard\Model\ProjectActivity; */ class ProjectActivityHelper extends Base { + /** + * Search events + * + * @access public + * @param string $search + * @return array + */ + public function searchEvents($search) + { + $projects = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $events = array(); + + if ($search !== '') { + $queryBuilder = $this->projectActivityLexer->build($search); + $queryBuilder + ->withFilter(new ProjectActivityProjectIdsFilter(array_keys($projects))) + ->getQuery() + ->desc(ProjectActivity::TABLE.'.id') + ->limit(500) + ; + + $events = $queryBuilder->format(new ProjectActivityEventFormatter($this->container)); + } + + return $events; + } + /** * Get project activity events * diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index 7ca864f4..e384f923 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index b2921de9..3c8de1ad 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index c4743922..747fa2d1 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 999bf048..fa447e62 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -1153,4 +1153,14 @@ return array( 'Upload my avatar image' => 'Mein Avatar Bild hochladen', 'Remove my image' => 'Mein Bild entfernen', 'The OAuth2 state parameter is invalid' => 'Der OAuth2 Statusparameter ist ungültig', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php index 9a31e485..84cf8462 100644 --- a/app/Locale/el_GR/translations.php +++ b/app/Locale/el_GR/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index c3623369..e52c959b 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 8e5dd81f..f47852b0 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index cedd6039..0c2e4955 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -1153,4 +1153,14 @@ return array( 'Upload my avatar image' => 'Uploader mon image d\'avatar', 'Remove my image' => 'Supprimer mon image', 'The OAuth2 state parameter is invalid' => 'Le paramètre "state" de OAuth2 est invalide', + 'User not found.' => 'Utilisateur introuvable.', + 'Search in activity stream' => 'Chercher dans le flux d\'activité', + 'My activities' => 'Mes activités', + 'Activity until yesterday' => 'Activités jusqu\'à hier', + 'Activity until today' => 'Activités jusqu\'à aujourd\'hui', + 'Search by creator: ' => 'Rechercher par créateur : ', + 'Search by creation date: ' => 'Rechercher par date de création : ', + 'Search by task status: ' => 'Rechercher par le statut des tâches : ', + 'Search by task title: ' => 'Rechercher par le titre des tâches : ', + 'Activity stream search' => 'Recherche dans le flux d\'activité', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index f642a6c1..9a2d666a 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index 3f105054..9cbca60e 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 93ceb03f..d0209b3a 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index b48eabd8..69ab5f17 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/ko_KR/translations.php b/app/Locale/ko_KR/translations.php index 8379761f..f4320c55 100644 --- a/app/Locale/ko_KR/translations.php +++ b/app/Locale/ko_KR/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php index 36b3db0b..f6f15937 100644 --- a/app/Locale/my_MY/translations.php +++ b/app/Locale/my_MY/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index 465efb53..f3d3047a 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index 3c3fa1ee..f08f5eff 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index d06e347f..8222f9e1 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index cdb06dea..60242d95 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -1153,4 +1153,14 @@ return array( 'Upload my avatar image' => 'Enviar a minha imagem de avatar', 'Remove my image' => 'Remover a minha imagem', 'The OAuth2 state parameter is invalid' => 'O parâmetro "state" de OAuth2 não é válido', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index e38344f8..956d1259 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -1153,4 +1153,14 @@ return array( 'Upload my avatar image' => 'Enviar a minha imagem de avatar', 'Remove my image' => 'Remover a minha imagem', 'The OAuth2 state parameter is invalid' => 'O parametro de estado do OAuth2 é inválido', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index b3503e52..1e548e0d 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -1152,5 +1152,15 @@ return array( 'Avatar' => 'Аватар', 'Upload my avatar image' => 'Загрузить моё изображение для аватара', 'Remove my image' => 'Удалить моё изображение', - 'The OAuth2 state parameter is invalid' => 'Параметр состояние OAuth2 неправильный' + 'The OAuth2 state parameter is invalid' => 'Параметр состояние OAuth2 неправильный', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index c7070a8d..b69e6cf4 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index e4728d2d..634b87d0 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 1e2fb98a..1e913f28 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index 6e8fae2f..95bcc8a8 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index decd49d8..7b0c3139 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -1153,4 +1153,14 @@ return array( // 'Upload my avatar image' => '', // 'Remove my image' => '', // 'The OAuth2 state parameter is invalid' => '', + // 'User not found.' => '', + // 'Search in activity stream' => '', + // 'My activities' => '', + // 'Activity until yesterday' => '', + // 'Activity until today' => '', + // 'Search by creator: ' => '', + // 'Search by creation date: ' => '', + // 'Search by task status: ' => '', + // 'Search by task title: ' => '', + // 'Activity stream search' => '', ); diff --git a/app/Model/ProjectActivity.php b/app/Model/ProjectActivity.php index 31cee113..d993015b 100644 --- a/app/Model/ProjectActivity.php +++ b/app/Model/ProjectActivity.php @@ -71,6 +71,7 @@ class ProjectActivity extends Base 'uc.avatar_path' ) ->join(Task::TABLE, 'id', 'task_id') + ->join(Project::TABLE, 'id', 'project_id') ->left(User::TABLE, 'uc', 'id', ProjectActivity::TABLE, 'creator_id'); } diff --git a/app/ServiceProvider/FilterProvider.php b/app/ServiceProvider/FilterProvider.php index 4b4dbd2d..f3918d77 100644 --- a/app/ServiceProvider/FilterProvider.php +++ b/app/ServiceProvider/FilterProvider.php @@ -4,6 +4,10 @@ namespace Kanboard\ServiceProvider; use Kanboard\Core\Filter\LexerBuilder; use Kanboard\Core\Filter\QueryBuilder; +use Kanboard\Filter\ProjectActivityCreationDateFilter; +use Kanboard\Filter\ProjectActivityCreatorFilter; +use Kanboard\Filter\ProjectActivityProjectNameFilter; +use Kanboard\Filter\ProjectActivityTaskStatusFilter; use Kanboard\Filter\ProjectActivityTaskTitleFilter; use Kanboard\Filter\TaskAssigneeFilter; use Kanboard\Filter\TaskCategoryFilter; @@ -86,8 +90,18 @@ class FilterProvider implements ServiceProviderInterface $container['projectActivityLexer'] = $container->factory(function ($c) { $builder = new LexerBuilder(); - $builder->withQuery($c['projectActivity']->getQuery()); - $builder->withFilter(new ProjectActivityTaskTitleFilter()); + $builder + ->withQuery($c['projectActivity']->getQuery()) + ->withFilter(new ProjectActivityTaskTitleFilter(), true) + ->withFilter(new ProjectActivityTaskStatusFilter()) + ->withFilter(new ProjectActivityProjectNameFilter()) + ->withFilter(ProjectActivityCreationDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter(ProjectActivityCreatorFilter::getInstance() + ->setCurrentUserId($c['userSession']->getId()) + ) + ; return $builder; }); @@ -124,17 +138,23 @@ class FilterProvider implements ServiceProviderInterface ) ->withFilter(new TaskColumnFilter()) ->withFilter(new TaskCommentFilter()) - ->withFilter(new TaskCreationDateFilter()) + ->withFilter(TaskCreationDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) ->withFilter(TaskCreatorFilter::getInstance() ->setCurrentUserId($c['userSession']->getId()) ) ->withFilter(new TaskDescriptionFilter()) - ->withFilter(new TaskDueDateFilter()) + ->withFilter(TaskDueDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) ->withFilter(new TaskIdFilter()) ->withFilter(TaskLinkFilter::getInstance() ->setDatabase($c['db']) ) - ->withFilter(new TaskModificationDateFilter()) + ->withFilter(TaskModificationDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) ->withFilter(new TaskProjectFilter()) ->withFilter(new TaskReferenceFilter()) ->withFilter(new TaskStatusFilter()) diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index 0e7548d4..30d23a51 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -42,7 +42,7 @@ class RouteProvider implements ServiceProviderInterface // Search routes $container['route']->addRoute('search', 'search', 'index'); - $container['route']->addRoute('search/:search', 'search', 'index'); + $container['route']->addRoute('search/activity', 'search', 'activity'); // ProjectCreation routes $container['route']->addRoute('project/create', 'ProjectCreation', 'create'); @@ -62,6 +62,7 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('project/:project_id/enable', 'project', 'enable'); $container['route']->addRoute('project/:project_id/permissions', 'ProjectPermission', 'index'); $container['route']->addRoute('project/:project_id/import', 'taskImport', 'step1'); + $container['route']->addRoute('project/:project_id/activity', 'activity', 'project'); // Project Overview $container['route']->addRoute('project/:project_id/overview', 'ProjectOverview', 'show'); diff --git a/app/Template/activity/filter_dropdown.php b/app/Template/activity/filter_dropdown.php new file mode 100644 index 00000000..8d7a7de3 --- /dev/null +++ b/app/Template/activity/filter_dropdown.php @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/app/Template/search/activity.php b/app/Template/search/activity.php new file mode 100644 index 00000000..60362215 --- /dev/null +++ b/app/Template/search/activity.php @@ -0,0 +1,39 @@ +
+ + +
+ +
+ + +
+

+

project:"My project" creator:me

+
    +
  • project:"My project"
  • +
  • creator:admin
  • +
  • created:today
  • +
  • status:open
  • +
  • title:"My task"
  • +
+

url->doc(t('View advanced search syntax'), 'search') ?>

+
+ +

+ + render('event/events', array('events' => $events)) ?> + + +
\ No newline at end of file diff --git a/app/Template/search/index.php b/app/Template/search/index.php index 9231a6f3..d5d07ed6 100644 --- a/app/Template/search/index.php +++ b/app/Template/search/index.php @@ -2,8 +2,8 @@ diff --git a/doc/search.markdown b/doc/search.markdown index f6d343e9..37bb8625 100644 --- a/doc/search.markdown +++ b/doc/search.markdown @@ -1,7 +1,8 @@ Advanced Search Syntax ====================== -Kanboard uses a simple query language for advanced search. +Kanboard uses a simple query language for advanced search. +You can search in tasks, comments, subtasks, links but also in the activity stream. Example of query ---------------- @@ -12,23 +13,23 @@ This example will return all tasks assigned to me with a due date for tomorrow a assigne:me due:tomorrow my title ``` -Search by task id or title --------------------------- +Global search +------------- + +### Search by task id or title - Search by task id: `#123` - Search by task id and task title: `123` - Search by task title: anything that doesn't match any search attributes -Search by status ----------------- +### Search by status Attribute: **status** - Query to find open tasks: `status:open` - Query to find closed tasks: `status:closed` -Search by assignee ------------------- +### Search by assignee Attribute: **assignee** @@ -38,8 +39,7 @@ Attribute: **assignee** - Query for unassigned tasks: `assignee:nobody` - Query for my assigned tasks: `assignee:me` -Search by task creator ----------------------- +### Search by task creator Attribute: **creator** @@ -47,23 +47,20 @@ Attribute: **creator** - Tasks created by John Doe: `creator:"John Doe"` - Tasks created by the user id #1: `creator:1` -Search by subtask assignee --------------------------- +### Search by subtask assignee Attribute: **subtask:assignee** - Example: `subtask:assignee:"John Doe"` -Search by color ---------------- +### Search by color Attribute: **color** - Query to search by color id: `color:blue` - Query to search by color name: `color:"Deep Orange"` -Search by the due date ----------------------- +### Search by the due date Attribute: **due** @@ -83,8 +80,7 @@ Operators supported with a date: - Greater than or equal: **due:>=2015-06-29** - Lower than or equal: **due:<=2015-06-29** -Search by modification date ---------------------------- +### Search by modification date Attribute: **modified** or **updated** @@ -94,29 +90,25 @@ There is also a filter by recently modified tasks: `modified:recently`. This query will use the same value as the board highlight period configured in settings. -Search by creation date ------------------------ +### Search by creation date Attribute: **created** Works in the same way as the modification date queries. -Search by description ---------------------- +### Search by description Attribute: **description** or **desc** Example: `description:"text search"` -Search by external reference ----------------------------- +### Search by external reference The task reference is an external id of your task, by example a ticket number from another software. - Find tasks with a reference: `ref:1234` or `reference:TICKET-1234` -Search by category ------------------- +### Search by category Attribute: **category** @@ -124,8 +116,7 @@ Attribute: **category** - Find all tasks that have those categories: `category:"Bug" category:"Improvements"` - Find tasks with no category assigned: `category:none` -Search by project ------------------ +### Search by project Attribute: **project** @@ -133,16 +124,14 @@ Attribute: **project** - Find tasks by project id: `project:23` - Find tasks for several projects: `project:"My project A" project:"My project B"` -Search by columns ------------------ +### Search by columns Attribute: **column** - Find tasks by column name: `column:"Work in progress"` - Find tasks for several columns: `column:"Backlog" column:ready` -Search by swim-lane -------------------- +### Search by swim-lane Attribute: **swimlane** @@ -150,17 +139,41 @@ Attribute: **swimlane** - Find tasks in the default swim-lane: `swimlane:default` - Find tasks into several swim-lanes: `swimlane:"Version 1.2" swimlane:"Version 1.3"` -Search by task link ------------------- +### Search by task link Attribute: **link** - Find tasks by link name: `link:"is a milestone of"` - Find tasks into several links: `link:"is a milestone of" link:"relates to"` -Search by comment ------------------ +### Search by comment Attribute: **comment** - Find comments that contains this title: `comment:"My comment message"` + +Activity stream search +---------------------- + +### Search events by task title + +Attribute: **title** or none (default) + +- Example: `title:"My task"` +- Search by task id: `#123` + +### Search events by task status + +Attribute: **status** + +### Search by event creator + +Attribute: **creator** + +### Search by event creation date + +Attribute: **created** + +### Search events by project + +Attribute: **project** diff --git a/tests/units/Filter/ProjectActivityCreationDateFilterTest.php b/tests/units/Filter/ProjectActivityCreationDateFilterTest.php new file mode 100644 index 00000000..d679f285 --- /dev/null +++ b/tests/units/Filter/ProjectActivityCreationDateFilterTest.php @@ -0,0 +1,117 @@ +container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreationDateFilter('today'); + $filter->setDateParser($this->container['dateParser']); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + } + + public function testWithYesterday() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreationDateFilter('yesterday'); + $filter->setDateParser($this->container['dateParser']); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(0, $events); + } + + public function testWithIsoDate() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreationDateFilter(date('Y-m-d')); + $filter->setDateParser($this->container['dateParser']); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + } + + public function testWithOperatorAndIsoDate() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreationDateFilter('>='.date('Y-m-d')); + $filter->setDateParser($this->container['dateParser']); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreationDateFilter('<'.date('Y-m-d')); + $filter->setDateParser($this->container['dateParser']); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(0, $events); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreationDateFilter('>'.date('Y-m-d')); + $filter->setDateParser($this->container['dateParser']); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(0, $events); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreationDateFilter('>='.date('Y-m-d')); + $filter->setDateParser($this->container['dateParser']); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + } +} diff --git a/tests/units/Filter/ProjectActivityCreatorFilterTest.php b/tests/units/Filter/ProjectActivityCreatorFilterTest.php new file mode 100644 index 00000000..99c70322 --- /dev/null +++ b/tests/units/Filter/ProjectActivityCreatorFilterTest.php @@ -0,0 +1,91 @@ +container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreatorFilter('admin'); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + } + + public function testWithAnotherUsername() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreatorFilter('John Doe'); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(0, $events); + } + + public function testWithCurrentUser() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreatorFilter('me'); + $filter->setCurrentUserId(1); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + } + + public function testWithAnotherCurrentUser() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityCreatorFilter('me'); + $filter->setCurrentUserId(2); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(0, $events); + } +} diff --git a/tests/units/Filter/ProjectActivityProjectNameFilterTest.php b/tests/units/Filter/ProjectActivityProjectNameFilterTest.php new file mode 100644 index 00000000..de9d7d59 --- /dev/null +++ b/tests/units/Filter/ProjectActivityProjectNameFilterTest.php @@ -0,0 +1,35 @@ +container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + $query = $projectActivityModel->getQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + $this->assertEquals(2, $projectModel->create(array('name' => 'P2'))); + + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(2, $taskCreation->create(array('title' => 'Test', 'project_id' => 2))); + + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + $this->assertNotFalse($projectActivityModel->createEvent(2, 2, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(2)))); + + $filter = new ProjectActivityProjectNameFilter('P1'); + $filter->withQuery($query)->apply(); + $this->assertCount(1, $query->findAll()); + } +} diff --git a/tests/units/Filter/ProjectActivityTaskStatusFilterTest.php b/tests/units/Filter/ProjectActivityTaskStatusFilterTest.php new file mode 100644 index 00000000..b8df6338 --- /dev/null +++ b/tests/units/Filter/ProjectActivityTaskStatusFilterTest.php @@ -0,0 +1,49 @@ +container); + $taskCreation = new TaskCreation($this->container); + $taskStatus = new TaskStatus($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(2, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 2, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(2)))); + + $this->assertTrue($taskStatus->close(1)); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityTaskStatusFilter('open'); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + $this->assertEquals(2, $events[0]['task_id']); + + $query = $projectActivityModel->getQuery(); + $filter = new ProjectActivityTaskStatusFilter('closed'); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + $this->assertEquals(1, $events[0]['task_id']); + } +} diff --git a/tests/units/Filter/ProjectActivityTaskTitleFilterTest.php b/tests/units/Filter/ProjectActivityTaskTitleFilterTest.php index 6a7c23af..925a1ab2 100644 --- a/tests/units/Filter/ProjectActivityTaskTitleFilterTest.php +++ b/tests/units/Filter/ProjectActivityTaskTitleFilterTest.php @@ -11,7 +11,7 @@ require_once __DIR__.'/../Base.php'; class ProjectActivityTaskTitleFilterTest extends Base { - public function testFilterByTaskId() + public function testWithFullTitle() { $taskFinder = new TaskFinder($this->container); $taskCreation = new TaskCreation($this->container); @@ -31,4 +31,49 @@ class ProjectActivityTaskTitleFilterTest extends Base $filter->withQuery($query)->apply(); $this->assertCount(1, $query->findAll()); } + + public function testWithPartialTitle() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + $query = $projectActivityModel->getQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test1', 'project_id' => 1))); + $this->assertEquals(2, $taskCreation->create(array('title' => 'Test2', 'project_id' => 1))); + + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 2, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(2)))); + + $filter = new ProjectActivityTaskTitleFilter('test'); + $filter->withQuery($query)->apply(); + $this->assertCount(2, $query->findAll()); + } + + public function testWithId() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $projectActivityModel = new ProjectActivity($this->container); + $query = $projectActivityModel->getQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'P1'))); + + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test1', 'project_id' => 1))); + $this->assertEquals(2, $taskCreation->create(array('title' => 'Test2', 'project_id' => 1))); + + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(1)))); + $this->assertNotFalse($projectActivityModel->createEvent(1, 2, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(2)))); + + $filter = new ProjectActivityTaskTitleFilter('#2'); + $filter->withQuery($query)->apply(); + + $events = $query->findAll(); + $this->assertCount(1, $events); + $this->assertEquals(2, $events[0]['task_id']); + } } -- cgit v1.2.3