From 07eb334c2546294acea43e85308bec907b13467c Mon Sep 17 00:00:00 2001
From: Douglas Barbosa Alexandre <dbalexandre@gmail.com>
Date: Thu, 18 Feb 2016 01:20:31 -0200
Subject: [PATCH] Add filters by project, author, type, and action to task
 queue page list

---
 app/controllers/dashboard/tasks_controller.rb |   8 +-
 app/finders/tasks_finder.rb                   | 129 ++++++++++++++++++
 app/helpers/tasks_helper.rb                   |  33 +++++
 app/views/dashboard/tasks/index.html.haml     |  45 +++++-
 features/dashboard/task_queue.feature         |  23 ++++
 features/steps/dashboard/task_queue.rb        | 116 ++++++++++++----
 6 files changed, 317 insertions(+), 37 deletions(-)
 create mode 100644 app/finders/tasks_finder.rb

diff --git a/app/controllers/dashboard/tasks_controller.rb b/app/controllers/dashboard/tasks_controller.rb
index e3245609204..a8884be54e4 100644
--- a/app/controllers/dashboard/tasks_controller.rb
+++ b/app/controllers/dashboard/tasks_controller.rb
@@ -1,12 +1,6 @@
 class Dashboard::TasksController < Dashboard::ApplicationController
   def index
-    @tasks = case params[:state]
-             when 'done'
-               current_user.tasks.done
-             else
-               current_user.tasks.pending
-             end
-
+    @tasks = TasksFinder.new(current_user, params).execute
     @tasks = @tasks.page(params[:page]).per(PER_PAGE)
   end
 
diff --git a/app/finders/tasks_finder.rb b/app/finders/tasks_finder.rb
new file mode 100644
index 00000000000..2a32e977c24
--- /dev/null
+++ b/app/finders/tasks_finder.rb
@@ -0,0 +1,129 @@
+# TasksFinder
+#
+# Used to filter Tasks by set of params
+#
+# Arguments:
+#   current_user - which user use
+#   params:
+#     action_id: integer
+#     author_id: integer
+#     project_id; integer
+#     state: 'pending' or 'done'
+#     type: 'Issue' or 'MergeRequest'
+#
+
+class TasksFinder
+  NONE = '0'
+
+  attr_accessor :current_user, :params
+
+  def initialize(current_user, params)
+    @current_user = current_user
+    @params = params
+  end
+
+  def execute
+    items = current_user.tasks
+    items = by_action_id(items)
+    items = by_author(items)
+    items = by_project(items)
+    items = by_state(items)
+    items = by_type(items)
+
+    items
+  end
+
+  private
+
+  def action_id?
+    action_id.present? && [Task::ASSIGNED, Task::MENTIONED].include?(action_id.to_i)
+  end
+
+  def action_id
+    params[:action_id]
+  end
+
+  def author?
+    params[:author_id].present?
+  end
+
+  def author
+    return @author if defined?(@author)
+
+    @author =
+      if author? && params[:author_id] != NONE
+        User.find(params[:author_id])
+      else
+        nil
+      end
+  end
+
+  def project?
+    params[:project_id].present?
+  end
+
+  def project
+    return @project if defined?(@project)
+
+    if project?
+      @project = Project.find(params[:project_id])
+
+      unless Ability.abilities.allowed?(current_user, :read_project, @project)
+        @project = nil
+      end
+    else
+      @project = nil
+    end
+
+    @project
+  end
+
+  def type?
+    type.present? && ['Issue', 'MergeRequest'].include?(type)
+  end
+
+  def type
+    params[:type]
+  end
+
+  def by_action_id(items)
+    if action_id?
+      items = items.where(action: action_id)
+    end
+
+    items
+  end
+
+  def by_author(items)
+    if author?
+      items = items.where(author_id: author.try(:id))
+    end
+
+    items
+  end
+
+  def by_project(items)
+    if project?
+      items = items.where(project: project)
+    end
+
+    items
+  end
+
+  def by_state(items)
+    case params[:state]
+    when 'done'
+      items.done
+    else
+      items.pending
+    end
+  end
+
+  def by_type(items)
+    if type?
+      items = items.where(target_type: type)
+    end
+
+    items
+  end
+end
diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb
index 6975c1d1604..59c7d93e65e 100644
--- a/app/helpers/tasks_helper.rb
+++ b/app/helpers/tasks_helper.rb
@@ -38,4 +38,37 @@ module TasksHelper
     text = first_line_in_markdown(text, 150, options)
     sanitize(text, tags: %w(a img b pre code p span))
   end
+
+  def task_actions_options
+    actions = [
+      OpenStruct.new(id: '', title: 'Any Action'),
+      OpenStruct.new(id: Task::ASSIGNED, title: 'Assigned'),
+      OpenStruct.new(id: Task::MENTIONED, title: 'Mentioned')
+    ]
+
+    options_from_collection_for_select(actions, 'id', 'title', params[:action_id])
+  end
+
+  def task_projects_options
+    projects = current_user.authorized_projects.sorted_by_activity.non_archived
+    projects = projects.includes(:namespace)
+
+    projects = projects.map do |project|
+      OpenStruct.new(id: project.id, title: project.name_with_namespace)
+    end
+
+    projects.unshift(OpenStruct.new(id: '', title: 'Any Project'))
+
+    options_from_collection_for_select(projects, 'id', 'title', params[:project_id])
+  end
+
+  def task_types_options
+    types = [
+      OpenStruct.new(title: 'Any Type', name: ''),
+      OpenStruct.new(title: 'Issue', name: 'Issue'),
+      OpenStruct.new(title: 'Merge Request', name: 'MergeRequest')
+    ]
+
+    options_from_collection_for_select(types, 'name', 'title', params[:type])
+  end
 end
diff --git a/app/views/dashboard/tasks/index.html.haml b/app/views/dashboard/tasks/index.html.haml
index 2f582009288..16e7b4ae0c5 100644
--- a/app/views/dashboard/tasks/index.html.haml
+++ b/app/views/dashboard/tasks/index.html.haml
@@ -3,12 +3,37 @@
 
 .top-area
   %ul.nav-links
-    %li{class: ("active" if params[:state].blank? || params[:state] == 'pending')}
-      = link_to dashboard_tasks_path(state: 'pending') do
-        Tasks (#{tasks_pending_count})
-    %li{class: ("active" if params[:state] == 'done')}
-      = link_to dashboard_tasks_path(state: 'done') do
-        Done (#{tasks_done_count})
+    %li{class: ('active' if params[:state].blank? || params[:state] == 'pending')}
+      = link_to page_filter_path(state: 'pending') do
+        %span
+          Tasks
+        %span{class: 'badge'}
+          = tasks_pending_count
+    %li{class: ('active' if params[:state] == 'done')}
+      = link_to page_filter_path(state: 'done') do
+        %span
+          Done
+        %span{class: 'badge'}
+          = tasks_done_count
+
+.tasks-filters
+  .gray-content-block.second-block
+    = form_tag page_filter_path(without: [:assignee_id, :milestone_title, :label_name, :scope, :sort]), method: :get, class: 'filter-form' do
+      .filter-item.inline
+        = select_tag('project_id', task_projects_options,
+          class: 'select2 trigger-submit', include_blank: true,
+          data: {placeholder: 'Project'})
+      .filter-item.inline
+        = users_select_tag(:author_id, selected: params[:author_id],
+          placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
+      .filter-item.inline
+        = select_tag('type', task_types_options,
+          class: 'select2 trigger-submit', include_blank: true,
+          data: {placeholder: 'Type'})
+      .filter-item.inline.actions-filter
+        = select_tag('action_id', task_actions_options,
+          class: 'select2 trigger-submit', include_blank: true,
+          data: {placeholder: 'Action'})
 
 .tasks
   - if @tasks.any?
@@ -23,3 +48,11 @@
     = paginate @tasks, theme: "gitlab"
   - else
     .nothing-here-block No tasks to show
+
+:javascript
+  new UsersSelect();
+
+  $('form.filter-form').on('submit', function (event) {
+    event.preventDefault();
+    Turbolinks.visit(this.action + '&' + $(this).serialize());
+  });
diff --git a/features/dashboard/task_queue.feature b/features/dashboard/task_queue.feature
index 42b4a86e498..8972a148289 100644
--- a/features/dashboard/task_queue.feature
+++ b/features/dashboard/task_queue.feature
@@ -4,6 +4,9 @@ Feature: Dashboard Task Queue
     Given I sign in as a user
     And I own project "Shop"
     And "John Doe" is a developer of project "Shop"
+    And "Mary Jane" is a developer of project "Shop"
+    And "Mary Jane" owns private project "Enterprise"
+    And I am a developer of project "Enterprise"
     And I have pending tasks
     And I visit dashboard task queue page
 
@@ -13,3 +16,23 @@ Feature: Dashboard Task Queue
     And I mark the pending task as done
     And I click on the "Done" tab
     Then I should see all tasks marked as done
+
+  @javascript
+    Scenario: I filter by project
+      Given I filter by "Enterprise"
+      Then I should not see tasks
+
+  @javascript
+    Scenario: I filter by author
+      Given I filter by "John Doe"
+      Then I should not see tasks related to "Mary Jane" in the list
+
+  @javascript
+    Scenario: I filter by type
+      Given I filter by "Issue"
+      Then I should not see tasks related to "Merge Requests" in the list
+
+  @javascript
+    Scenario: I filter by action
+      Given I filter by "Mentioned"
+      Then I should not see tasks related to "Assignments" in the list
diff --git a/features/steps/dashboard/task_queue.rb b/features/steps/dashboard/task_queue.rb
index 8695dd5cfb1..53920f585dc 100644
--- a/features/steps/dashboard/task_queue.rb
+++ b/features/steps/dashboard/task_queue.rb
@@ -3,58 +3,126 @@ class Spinach::Features::DashboardTaskQueue < Spinach::FeatureSteps
   include SharedPaths
   include SharedProject
   include SharedUser
+  include Select2Helper
 
   step '"John Doe" is a developer of project "Shop"' do
     project.team << [john_doe, :developer]
   end
 
+  step 'I am a developer of project "Enterprise"' do
+    enterprise.team << [current_user, :developer]
+  end
+
+  step '"Mary Jane" is a developer of project "Shop"' do
+    project.team << [john_doe, :developer]
+  end
+
   step 'I have pending tasks' do
-    create(:task, user: current_user, project: project, author: john_doe, target: assigned_issue, action: Task::ASSIGNED)
+    create(:task, user: current_user, project: project, author: mary_jane, target: issue, action: Task::MENTIONED)
+    create(:task, user: current_user, project: project, author: john_doe, target: issue, action: Task::ASSIGNED)
+    note = create(:note, author: john_doe, noteable: issue, note: "#{current_user.to_reference} Wdyt?")
+    create(:task, user: current_user, project: project, author: john_doe, target: issue, action: Task::MENTIONED, note: note)
+    create(:task, user: current_user, project: project, author: john_doe, target: merge_request, action: Task::ASSIGNED)
   end
 
   step 'I should see pending tasks assigned to me' do
-    expect(page).to have_link 'Tasks (1)'
-    expect(page).to have_link 'Done (0)'
-
-    page.within('.tasks') do
-      expect(page).to have_content project.name_with_namespace
-      expect(page).to have_content "John Doe assigned issue ##{assigned_issue.iid}"
-      expect(page).to have_content(assigned_issue.title[0..10])
-      expect(page).to have_link 'Done'
-    end
+    expect(page).to have_content 'Tasks 4'
+    expect(page).to have_content 'Done 0'
+
+    expect(page).to have_link project.name_with_namespace
+    should_see_task(1, "John Doe assigned merge request ##{merge_request.iid}", merge_request.title)
+    should_see_task(2, "John Doe mentioned on issue ##{issue.iid}", "#{current_user.to_reference} Wdyt?")
+    should_see_task(3, "John Doe assigned issue ##{issue.iid}", issue.title)
+    should_see_task(4, "Mary Jane mentioned on issue ##{issue.iid}", issue.title)
   end
 
   step 'I mark the pending task as done' do
-    click_link 'Done'
+    page.within('.task:nth-child(1)') do
+      click_link 'Done'
+    end
 
     expect(page).to have_content 'Task was successfully marked as done.'
-    expect(page).to have_link 'Tasks (0)'
-    expect(page).to have_link 'Done (1)'
-    expect(page).to have_content 'No tasks to show'
+    expect(page).to have_content 'Tasks 3'
+    expect(page).to have_content 'Done 1'
+    should_not_see_task "John Doe assigned merge request ##{merge_request.iid}"
   end
 
   step 'I click on the "Done" tab' do
-    click_link 'Done (1)'
+    click_link 'Done 1'
   end
 
   step 'I should see all tasks marked as done' do
-    page.within('.tasks') do
-      expect(page).to have_content project.name_with_namespace
-      expect(page).to have_content "John Doe assigned issue ##{assigned_issue.iid}"
-      expect(page).to have_content(assigned_issue.title[0..10])
-      expect(page).not_to have_link 'Done'
+    expect(page).to have_link project.name_with_namespace
+    should_see_task(1, "John Doe assigned merge request ##{merge_request.iid}", merge_request.title, false)
+  end
+
+  step 'I filter by "Enterprise"' do
+    select2(enterprise.id, from: "#project_id")
+  end
+
+  step 'I filter by "John Doe"' do
+    select2(john_doe.id, from: "#author_id")
+  end
+
+  step 'I filter by "Issue"' do
+    select2('Issue', from: "#type")
+  end
+
+  step 'I filter by "Mentioned"' do
+    select2("#{Task::MENTIONED}", from: '#action_id')
+  end
+
+  step 'I should not see tasks' do
+    expect(page).to have_content 'No tasks to show'
+  end
+
+  step 'I should not see tasks related to "Mary Jane" in the list' do
+    should_not_see_task "Mary Jane mentioned on issue ##{issue.iid}"
+  end
+
+  step 'I should not see tasks related to "Merge Requests" in the list' do
+    should_not_see_task "John Doe assigned merge request ##{merge_request.iid}"
+  end
+
+  step 'I should not see tasks related to "Assignments" in the list' do
+    should_not_see_task "John Doe assigned merge request ##{merge_request.iid}"
+    should_not_see_task "John Doe assigned issue ##{issue.iid}"
+  end
+
+  def should_see_task(position, title, body, pending = true)
+    page.within(".task:nth-child(#{position})") do
+      expect(page).to have_content title
+      expect(page).to have_content body
+
+      if pending
+        expect(page).to have_link 'Done'
+      else
+        expect(page).to_not have_link 'Done'
+      end
     end
   end
 
-  def assigned_issue
-    @assigned_issue ||= create(:issue, assignee: current_user, project: project)
+  def should_not_see_task(title)
+    expect(page).not_to have_content title
   end
 
   def john_doe
     @john_doe ||= user_exists("John Doe", { username: "john_doe" })
   end
 
-  def project
-    @project ||= create(:project, name: "Shop")
+  def mary_jane
+    @mary_jane ||= user_exists("Mary Jane", { username: "mary_jane" })
+  end
+
+  def enterprise
+    @enterprise ||= Project.find_by(name: 'Enterprise')
+  end
+
+  def issue
+    @issue ||= create(:issue, assignee: current_user, project: project)
+  end
+
+  def merge_request
+    @merge_request ||= create(:merge_request, assignee: current_user, source_project: project)
   end
 end
-- 
GitLab