From 11858be4e8d5aba983700c6cba1c4d0a33ea8e9d Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sat, 9 Apr 2016 22:42:17 -0400 Subject: Filter refactoring --- app/Filter/BaseFilter.php | 119 ++++++++++++++++++++++ app/Filter/ProjectGroupRoleProjectFilter.php | 38 +++++++ app/Filter/ProjectGroupRoleUsernameFilter.php | 44 ++++++++ app/Filter/ProjectIdsFilter.php | 43 ++++++++ app/Filter/ProjectStatusFilter.php | 45 +++++++++ app/Filter/ProjectTypeFilter.php | 45 +++++++++ app/Filter/ProjectUserRoleProjectFilter.php | 38 +++++++ app/Filter/ProjectUserRoleUsernameFilter.php | 41 ++++++++ app/Filter/TaskAssigneeFilter.php | 75 ++++++++++++++ app/Filter/TaskCategoryFilter.php | 46 +++++++++ app/Filter/TaskColorFilter.php | 60 +++++++++++ app/Filter/TaskColumnFilter.php | 44 ++++++++ app/Filter/TaskCompletionDateFilter.php | 38 +++++++ app/Filter/TaskCreationDateFilter.php | 38 +++++++ app/Filter/TaskDescriptionFilter.php | 38 +++++++ app/Filter/TaskDueDateFilter.php | 41 ++++++++ app/Filter/TaskDueDateRangeFilter.php | 39 +++++++ app/Filter/TaskIdExclusionFilter.php | 38 +++++++ app/Filter/TaskIdFilter.php | 38 +++++++ app/Filter/TaskLinkFilter.php | 85 ++++++++++++++++ app/Filter/TaskModificationDateFilter.php | 38 +++++++ app/Filter/TaskProjectFilter.php | 44 ++++++++ app/Filter/TaskProjectsFilter.php | 38 +++++++ app/Filter/TaskReferenceFilter.php | 38 +++++++ app/Filter/TaskStartDateFilter.php | 38 +++++++ app/Filter/TaskStatusFilter.php | 43 ++++++++ app/Filter/TaskSubtaskAssigneeFilter.php | 140 ++++++++++++++++++++++++++ app/Filter/TaskSwimlaneFilter.php | 50 +++++++++ app/Filter/TaskTitleFilter.php | 46 +++++++++ app/Filter/UserNameFilter.php | 35 +++++++ 30 files changed, 1503 insertions(+) create mode 100644 app/Filter/BaseFilter.php create mode 100644 app/Filter/ProjectGroupRoleProjectFilter.php create mode 100644 app/Filter/ProjectGroupRoleUsernameFilter.php create mode 100644 app/Filter/ProjectIdsFilter.php create mode 100644 app/Filter/ProjectStatusFilter.php create mode 100644 app/Filter/ProjectTypeFilter.php create mode 100644 app/Filter/ProjectUserRoleProjectFilter.php create mode 100644 app/Filter/ProjectUserRoleUsernameFilter.php create mode 100644 app/Filter/TaskAssigneeFilter.php create mode 100644 app/Filter/TaskCategoryFilter.php create mode 100644 app/Filter/TaskColorFilter.php create mode 100644 app/Filter/TaskColumnFilter.php create mode 100644 app/Filter/TaskCompletionDateFilter.php create mode 100644 app/Filter/TaskCreationDateFilter.php create mode 100644 app/Filter/TaskDescriptionFilter.php create mode 100644 app/Filter/TaskDueDateFilter.php create mode 100644 app/Filter/TaskDueDateRangeFilter.php create mode 100644 app/Filter/TaskIdExclusionFilter.php create mode 100644 app/Filter/TaskIdFilter.php create mode 100644 app/Filter/TaskLinkFilter.php create mode 100644 app/Filter/TaskModificationDateFilter.php create mode 100644 app/Filter/TaskProjectFilter.php create mode 100644 app/Filter/TaskProjectsFilter.php create mode 100644 app/Filter/TaskReferenceFilter.php create mode 100644 app/Filter/TaskStartDateFilter.php create mode 100644 app/Filter/TaskStatusFilter.php create mode 100644 app/Filter/TaskSubtaskAssigneeFilter.php create mode 100644 app/Filter/TaskSwimlaneFilter.php create mode 100644 app/Filter/TaskTitleFilter.php create mode 100644 app/Filter/UserNameFilter.php (limited to 'app/Filter') diff --git a/app/Filter/BaseFilter.php b/app/Filter/BaseFilter.php new file mode 100644 index 00000000..a7e6a61a --- /dev/null +++ b/app/Filter/BaseFilter.php @@ -0,0 +1,119 @@ +value = $value; + } + + /** + * Get object instance + * + * @static + * @access public + * @param mixed $value + * @return static + */ + public static function getInstance($value = null) + { + $self = new static($value); + return $self; + } + + /** + * Set query + * + * @access public + * @param Table $query + * @return \Kanboard\Core\Filter\FilterInterface + */ + public function withQuery(Table $query) + { + $this->query = $query; + return $this; + } + + /** + * Set the value + * + * @access public + * @param string $value + * @return \Kanboard\Core\Filter\FilterInterface + */ + public function withValue($value) + { + $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/ProjectGroupRoleProjectFilter.php b/app/Filter/ProjectGroupRoleProjectFilter.php new file mode 100644 index 00000000..b0950868 --- /dev/null +++ b/app/Filter/ProjectGroupRoleProjectFilter.php @@ -0,0 +1,38 @@ +query->eq(ProjectGroupRole::TABLE.'.project_id', $this->value); + return $this; + } +} diff --git a/app/Filter/ProjectGroupRoleUsernameFilter.php b/app/Filter/ProjectGroupRoleUsernameFilter.php new file mode 100644 index 00000000..c10855bc --- /dev/null +++ b/app/Filter/ProjectGroupRoleUsernameFilter.php @@ -0,0 +1,44 @@ +query + ->join(GroupMember::TABLE, 'group_id', 'group_id', ProjectGroupRole::TABLE) + ->join(User::TABLE, 'id', 'user_id', GroupMember::TABLE) + ->ilike(User::TABLE.'.username', $this->value.'%'); + + return $this; + } +} diff --git a/app/Filter/ProjectIdsFilter.php b/app/Filter/ProjectIdsFilter.php new file mode 100644 index 00000000..641f7f18 --- /dev/null +++ b/app/Filter/ProjectIdsFilter.php @@ -0,0 +1,43 @@ +value)) { + $this->query->eq(Project::TABLE.'.id', 0); + } else { + $this->query->in(Project::TABLE.'.id', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/ProjectStatusFilter.php b/app/Filter/ProjectStatusFilter.php new file mode 100644 index 00000000..a994600c --- /dev/null +++ b/app/Filter/ProjectStatusFilter.php @@ -0,0 +1,45 @@ +value) || ctype_digit($this->value)) { + $this->query->eq(Project::TABLE.'.is_active', $this->value); + } elseif ($this->value === 'inactive' || $this->value === 'closed' || $this->value === 'disabled') { + $this->query->eq(Project::TABLE.'.is_active', 0); + } else { + $this->query->eq(Project::TABLE.'.is_active', 1); + } + + return $this; + } +} diff --git a/app/Filter/ProjectTypeFilter.php b/app/Filter/ProjectTypeFilter.php new file mode 100644 index 00000000..e085e2f6 --- /dev/null +++ b/app/Filter/ProjectTypeFilter.php @@ -0,0 +1,45 @@ +value) || ctype_digit($this->value)) { + $this->query->eq(Project::TABLE.'.is_private', $this->value); + } elseif ($this->value === 'private') { + $this->query->eq(Project::TABLE.'.is_private', Project::TYPE_PRIVATE); + } else { + $this->query->eq(Project::TABLE.'.is_private', Project::TYPE_TEAM); + } + + return $this; + } +} diff --git a/app/Filter/ProjectUserRoleProjectFilter.php b/app/Filter/ProjectUserRoleProjectFilter.php new file mode 100644 index 00000000..3b880df5 --- /dev/null +++ b/app/Filter/ProjectUserRoleProjectFilter.php @@ -0,0 +1,38 @@ +query->eq(ProjectUserRole::TABLE.'.project_id', $this->value); + return $this; + } +} diff --git a/app/Filter/ProjectUserRoleUsernameFilter.php b/app/Filter/ProjectUserRoleUsernameFilter.php new file mode 100644 index 00000000..c00493a3 --- /dev/null +++ b/app/Filter/ProjectUserRoleUsernameFilter.php @@ -0,0 +1,41 @@ +query + ->join(User::TABLE, 'id', 'user_id') + ->ilike(User::TABLE.'.username', $this->value.'%'); + + return $this; + } +} diff --git a/app/Filter/TaskAssigneeFilter.php b/app/Filter/TaskAssigneeFilter.php new file mode 100644 index 00000000..783d6a12 --- /dev/null +++ b/app/Filter/TaskAssigneeFilter.php @@ -0,0 +1,75 @@ +currentUserId = $userId; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('assignee'); + } + + /** + * Apply filter + * + * @access public + * @return string + */ + public function apply() + { + if (is_int($this->value) || ctype_digit($this->value)) { + $this->query->eq(Task::TABLE.'.owner_id', $this->value); + } else { + switch ($this->value) { + case 'me': + $this->query->eq(Task::TABLE.'.owner_id', $this->currentUserId); + break; + case 'nobody': + $this->query->eq(Task::TABLE.'.owner_id', 0); + break; + default: + $this->query->beginOr(); + $this->query->ilike(User::TABLE.'.username', '%'.$this->value.'%'); + $this->query->ilike(User::TABLE.'.name', '%'.$this->value.'%'); + $this->query->closeOr(); + } + } + } +} diff --git a/app/Filter/TaskCategoryFilter.php b/app/Filter/TaskCategoryFilter.php new file mode 100644 index 00000000..517f24d9 --- /dev/null +++ b/app/Filter/TaskCategoryFilter.php @@ -0,0 +1,46 @@ +value) || ctype_digit($this->value)) { + $this->query->eq(Task::TABLE.'.category_id', $this->value); + } elseif ($this->value === 'none') { + $this->query->eq(Task::TABLE.'.category_id', 0); + } else { + $this->query->eq(Category::TABLE.'.name', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskColorFilter.php b/app/Filter/TaskColorFilter.php new file mode 100644 index 00000000..784162d4 --- /dev/null +++ b/app/Filter/TaskColorFilter.php @@ -0,0 +1,60 @@ +colorModel = $colorModel; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('color', 'colour'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->eq(Task::TABLE.'.color_id', $this->colorModel->find($this->value)); + return $this; + } +} diff --git a/app/Filter/TaskColumnFilter.php b/app/Filter/TaskColumnFilter.php new file mode 100644 index 00000000..9a4d4253 --- /dev/null +++ b/app/Filter/TaskColumnFilter.php @@ -0,0 +1,44 @@ +value) || ctype_digit($this->value)) { + $this->query->eq(Task::TABLE.'.column_id', $this->value); + } else { + $this->query->eq(Column::TABLE.'.title', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskCompletionDateFilter.php b/app/Filter/TaskCompletionDateFilter.php new file mode 100644 index 00000000..5166bebf --- /dev/null +++ b/app/Filter/TaskCompletionDateFilter.php @@ -0,0 +1,38 @@ +applyDateFilter(Task::TABLE.'.date_completed'); + return $this; + } +} diff --git a/app/Filter/TaskCreationDateFilter.php b/app/Filter/TaskCreationDateFilter.php new file mode 100644 index 00000000..26318b3e --- /dev/null +++ b/app/Filter/TaskCreationDateFilter.php @@ -0,0 +1,38 @@ +applyDateFilter(Task::TABLE.'.date_creation'); + return $this; + } +} diff --git a/app/Filter/TaskDescriptionFilter.php b/app/Filter/TaskDescriptionFilter.php new file mode 100644 index 00000000..6dda58ae --- /dev/null +++ b/app/Filter/TaskDescriptionFilter.php @@ -0,0 +1,38 @@ +query->ilike(Task::TABLE.'.description', '%'.$this->value.'%'); + return $this; + } +} diff --git a/app/Filter/TaskDueDateFilter.php b/app/Filter/TaskDueDateFilter.php new file mode 100644 index 00000000..6ba55eb9 --- /dev/null +++ b/app/Filter/TaskDueDateFilter.php @@ -0,0 +1,41 @@ +query->neq(Task::TABLE.'.date_due', 0); + $this->query->notNull(Task::TABLE.'.date_due'); + $this->applyDateFilter(Task::TABLE.'.date_due'); + + return $this; + } +} diff --git a/app/Filter/TaskDueDateRangeFilter.php b/app/Filter/TaskDueDateRangeFilter.php new file mode 100644 index 00000000..10deb0d3 --- /dev/null +++ b/app/Filter/TaskDueDateRangeFilter.php @@ -0,0 +1,39 @@ +query->gte(Task::TABLE.'.date_due', is_numeric($this->value[0]) ? $this->value[0] : strtotime($this->value[0])); + $this->query->lte(Task::TABLE.'.date_due', is_numeric($this->value[1]) ? $this->value[1] : strtotime($this->value[1])); + return $this; + } +} diff --git a/app/Filter/TaskIdExclusionFilter.php b/app/Filter/TaskIdExclusionFilter.php new file mode 100644 index 00000000..8bfefb2b --- /dev/null +++ b/app/Filter/TaskIdExclusionFilter.php @@ -0,0 +1,38 @@ +query->notin(Task::TABLE.'.id', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskIdFilter.php b/app/Filter/TaskIdFilter.php new file mode 100644 index 00000000..87bac794 --- /dev/null +++ b/app/Filter/TaskIdFilter.php @@ -0,0 +1,38 @@ +query->eq(Task::TABLE.'.id', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskLinkFilter.php b/app/Filter/TaskLinkFilter.php new file mode 100644 index 00000000..18a13a09 --- /dev/null +++ b/app/Filter/TaskLinkFilter.php @@ -0,0 +1,85 @@ +db = $db; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('link'); + } + + /** + * Apply filter + * + * @access public + * @return string + */ + public function apply() + { + $task_ids = $this->getSubQuery()->findAllByColumn('task_id'); + + if (! empty($task_ids)) { + $this->query->in(Task::TABLE.'.id', $task_ids); + } else { + $this->query->eq(Task::TABLE.'.id', 0); // No match + } + } + + /** + * Get subquery + * + * @access protected + * @return Table + */ + protected function getSubQuery() + { + return $this->db->table(TaskLink::TABLE) + ->columns( + TaskLink::TABLE.'.task_id', + Link::TABLE.'.label' + ) + ->join(Link::TABLE, 'id', 'link_id', TaskLink::TABLE) + ->ilike(Link::TABLE.'.label', $this->value); + } +} diff --git a/app/Filter/TaskModificationDateFilter.php b/app/Filter/TaskModificationDateFilter.php new file mode 100644 index 00000000..d8838bce --- /dev/null +++ b/app/Filter/TaskModificationDateFilter.php @@ -0,0 +1,38 @@ +applyDateFilter(Task::TABLE.'.date_modification'); + return $this; + } +} diff --git a/app/Filter/TaskProjectFilter.php b/app/Filter/TaskProjectFilter.php new file mode 100644 index 00000000..e432efee --- /dev/null +++ b/app/Filter/TaskProjectFilter.php @@ -0,0 +1,44 @@ +value) || ctype_digit($this->value)) { + $this->query->eq(Task::TABLE.'.project_id', $this->value); + } else { + $this->query->ilike(Project::TABLE.'.name', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskProjectsFilter.php b/app/Filter/TaskProjectsFilter.php new file mode 100644 index 00000000..e0fc09cf --- /dev/null +++ b/app/Filter/TaskProjectsFilter.php @@ -0,0 +1,38 @@ +query->in(Task::TABLE.'.project_id', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskReferenceFilter.php b/app/Filter/TaskReferenceFilter.php new file mode 100644 index 00000000..4ad47dd5 --- /dev/null +++ b/app/Filter/TaskReferenceFilter.php @@ -0,0 +1,38 @@ +query->eq(Task::TABLE.'.reference', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskStartDateFilter.php b/app/Filter/TaskStartDateFilter.php new file mode 100644 index 00000000..d45bc0d4 --- /dev/null +++ b/app/Filter/TaskStartDateFilter.php @@ -0,0 +1,38 @@ +applyDateFilter(Task::TABLE.'.date_started'); + return $this; + } +} diff --git a/app/Filter/TaskStatusFilter.php b/app/Filter/TaskStatusFilter.php new file mode 100644 index 00000000..0ba4361e --- /dev/null +++ b/app/Filter/TaskStatusFilter.php @@ -0,0 +1,43 @@ +value === 'open' || $this->value === 'closed') { + $this->query->eq(Task::TABLE.'.is_active', $this->value === 'open' ? Task::STATUS_OPEN : Task::STATUS_CLOSED); + } else { + $this->query->eq(Task::TABLE.'.is_active', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskSubtaskAssigneeFilter.php b/app/Filter/TaskSubtaskAssigneeFilter.php new file mode 100644 index 00000000..4c757315 --- /dev/null +++ b/app/Filter/TaskSubtaskAssigneeFilter.php @@ -0,0 +1,140 @@ +currentUserId = $userId; + return $this; + } + + /** + * Set database object + * + * @access public + * @param Database $db + * @return TaskSubtaskAssigneeFilter + */ + public function setDatabase(Database $db) + { + $this->db = $db; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('subtask:assignee'); + } + + /** + * Apply filter + * + * @access public + * @return string + */ + public function apply() + { + $task_ids = $this->getSubQuery()->findAllByColumn('task_id'); + + if (! empty($task_ids)) { + $this->query->in(Task::TABLE.'.id', $task_ids); + } else { + $this->query->eq(Task::TABLE.'.id', 0); // No match + } + } + + /** + * Get subquery + * + * @access protected + * @return Table + */ + protected function getSubQuery() + { + $subquery = $this->db->table(Subtask::TABLE) + ->columns( + Subtask::TABLE.'.user_id', + Subtask::TABLE.'.task_id', + User::TABLE.'.name', + User::TABLE.'.username' + ) + ->join(User::TABLE, 'id', 'user_id', Subtask::TABLE) + ->neq(Subtask::TABLE.'.status', Subtask::STATUS_DONE); + + return $this->applySubQueryFilter($subquery); + } + + /** + * Apply subquery filter + * + * @access protected + * @param Table $subquery + * @return Table + */ + protected function applySubQueryFilter(Table $subquery) + { + if (is_int($this->value) || ctype_digit($this->value)) { + $subquery->eq(Subtask::TABLE.'.user_id', $this->value); + } else { + switch ($this->value) { + case 'me': + $subquery->eq(Subtask::TABLE.'.user_id', $this->currentUserId); + break; + case 'nobody': + $subquery->eq(Subtask::TABLE.'.user_id', 0); + break; + default: + $subquery->beginOr(); + $subquery->ilike(User::TABLE.'.username', $this->value.'%'); + $subquery->ilike(User::TABLE.'.name', '%'.$this->value.'%'); + $subquery->closeOr(); + } + } + + return $subquery; + } +} diff --git a/app/Filter/TaskSwimlaneFilter.php b/app/Filter/TaskSwimlaneFilter.php new file mode 100644 index 00000000..4e030244 --- /dev/null +++ b/app/Filter/TaskSwimlaneFilter.php @@ -0,0 +1,50 @@ +value) || ctype_digit($this->value)) { + $this->query->eq(Task::TABLE.'.swimlane_id', $this->value); + } elseif ($this->value === 'default') { + $this->query->eq(Task::TABLE.'.swimlane_id', 0); + } else { + $this->query->beginOr(); + $this->query->ilike(Swimlane::TABLE.'.name', $this->value); + $this->query->ilike(Project::TABLE.'.default_swimlane', $this->value); + $this->query->closeOr(); + } + + return $this; + } +} diff --git a/app/Filter/TaskTitleFilter.php b/app/Filter/TaskTitleFilter.php new file mode 100644 index 00000000..9853369c --- /dev/null +++ b/app/Filter/TaskTitleFilter.php @@ -0,0 +1,46 @@ +value) || (strlen($this->value) > 1 && $this->value{0} === '#' && ctype_digit(substr($this->value, 1)))) { + $this->query->beginOr(); + $this->query->eq(Task::TABLE.'.id', str_replace('#', '', $this->value)); + $this->query->ilike(Task::TABLE.'.title', '%'.$this->value.'%'); + $this->query->closeOr(); + } else { + $this->query->ilike(Task::TABLE.'.title', '%'.$this->value.'%'); + } + + return $this; + } +} diff --git a/app/Filter/UserNameFilter.php b/app/Filter/UserNameFilter.php new file mode 100644 index 00000000..dfb07fdd --- /dev/null +++ b/app/Filter/UserNameFilter.php @@ -0,0 +1,35 @@ +query->beginOr() + ->ilike('username', '%'.$this->value.'%') + ->ilike('name', '%'.$this->value.'%') + ->closeOr(); + + return $this; + } +} -- cgit v1.2.3 From 7705f4c533c3db726624e639be72fc9822904e96 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sat, 9 Apr 2016 23:24:26 -0400 Subject: Added search in comments --- ChangeLog | 4 +++ app/Filter/TaskCommentFilter.php | 41 ++++++++++++++++++++++ app/ServiceProvider/FilterProvider.php | 2 ++ doc/search.markdown | 23 ++++++++---- tests/units/Filter/TaskCommentFilterTest.php | 52 ++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 app/Filter/TaskCommentFilter.php create mode 100644 tests/units/Filter/TaskCommentFilterTest.php (limited to 'app/Filter') diff --git a/ChangeLog b/ChangeLog index ea12d9b9..941c46c9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,10 @@ Version 1.0.28 (unreleased) -------------- +New features: + +* Search in comments + Improvements: * Filter/Lexer/QueryBuilder refactoring diff --git a/app/Filter/TaskCommentFilter.php b/app/Filter/TaskCommentFilter.php new file mode 100644 index 00000000..455098c2 --- /dev/null +++ b/app/Filter/TaskCommentFilter.php @@ -0,0 +1,41 @@ +query->ilike(Comment::TABLE.'.comment', '%'.$this->value.'%'); + $this->query->join(Comment::TABLE, 'task_id', 'id', Task::TABLE); + + return $this; + } +} diff --git a/app/ServiceProvider/FilterProvider.php b/app/ServiceProvider/FilterProvider.php index 555cb262..66608b8c 100644 --- a/app/ServiceProvider/FilterProvider.php +++ b/app/ServiceProvider/FilterProvider.php @@ -8,6 +8,7 @@ use Kanboard\Filter\TaskAssigneeFilter; use Kanboard\Filter\TaskCategoryFilter; use Kanboard\Filter\TaskColorFilter; use Kanboard\Filter\TaskColumnFilter; +use Kanboard\Filter\TaskCommentFilter; use Kanboard\Filter\TaskCreationDateFilter; use Kanboard\Filter\TaskDescriptionFilter; use Kanboard\Filter\TaskDueDateFilter; @@ -85,6 +86,7 @@ class FilterProvider implements ServiceProviderInterface ->withFilter(new TaskCategoryFilter()) ->withFilter(TaskColorFilter::getInstance()->setColorModel($c['color'])) ->withFilter(new TaskColumnFilter()) + ->withFilter(new TaskCommentFilter()) ->withFilter(new TaskCreationDateFilter()) ->withFilter(new TaskDescriptionFilter()) ->withFilter(new TaskDueDateFilter()) diff --git a/doc/search.markdown b/doc/search.markdown index 1a97a7fc..93c8214e 100644 --- a/doc/search.markdown +++ b/doc/search.markdown @@ -38,7 +38,12 @@ Attribute: **assignee** - Query for unassigned tasks: `assignee:nobody` - Query for my assigned tasks: `assignee:me` -Note: Kanboard will also search in assigned subtasks with the status todo and in progress. +Search by subtask assignee +-------------------------- + +Attribute: **subtask:assignee** + +- Example: `subtask:assignee:"John Doe"` Search by color --------------- @@ -90,7 +95,7 @@ Works in the same way as the modification date queries. Search by description --------------------- -Attribute: **description** +Attribute: **description** or **desc** Example: `description:"text search"` @@ -127,14 +132,14 @@ 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** -- Find tasks by swim lane: `swimlane:"Version 42"` -- Find tasks in the default swim lane: `swimlane:default` -- Find tasks into several swim lanes: `swimlane:"Version 1.2" swimlane:"Version 1.3"` +- Find tasks by swim-lane: `swimlane:"Version 42"` +- 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 ------------------ @@ -144,3 +149,9 @@ 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 +----------------- + +Attribute: **comment** + +- Find comments that contains this title: `comment:"My comment message"` diff --git a/tests/units/Filter/TaskCommentFilterTest.php b/tests/units/Filter/TaskCommentFilterTest.php new file mode 100644 index 00000000..8d1b7f44 --- /dev/null +++ b/tests/units/Filter/TaskCommentFilterTest.php @@ -0,0 +1,52 @@ +container); + $taskCreation = new TaskCreation($this->container); + $commentModel = new Comment($this->container); + $projectModel = new Project($this->container); + $query = $taskFinder->getExtendedQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(1, $commentModel->create(array('task_id' => 1, 'user_id' => 1, 'comment' => 'This is a test'))); + + $filter = new TaskCommentFilter(); + $filter->withQuery($query); + $filter->withValue('test'); + $filter->apply(); + + $this->assertCount(1, $query->findAll()); + } + + public function testNoMatch() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $commentModel = new Comment($this->container); + $projectModel = new Project($this->container); + $query = $taskFinder->getExtendedQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(1, $commentModel->create(array('task_id' => 1, 'user_id' => 1, 'comment' => 'This is a test'))); + + $filter = new TaskCommentFilter(); + $filter->withQuery($query); + $filter->withValue('foobar'); + $filter->apply(); + + $this->assertCount(0, $query->findAll()); + } +} -- cgit v1.2.3 From 38326c4ddf91ed54374775d7f7599136f3e38eea Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 10 Apr 2016 08:15:10 -0400 Subject: Added search by task creator --- ChangeLog | 1 + app/Filter/TaskCreatorFilter.php | 74 +++++++++++++ app/Model/TaskFinder.php | 1 + app/ServiceProvider/FilterProvider.php | 8 +- doc/search.markdown | 13 ++- tests/units/Filter/TaskCreatorFilterTest.php | 159 +++++++++++++++++++++++++++ 6 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 app/Filter/TaskCreatorFilter.php create mode 100644 tests/units/Filter/TaskCreatorFilterTest.php (limited to 'app/Filter') diff --git a/ChangeLog b/ChangeLog index 941c46c9..1bbe8062 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ Version 1.0.28 (unreleased) New features: * Search in comments +* Search by task creator Improvements: diff --git a/app/Filter/TaskCreatorFilter.php b/app/Filter/TaskCreatorFilter.php new file mode 100644 index 00000000..af35e6bc --- /dev/null +++ b/app/Filter/TaskCreatorFilter.php @@ -0,0 +1,74 @@ +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 (is_int($this->value) || ctype_digit($this->value)) { + $this->query->eq(Task::TABLE.'.creator_id', $this->value); + } else { + switch ($this->value) { + case 'me': + $this->query->eq(Task::TABLE.'.creator_id', $this->currentUserId); + break; + case 'nobody': + $this->query->eq(Task::TABLE.'.creator_id', 0); + break; + default: + $this->query->beginOr(); + $this->query->ilike('uc.username', '%'.$this->value.'%'); + $this->query->ilike('uc.name', '%'.$this->value.'%'); + $this->query->closeOr(); + } + } + } +} diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 1840b505..d406b794 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -138,6 +138,7 @@ class TaskFinder extends Base Project::TABLE.'.name AS project_name' ) ->join(User::TABLE, 'id', 'owner_id', Task::TABLE) + ->left(User::TABLE, 'uc', 'id', Task::TABLE, 'creator_id') ->join(Category::TABLE, 'id', 'category_id', Task::TABLE) ->join(Column::TABLE, 'id', 'column_id', Task::TABLE) ->join(Swimlane::TABLE, 'id', 'swimlane_id', Task::TABLE) diff --git a/app/ServiceProvider/FilterProvider.php b/app/ServiceProvider/FilterProvider.php index 66608b8c..3100ae7e 100644 --- a/app/ServiceProvider/FilterProvider.php +++ b/app/ServiceProvider/FilterProvider.php @@ -10,6 +10,7 @@ use Kanboard\Filter\TaskColorFilter; use Kanboard\Filter\TaskColumnFilter; use Kanboard\Filter\TaskCommentFilter; use Kanboard\Filter\TaskCreationDateFilter; +use Kanboard\Filter\TaskCreatorFilter; use Kanboard\Filter\TaskDescriptionFilter; use Kanboard\Filter\TaskDueDateFilter; use Kanboard\Filter\TaskIdFilter; @@ -84,10 +85,15 @@ class FilterProvider implements ServiceProviderInterface ->setCurrentUserId($c['userSession']->getId()) ) ->withFilter(new TaskCategoryFilter()) - ->withFilter(TaskColorFilter::getInstance()->setColorModel($c['color'])) + ->withFilter(TaskColorFilter::getInstance() + ->setColorModel($c['color']) + ) ->withFilter(new TaskColumnFilter()) ->withFilter(new TaskCommentFilter()) ->withFilter(new TaskCreationDateFilter()) + ->withFilter(TaskCreatorFilter::getInstance() + ->setCurrentUserId($c['userSession']->getId()) + ) ->withFilter(new TaskDescriptionFilter()) ->withFilter(new TaskDueDateFilter()) ->withFilter(new TaskIdFilter()) diff --git a/doc/search.markdown b/doc/search.markdown index 93c8214e..f6d343e9 100644 --- a/doc/search.markdown +++ b/doc/search.markdown @@ -27,8 +27,8 @@ Attribute: **status** - Query to find open tasks: `status:open` - Query to find closed tasks: `status:closed` -Search by assignees -------------------- +Search by assignee +------------------ Attribute: **assignee** @@ -38,6 +38,15 @@ Attribute: **assignee** - Query for unassigned tasks: `assignee:nobody` - Query for my assigned tasks: `assignee:me` +Search by task creator +---------------------- + +Attribute: **creator** + +- Tasks created by myself: `creator:me` +- Tasks created by John Doe: `creator:"John Doe"` +- Tasks created by the user id #1: `creator:1` + Search by subtask assignee -------------------------- diff --git a/tests/units/Filter/TaskCreatorFilterTest.php b/tests/units/Filter/TaskCreatorFilterTest.php new file mode 100644 index 00000000..1c344de7 --- /dev/null +++ b/tests/units/Filter/TaskCreatorFilterTest.php @@ -0,0 +1,159 @@ +container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $query = $taskFinder->getExtendedQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'creator_id' => 1))); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue(1); + $filter->apply(); + + $this->assertCount(1, $query->findAll()); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue(123); + $filter->apply(); + + $this->assertCount(0, $query->findAll()); + } + + public function testWithStringAssigneeId() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $query = $taskFinder->getExtendedQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'creator_id' => 1))); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue('1'); + $filter->apply(); + + $this->assertCount(1, $query->findAll()); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue("123"); + $filter->apply(); + + $this->assertCount(0, $query->findAll()); + } + + public function testWithUsername() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $query = $taskFinder->getExtendedQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'creator_id' => 1))); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue('admin'); + $filter->apply(); + + $this->assertCount(1, $query->findAll()); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue('foobar'); + $filter->apply(); + + $this->assertCount(0, $query->findAll()); + } + + public function testWithName() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $userModel = new User($this->container); + $query = $taskFinder->getExtendedQuery(); + + $this->assertEquals(2, $userModel->create(array('username' => 'foobar', 'name' => 'Foo Bar'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'creator_id' => 2))); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue('foo bar'); + $filter->apply(); + + $this->assertCount(1, $query->findAll()); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue('bob'); + $filter->apply(); + + $this->assertCount(0, $query->findAll()); + } + + public function testWithNobody() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $query = $taskFinder->getExtendedQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + + $filter = new TaskCreatorFilter(); + $filter->withQuery($query); + $filter->withValue('nobody'); + $filter->apply(); + + $this->assertCount(1, $query->findAll()); + } + + public function testWithCurrentUser() + { + $taskFinder = new TaskFinder($this->container); + $taskCreation = new TaskCreation($this->container); + $projectModel = new Project($this->container); + $query = $taskFinder->getExtendedQuery(); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'creator_id' => 1))); + + $filter = new TaskCreatorFilter(); + $filter->setCurrentUserId(1); + $filter->withQuery($query); + $filter->withValue('me'); + $filter->apply(); + + $this->assertCount(1, $query->findAll()); + + $filter = new TaskCreatorFilter(); + $filter->setCurrentUserId(2); + $filter->withQuery($query); + $filter->withValue('me'); + $filter->apply(); + + $this->assertCount(0, $query->findAll()); + } +} -- cgit v1.2.3 From 2eadfb22912d94e76a479b694070735fbb0298f1 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 10 Apr 2016 12:13:42 -0400 Subject: Refactor ProjectActivity model to use Filter and Formatter interface --- app/Api/Me.php | 2 +- app/Api/Project.php | 4 +- app/Controller/Activity.php | 4 +- app/Controller/App.php | 2 +- app/Controller/Feed.php | 4 +- app/Controller/ProjectOverview.php | 2 +- app/Core/Base.php | 2 + app/Core/Helper.php | 1 + app/Filter/ProjectActivityProjectIdFilter.php | 38 ++++++ app/Filter/ProjectActivityProjectIdsFilter.php | 43 ++++++ app/Filter/ProjectActivityTaskIdFilter.php | 38 ++++++ app/Filter/ProjectActivityTaskTitleFilter.php | 38 ++++++ app/Formatter/ProjectActivityEventFormatter.php | 61 +++++++++ app/Helper/ProjectActivityHelper.php | 78 +++++++++++ app/Model/ProjectActivity.php | 151 ++------------------- app/ServiceProvider/FilterProvider.php | 42 +++++- app/ServiceProvider/HelperProvider.php | 1 + .../Filter/ProjectActivityProjectIdFilterTest.php | 35 +++++ .../Filter/ProjectActivityProjectIdsFilterTest.php | 63 +++++++++ .../Filter/ProjectActivityTaskIdFilterTest.php | 34 +++++ .../Filter/ProjectActivityTaskTitleFilterTest.php | 34 +++++ tests/units/Helper/ProjectActivityHelperTest.php | 97 +++++++++++++ tests/units/Model/ProjectActivityTest.php | 83 +++-------- 23 files changed, 647 insertions(+), 210 deletions(-) create mode 100644 app/Filter/ProjectActivityProjectIdFilter.php create mode 100644 app/Filter/ProjectActivityProjectIdsFilter.php create mode 100644 app/Filter/ProjectActivityTaskIdFilter.php create mode 100644 app/Filter/ProjectActivityTaskTitleFilter.php create mode 100644 app/Formatter/ProjectActivityEventFormatter.php create mode 100644 app/Helper/ProjectActivityHelper.php create mode 100644 tests/units/Filter/ProjectActivityProjectIdFilterTest.php create mode 100644 tests/units/Filter/ProjectActivityProjectIdsFilterTest.php create mode 100644 tests/units/Filter/ProjectActivityTaskIdFilterTest.php create mode 100644 tests/units/Filter/ProjectActivityTaskTitleFilterTest.php create mode 100644 tests/units/Helper/ProjectActivityHelperTest.php (limited to 'app/Filter') diff --git a/app/Api/Me.php b/app/Api/Me.php index ccc809ed..3d08626a 100644 --- a/app/Api/Me.php +++ b/app/Api/Me.php @@ -33,7 +33,7 @@ class Me extends Base public function getMyActivityStream() { $project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId()); - return $this->projectActivity->getProjects($project_ids, 100); + return $this->helper->projectActivity->getProjectsEvents($project_ids, 100); } public function createMyPrivateProject($name, $description = null) diff --git a/app/Api/Project.php b/app/Api/Project.php index 8e311f7f..846d7046 100644 --- a/app/Api/Project.php +++ b/app/Api/Project.php @@ -53,13 +53,13 @@ class Project extends Base public function getProjectActivities(array $project_ids) { - return $this->projectActivity->getProjects($project_ids); + return $this->helper->projectActivity->getProjectsEvents($project_ids); } public function getProjectActivity($project_id) { $this->checkProjectPermission($project_id); - return $this->projectActivity->getProject($project_id); + return $this->helper->projectActivity->getProjectEvents($project_id); } public function createProject($name, $description = null) diff --git a/app/Controller/Activity.php b/app/Controller/Activity.php index e455b1da..47a66e0a 100644 --- a/app/Controller/Activity.php +++ b/app/Controller/Activity.php @@ -20,7 +20,7 @@ class Activity extends Base $project = $this->getProject(); $this->response->html($this->helper->layout->app('activity/project', array( - 'events' => $this->projectActivity->getProject($project['id']), + 'events' => $this->helper->projectActivity->getProjectEvents($project['id']), 'project' => $project, 'title' => t('%s\'s activity', $project['name']) ))); @@ -39,7 +39,7 @@ class Activity extends Base 'title' => $task['title'], 'task' => $task, 'project' => $this->project->getById($task['project_id']), - 'events' => $this->projectActivity->getTask($task['id']), + 'events' => $this->helper->projectActivity->getTaskEvents($task['id']), ))); } } diff --git a/app/Controller/App.php b/app/Controller/App.php index df1d3c90..01f733ff 100644 --- a/app/Controller/App.php +++ b/app/Controller/App.php @@ -157,7 +157,7 @@ class App extends Base $this->response->html($this->helper->layout->dashboard('app/activity', array( 'title' => t('My activity stream'), - 'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id']), 100), + 'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermission->getActiveProjectIds($user['id']), 100), 'user' => $user, ))); } diff --git a/app/Controller/Feed.php b/app/Controller/Feed.php index 8457c383..f8b3d320 100644 --- a/app/Controller/Feed.php +++ b/app/Controller/Feed.php @@ -26,7 +26,7 @@ class Feed extends Base } $this->response->xml($this->template->render('feed/user', array( - 'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id'])), + 'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermission->getActiveProjectIds($user['id'])), 'user' => $user, ))); } @@ -47,7 +47,7 @@ class Feed extends Base } $this->response->xml($this->template->render('feed/project', array( - 'events' => $this->projectActivity->getProject($project['id']), + 'events' => $this->helper->projectActivity->getProjectEvents($project['id']), 'project' => $project, ))); } diff --git a/app/Controller/ProjectOverview.php b/app/Controller/ProjectOverview.php index 04645804..b2bb33d6 100644 --- a/app/Controller/ProjectOverview.php +++ b/app/Controller/ProjectOverview.php @@ -24,7 +24,7 @@ class ProjectOverview extends Base 'description' => $this->helper->projectHeader->getDescription($project), 'users' => $this->projectUserRole->getAllUsersGroupedByRole($project['id']), 'roles' => $this->role->getProjectRoles(), - 'events' => $this->projectActivity->getProject($project['id'], 10), + 'events' => $this->helper->projectActivity->getProjectEvents($project['id'], 10), 'images' => $this->projectFile->getAllImages($project['id']), 'files' => $this->projectFile->getAllDocuments($project['id']), ))); diff --git a/app/Core/Base.php b/app/Core/Base.php index 8c6b7620..2b619af5 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -129,10 +129,12 @@ use Pimple\Container; * @property \Kanboard\Export\TransitionExport $transitionExport * @property \Kanboard\Core\Filter\QueryBuilder $projectGroupRoleQuery * @property \Kanboard\Core\Filter\QueryBuilder $projectUserRoleQuery + * @property \Kanboard\Core\Filter\QueryBuilder $projectActivityQuery * @property \Kanboard\Core\Filter\QueryBuilder $userQuery * @property \Kanboard\Core\Filter\QueryBuilder $projectQuery * @property \Kanboard\Core\Filter\QueryBuilder $taskQuery * @property \Kanboard\Core\Filter\LexerBuilder $taskLexer + * @property \Kanboard\Core\Filter\LexerBuilder $projectActivityLexer * @property \Psr\Log\LoggerInterface $logger * @property \PicoDb\Database $db * @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher diff --git a/app/Core/Helper.php b/app/Core/Helper.php index ab1f8f76..66f8d429 100644 --- a/app/Core/Helper.php +++ b/app/Core/Helper.php @@ -26,6 +26,7 @@ use Pimple\Container; * @property \Kanboard\Helper\UserHelper $user * @property \Kanboard\Helper\LayoutHelper $layout * @property \Kanboard\Helper\ProjectHeaderHelper $projectHeader + * @property \Kanboard\Helper\ProjectActivityHelper $projectActivity */ class Helper { diff --git a/app/Filter/ProjectActivityProjectIdFilter.php b/app/Filter/ProjectActivityProjectIdFilter.php new file mode 100644 index 00000000..bb4d8bd1 --- /dev/null +++ b/app/Filter/ProjectActivityProjectIdFilter.php @@ -0,0 +1,38 @@ +query->eq(ProjectActivity::TABLE.'.project_id', $this->value); + return $this; + } +} diff --git a/app/Filter/ProjectActivityProjectIdsFilter.php b/app/Filter/ProjectActivityProjectIdsFilter.php new file mode 100644 index 00000000..4d7c9028 --- /dev/null +++ b/app/Filter/ProjectActivityProjectIdsFilter.php @@ -0,0 +1,43 @@ +value)) { + $this->query->eq(ProjectActivity::TABLE.'.project_id', 0); + } else { + $this->query->in(ProjectActivity::TABLE.'.project_id', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/ProjectActivityTaskIdFilter.php b/app/Filter/ProjectActivityTaskIdFilter.php new file mode 100644 index 00000000..e99efe09 --- /dev/null +++ b/app/Filter/ProjectActivityTaskIdFilter.php @@ -0,0 +1,38 @@ +query->eq(ProjectActivity::TABLE.'.task_id', $this->value); + return $this; + } +} diff --git a/app/Filter/ProjectActivityTaskTitleFilter.php b/app/Filter/ProjectActivityTaskTitleFilter.php new file mode 100644 index 00000000..ed3f36d6 --- /dev/null +++ b/app/Filter/ProjectActivityTaskTitleFilter.php @@ -0,0 +1,38 @@ +query->ilike(Task::TABLE.'.title', '%'.$this->value.'%'); + return $this; + } +} diff --git a/app/Formatter/ProjectActivityEventFormatter.php b/app/Formatter/ProjectActivityEventFormatter.php new file mode 100644 index 00000000..ae80e5e7 --- /dev/null +++ b/app/Formatter/ProjectActivityEventFormatter.php @@ -0,0 +1,61 @@ +query->findAll(); + + foreach ($events as &$event) { + $event += $this->unserializeEvent($event['data']); + unset($event['data']); + + $event['author'] = $event['author_name'] ?: $event['author_username']; + $event['event_title'] = $this->notification->getTitleWithAuthor($event['author'], $event['event_name'], $event); + $event['event_content'] = $this->renderEvent($event); + } + + return $events; + } + + /** + * Decode event data, supports unserialize() and json_decode() + * + * @access protected + * @param string $data Serialized data + * @return array + */ + protected function unserializeEvent($data) + { + if ($data{0} === 'a') { + return unserialize($data); + } + + return json_decode($data, true) ?: array(); + } + + /** + * Get the event html content + * + * @access protected + * @param array $params Event properties + * @return string + */ + protected function renderEvent(array $params) + { + return $this->template->render( + 'event/'.str_replace('.', '_', $params['event_name']), + $params + ); + } +} diff --git a/app/Helper/ProjectActivityHelper.php b/app/Helper/ProjectActivityHelper.php new file mode 100644 index 00000000..738fec66 --- /dev/null +++ b/app/Helper/ProjectActivityHelper.php @@ -0,0 +1,78 @@ +projectActivityQuery + ->withFilter(new ProjectActivityProjectIdFilter($project_id)); + + $queryBuilder->getQuery() + ->desc(ProjectActivity::TABLE.'.id') + ->limit($limit) + ; + + return $queryBuilder->format(new ProjectActivityEventFormatter($this->container)); + } + + /** + * Get projects activity events + * + * @access public + * @param int[] $project_ids + * @param int $limit + * @return array + */ + public function getProjectsEvents(array $project_ids, $limit = 50) + { + $queryBuilder = $this->projectActivityQuery + ->withFilter(new ProjectActivityProjectIdsFilter($project_ids)); + + $queryBuilder->getQuery() + ->desc(ProjectActivity::TABLE.'.id') + ->limit($limit) + ; + + return $queryBuilder->format(new ProjectActivityEventFormatter($this->container)); + } + + /** + * Get task activity events + * + * @access public + * @param integer $task_id + * @return array + */ + public function getTaskEvents($task_id) + { + $queryBuilder = $this->projectActivityQuery + ->withFilter(new ProjectActivityTaskIdFilter($task_id)); + + $queryBuilder->getQuery()->desc(ProjectActivity::TABLE.'.id'); + + return $queryBuilder->format(new ProjectActivityEventFormatter($this->container)); + } +} diff --git a/app/Model/ProjectActivity.php b/app/Model/ProjectActivity.php index 34893f0b..31cee113 100644 --- a/app/Model/ProjectActivity.php +++ b/app/Model/ProjectActivity.php @@ -53,115 +53,25 @@ class ProjectActivity extends Base } /** - * Get all events for the given project + * Get query * * @access public - * @param integer $project_id Project id - * @param integer $limit Maximum events number - * @param integer $start Timestamp of earliest activity - * @param integer $end Timestamp of latest activity - * @return array - */ - public function getProject($project_id, $limit = 50, $start = null, $end = null) - { - return $this->getProjects(array($project_id), $limit, $start, $end); - } - - /** - * Get all events for the given projects list - * - * @access public - * @param integer[] $project_ids Projects id - * @param integer $limit Maximum events number - * @param integer $start Timestamp of earliest activity - * @param integer $end Timestamp of latest activity - * @return array - */ - public function getProjects(array $project_ids, $limit = 50, $start = null, $end = null) - { - if (empty($project_ids)) { - return array(); - } - - $query = $this - ->db - ->table(self::TABLE) - ->columns( - self::TABLE.'.*', - User::TABLE.'.username AS author_username', - User::TABLE.'.name AS author_name', - User::TABLE.'.email', - User::TABLE.'.avatar_path' - ) - ->in('project_id', $project_ids) - ->join(User::TABLE, 'id', 'creator_id') - ->desc(self::TABLE.'.id') - ->limit($limit); - - return $this->getEvents($query, $start, $end); - } - - /** - * Get all events for the given task - * - * @access public - * @param integer $task_id Task id - * @param integer $limit Maximum events number - * @param integer $start Timestamp of earliest activity - * @param integer $end Timestamp of latest activity - * @return array - */ - public function getTask($task_id, $limit = 50, $start = null, $end = null) - { - $query = $this - ->db - ->table(self::TABLE) - ->columns( - self::TABLE.'.*', - User::TABLE.'.username AS author_username', - User::TABLE.'.name AS author_name', - User::TABLE.'.email', - User::TABLE.'.avatar_path' - ) - ->eq('task_id', $task_id) - ->join(User::TABLE, 'id', 'creator_id') - ->desc(self::TABLE.'.id') - ->limit($limit); - - return $this->getEvents($query, $start, $end); - } - - /** - * Common function to return events - * - * @access public - * @param Table $query PicoDb Query - * @param integer $start Timestamp of earliest activity - * @param integer $end Timestamp of latest activity - * @return array + * @return Table */ - private function getEvents(Table $query, $start, $end) + public function getQuery() { - if (! is_null($start)) { - $query->gte('date_creation', $start); - } - - if (! is_null($end)) { - $query->lte('date_creation', $end); - } - - $events = $query->findAll(); - - foreach ($events as &$event) { - $event += $this->decode($event['data']); - unset($event['data']); - - $event['author'] = $event['author_name'] ?: $event['author_username']; - $event['event_title'] = $this->notification->getTitleWithAuthor($event['author'], $event['event_name'], $event); - $event['event_content'] = $this->getContent($event); - } - - return $events; + return $this + ->db + ->table(ProjectActivity::TABLE) + ->columns( + ProjectActivity::TABLE.'.*', + 'uc.username AS author_username', + 'uc.name AS author_name', + 'uc.email', + 'uc.avatar_path' + ) + ->join(Task::TABLE, 'id', 'task_id') + ->left(User::TABLE, 'uc', 'id', ProjectActivity::TABLE, 'creator_id'); } /** @@ -179,35 +89,4 @@ class ProjectActivity extends Base $this->db->table(self::TABLE)->in('id', $ids)->remove(); } } - - /** - * Get the event html content - * - * @access public - * @param array $params Event properties - * @return string - */ - public function getContent(array $params) - { - return $this->template->render( - 'event/'.str_replace('.', '_', $params['event_name']), - $params - ); - } - - /** - * Decode event data, supports unserialize() and json_decode() - * - * @access public - * @param string $data Serialized data - * @return array - */ - public function decode($data) - { - if ($data{0} === 'a') { - return unserialize($data); - } - - return json_decode($data, true) ?: array(); - } } diff --git a/app/ServiceProvider/FilterProvider.php b/app/ServiceProvider/FilterProvider.php index 3100ae7e..4b4dbd2d 100644 --- a/app/ServiceProvider/FilterProvider.php +++ b/app/ServiceProvider/FilterProvider.php @@ -4,6 +4,7 @@ namespace Kanboard\ServiceProvider; use Kanboard\Core\Filter\LexerBuilder; use Kanboard\Core\Filter\QueryBuilder; +use Kanboard\Filter\ProjectActivityTaskTitleFilter; use Kanboard\Filter\TaskAssigneeFilter; use Kanboard\Filter\TaskCategoryFilter; use Kanboard\Filter\TaskColorFilter; @@ -45,6 +46,25 @@ class FilterProvider implements ServiceProviderInterface * @return \Pimple\Container */ public function register(Container $container) + { + $this->createUserFilter($container); + $this->createProjectFilter($container); + $this->createTaskFilter($container); + return $container; + } + + public function createUserFilter(Container $container) + { + $container['userQuery'] = $container->factory(function ($c) { + $builder = new QueryBuilder(); + $builder->withQuery($c['db']->table(User::TABLE)); + return $builder; + }); + + return $container; + } + + public function createProjectFilter(Container $container) { $container['projectGroupRoleQuery'] = $container->factory(function ($c) { $builder = new QueryBuilder(); @@ -58,18 +78,32 @@ class FilterProvider implements ServiceProviderInterface return $builder; }); - $container['userQuery'] = $container->factory(function ($c) { + $container['projectQuery'] = $container->factory(function ($c) { $builder = new QueryBuilder(); - $builder->withQuery($c['db']->table(User::TABLE)); + $builder->withQuery($c['db']->table(Project::TABLE)); return $builder; }); - $container['projectQuery'] = $container->factory(function ($c) { + $container['projectActivityLexer'] = $container->factory(function ($c) { + $builder = new LexerBuilder(); + $builder->withQuery($c['projectActivity']->getQuery()); + $builder->withFilter(new ProjectActivityTaskTitleFilter()); + + return $builder; + }); + + $container['projectActivityQuery'] = $container->factory(function ($c) { $builder = new QueryBuilder(); - $builder->withQuery($c['db']->table(Project::TABLE)); + $builder->withQuery($c['projectActivity']->getQuery()); + return $builder; }); + return $container; + } + + public function createTaskFilter(Container $container) + { $container['taskQuery'] = $container->factory(function ($c) { $builder = new QueryBuilder(); $builder->withQuery($c['taskFinder']->getExtendedQuery()); diff --git a/app/ServiceProvider/HelperProvider.php b/app/ServiceProvider/HelperProvider.php index 3590afa5..bf3956a2 100644 --- a/app/ServiceProvider/HelperProvider.php +++ b/app/ServiceProvider/HelperProvider.php @@ -30,6 +30,7 @@ class HelperProvider implements ServiceProviderInterface $container['helper']->register('user', '\Kanboard\Helper\UserHelper'); $container['helper']->register('avatar', '\Kanboard\Helper\AvatarHelper'); $container['helper']->register('projectHeader', '\Kanboard\Helper\ProjectHeaderHelper'); + $container['helper']->register('projectActivity', '\Kanboard\Helper\ProjectActivityHelper'); $container['template'] = new Template($container['helper']); diff --git a/tests/units/Filter/ProjectActivityProjectIdFilterTest.php b/tests/units/Filter/ProjectActivityProjectIdFilterTest.php new file mode 100644 index 00000000..193852e1 --- /dev/null +++ b/tests/units/Filter/ProjectActivityProjectIdFilterTest.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 ProjectActivityProjectIdFilter(1); + $filter->withQuery($query)->apply(); + $this->assertCount(1, $query->findAll()); + } +} diff --git a/tests/units/Filter/ProjectActivityProjectIdsFilterTest.php b/tests/units/Filter/ProjectActivityProjectIdsFilterTest.php new file mode 100644 index 00000000..e99d2e2f --- /dev/null +++ b/tests/units/Filter/ProjectActivityProjectIdsFilterTest.php @@ -0,0 +1,63 @@ +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(3, $projectModel->create(array('name' => 'P3'))); + + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(2, $taskCreation->create(array('title' => 'Test', 'project_id' => 2))); + $this->assertEquals(3, $taskCreation->create(array('title' => 'Test', 'project_id' => 3))); + + $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)))); + $this->assertNotFalse($projectActivityModel->createEvent(3, 3, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(3)))); + + $filter = new ProjectActivityProjectIdsFilter(array(1, 2)); + $filter->withQuery($query)->apply(); + $this->assertCount(2, $query->findAll()); + } + + public function testWithEmptyArgument() + { + $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(2, $projectModel->create(array('name' => 'P2'))); + $this->assertEquals(3, $projectModel->create(array('name' => 'P3'))); + + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(2, $taskCreation->create(array('title' => 'Test', 'project_id' => 2))); + $this->assertEquals(3, $taskCreation->create(array('title' => 'Test', 'project_id' => 3))); + + $this->assertNotFalse($projectActivityModel->createEvent(1, 1, 1, Task::EVENT_CREATE, $taskFinder->getById(1))); + $this->assertNotFalse($projectActivityModel->createEvent(2, 2, 1, Task::EVENT_CREATE, $taskFinder->getById(2))); + $this->assertNotFalse($projectActivityModel->createEvent(3, 3, 1, Task::EVENT_CREATE, $taskFinder->getById(3))); + + $filter = new ProjectActivityProjectIdsFilter(array()); + $filter->withQuery($query)->apply(); + $this->assertCount(0, $query->findAll()); + } +} diff --git a/tests/units/Filter/ProjectActivityTaskIdFilterTest.php b/tests/units/Filter/ProjectActivityTaskIdFilterTest.php new file mode 100644 index 00000000..646cab1b --- /dev/null +++ b/tests/units/Filter/ProjectActivityTaskIdFilterTest.php @@ -0,0 +1,34 @@ +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' => '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)))); + + $filter = new ProjectActivityTaskIdFilter(1); + $filter->withQuery($query)->apply(); + $this->assertCount(1, $query->findAll()); + } +} diff --git a/tests/units/Filter/ProjectActivityTaskTitleFilterTest.php b/tests/units/Filter/ProjectActivityTaskTitleFilterTest.php new file mode 100644 index 00000000..6a7c23af --- /dev/null +++ b/tests/units/Filter/ProjectActivityTaskTitleFilterTest.php @@ -0,0 +1,34 @@ +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('test2'); + $filter->withQuery($query)->apply(); + $this->assertCount(1, $query->findAll()); + } +} diff --git a/tests/units/Helper/ProjectActivityHelperTest.php b/tests/units/Helper/ProjectActivityHelperTest.php new file mode 100644 index 00000000..88b2d352 --- /dev/null +++ b/tests/units/Helper/ProjectActivityHelperTest.php @@ -0,0 +1,97 @@ +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->assertEquals(2, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(3, $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->assertNotFalse($projectActivityModel->createEvent(1, 3, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(3)))); + + $helper = new ProjectActivityHelper($this->container); + $events = $helper->getProjectEvents(1); + + $this->assertCount(3, $events); + $this->assertEquals(3, $events[0]['task_id']); + $this->assertNotEmpty($events[0]['event_content']); + $this->assertNotEmpty($events[0]['event_title']); + $this->assertNotEmpty($events[0]['author']); + $this->assertInternalType('array', $events[0]['task']); + } + + public function testGetProjectsEvents() + { + $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(2, $projectModel->create(array('name' => 'P2'))); + $this->assertEquals(3, $projectModel->create(array('name' => 'P3'))); + + $this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(2, $taskCreation->create(array('title' => 'Test', 'project_id' => 2))); + $this->assertEquals(3, $taskCreation->create(array('title' => 'Test', 'project_id' => 3))); + + $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)))); + $this->assertNotFalse($projectActivityModel->createEvent(3, 3, 1, Task::EVENT_CREATE, array('task' => $taskFinder->getById(3)))); + + $helper = new ProjectActivityHelper($this->container); + $events = $helper->getProjectsEvents(array(1, 2)); + + $this->assertCount(2, $events); + $this->assertEquals(2, $events[0]['task_id']); + $this->assertNotEmpty($events[0]['event_content']); + $this->assertNotEmpty($events[0]['event_title']); + $this->assertNotEmpty($events[0]['author']); + $this->assertInternalType('array', $events[0]['task']); + } + + public function testGetTaskEvents() + { + $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->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)))); + + $helper = new ProjectActivityHelper($this->container); + $events = $helper->getTaskEvents(1); + + $this->assertCount(1, $events); + $this->assertEquals(1, $events[0]['task_id']); + $this->assertNotEmpty($events[0]['event_content']); + $this->assertNotEmpty($events[0]['event_title']); + $this->assertNotEmpty($events[0]['author']); + $this->assertInternalType('array', $events[0]['task']); + } +} diff --git a/tests/units/Model/ProjectActivityTest.php b/tests/units/Model/ProjectActivityTest.php index 27ea039d..a624cd86 100644 --- a/tests/units/Model/ProjectActivityTest.php +++ b/tests/units/Model/ProjectActivityTest.php @@ -10,90 +10,51 @@ use Kanboard\Model\Project; class ProjectActivityTest extends Base { - public function testDecode() - { - $e = new ProjectActivity($this->container); - $input = array('test'); - $serialized = serialize($input); - $json = json_encode($input); - - $this->assertEquals($input, $e->decode($serialized)); - $this->assertEquals($input, $e->decode($json)); - } - public function testCreation() { - $e = new ProjectActivity($this->container); - $tc = new TaskCreation($this->container); - $tf = new TaskFinder($this->container); - $p = new Project($this->container); + $projectActivity = new ProjectActivity($this->container); + $taskCreation = new TaskCreation($this->container); + $taskFinder = new TaskFinder($this->container); + $projectModel = new Project($this->container); - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1))); - $this->assertEquals(2, $tc->create(array('title' => 'Task #2', 'project_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Task #1', 'project_id' => 1))); + $this->assertEquals(2, $taskCreation->create(array('title' => 'Task #2', 'project_id' => 1))); - $this->assertTrue($e->createEvent(1, 1, 1, Task::EVENT_CLOSE, array('task' => $tf->getbyId(1)))); - $this->assertTrue($e->createEvent(1, 2, 1, Task::EVENT_UPDATE, array('task' => $tf->getById(2)))); - $this->assertFalse($e->createEvent(1, 1, 0, Task::EVENT_OPEN, array('task' => $tf->getbyId(1)))); + $this->assertTrue($projectActivity->createEvent(1, 1, 1, Task::EVENT_CLOSE, array('task' => $taskFinder->getbyId(1)))); + $this->assertTrue($projectActivity->createEvent(1, 2, 1, Task::EVENT_UPDATE, array('task' => $taskFinder->getById(2)))); + $this->assertFalse($projectActivity->createEvent(1, 1, 0, Task::EVENT_OPEN, array('task' => $taskFinder->getbyId(1)))); - $events = $e->getProject(1); + $events = $projectActivity->getQuery()->desc('id')->findAll(); - $this->assertNotEmpty($events); - $this->assertTrue(is_array($events)); - $this->assertEquals(2, count($events)); + $this->assertCount(2, $events); $this->assertEquals(time(), $events[0]['date_creation'], '', 1); $this->assertEquals(Task::EVENT_UPDATE, $events[0]['event_name']); $this->assertEquals(Task::EVENT_CLOSE, $events[1]['event_name']); } - public function testFetchAllContent() - { - $e = new ProjectActivity($this->container); - $tc = new TaskCreation($this->container); - $tf = new TaskFinder($this->container); - $p = new Project($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1))); - - $nb_events = 80; - - for ($i = 0; $i < $nb_events; $i++) { - $this->assertTrue($e->createEvent(1, 1, 1, Task::EVENT_UPDATE, array('task' => $tf->getbyId(1)))); - } - - $events = $e->getProject(1); - - $this->assertNotEmpty($events); - $this->assertTrue(is_array($events)); - $this->assertEquals(50, count($events)); - $this->assertEquals('admin', $events[0]['author']); - $this->assertNotEmpty($events[0]['event_title']); - $this->assertNotEmpty($events[0]['event_content']); - } - public function testCleanup() { - $e = new ProjectActivity($this->container); - $tc = new TaskCreation($this->container); - $tf = new TaskFinder($this->container); - $p = new Project($this->container); + $projectActivity = new ProjectActivity($this->container); + $taskCreation = new TaskCreation($this->container); + $taskFinder = new TaskFinder($this->container); + $projectModel = new Project($this->container); - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $taskCreation->create(array('title' => 'Task #1', 'project_id' => 1))); $max = 15; $nb_events = 100; - $task = $tf->getbyId(1); + $task = $taskFinder->getbyId(1); for ($i = 0; $i < $nb_events; $i++) { - $this->assertTrue($e->createEvent(1, 1, 1, Task::EVENT_CLOSE, array('task' => $task))); + $this->assertTrue($projectActivity->createEvent(1, 1, 1, Task::EVENT_CLOSE, array('task' => $task))); } $this->assertEquals($nb_events, $this->container['db']->table('project_activities')->count()); - $e->cleanup($max); + $projectActivity->cleanup($max); - $events = $e->getProject(1); + $events = $projectActivity->getQuery()->desc('id')->findAll(); $this->assertNotEmpty($events); $this->assertCount($max, $events); -- cgit v1.2.3 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 (limited to 'app/Filter') 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