From 19c9ee4752717fb25529ef8b4dba7bc29616a076 Mon Sep 17 00:00:00 2001
From: Phil Hughes <me@iamphill.com>
Date: Tue, 30 Aug 2016 10:32:19 +0100
Subject: [PATCH] Added search for all lists on issue boards

Closes #21139
---
 .../javascripts/boards/boards_bundle.js.es6   |  7 ++
 .../boards/components/board.js.es6            | 10 +--
 .../javascripts/boards/models/list.js.es6     |  4 --
 .../boards/stores/boards_store.js.es6         |  3 +-
 app/assets/stylesheets/pages/boards.scss      | 45 +++----------
 .../boards/components/_board.html.haml        |  5 --
 app/views/shared/issuable/_filter.html.haml   | 21 +++---
 spec/features/boards/boards_spec.rb           | 65 +++++++++++--------
 spec/javascripts/boards/list_spec.js.es6      |  9 ---
 9 files changed, 71 insertions(+), 98 deletions(-)

diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6
index a612cf0f1ae..91c12570e09 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js.es6
@@ -54,4 +54,11 @@ $(() => {
         });
     }
   });
+
+  gl.IssueBoardsSearch = new Vue({
+    el: '#js-boards-seach',
+    data: {
+      filters: Store.state.filters
+    }
+  });
 });
diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6
index d7f4107cb02..2e3a8f6870b 100644
--- a/app/assets/javascripts/boards/components/board.js.es6
+++ b/app/assets/javascripts/boards/components/board.js.es6
@@ -21,15 +21,10 @@
     },
     data () {
       return {
-        query: '',
         filters: Store.state.filters
       };
     },
     watch: {
-      query () {
-        this.list.filters = this.getFilterData();
-        this.list.getIssues(true);
-      },
       filters: {
         handler () {
           this.list.page = 1;
@@ -40,10 +35,7 @@
     },
     methods: {
       getFilterData () {
-        const filters = this.filters;
-        let queryData = { search: this.query };
-
-        Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; });
+        Object.keys(this.filters).forEach((key) => { queryData[key] = this.filters[key]; });
 
         return queryData;
       }
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index be2b8c568a8..816fa49516c 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -58,10 +58,6 @@ class List {
     }
   }
 
-  canSearch () {
-    return this.type === 'backlog';
-  }
-
   getIssues (emptyIssues = true) {
     const filters = this.filters;
     let data = { page: this.page };
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6
index 18f26a1f911..bd07ee0c161 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js.es6
@@ -15,7 +15,8 @@
         author_id: gl.utils.getParameterValues('author_id')[0],
         assignee_id: gl.utils.getParameterValues('assignee_id')[0],
         milestone_title: gl.utils.getParameterValues('milestone_title')[0],
-        label_name: gl.utils.getParameterValues('label_name[]')
+        label_name: gl.utils.getParameterValues('label_name[]'),
+        search: ''
       };
     },
     addList (listObj) {
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 9ac4d801ac4..d91558bc672 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -10,7 +10,7 @@
 .is-dragging {
   // Important because plugin sets inline CSS
   opacity: 1!important;
-  
+
   * {
     // !important to make sure no style can override this when dragging
     cursor: -webkit-grabbing!important;
@@ -160,40 +160,6 @@
   border-bottom: 1px solid $border-color;
 }
 
-.board-search-container {
-  position: relative;
-  background-color: #fff;
-
-  .form-control {
-    padding-right: 30px;
-  }
-}
-
-.board-search-icon,
-.board-search-clear-btn {
-  position: absolute;
-  right: $gl-padding + 10px;
-  top: 50%;
-  margin-top: -7px;
-  font-size: 14px;
-}
-
-.board-search-icon {
-  color: $gl-placeholder-color;
-}
-
-.board-search-clear-btn {
-  padding: 0;
-  line-height: 1;
-  background: transparent;
-  border: 0;
-  outline: 0;
-
-  &:hover {
-    color: $gl-link-color;
-  }
-}
-
 .board-delete {
   margin-right: 10px;
   padding: 0;
@@ -304,3 +270,12 @@
   margin-right: 8px;
   font-weight: 500;
 }
+
+.issue-boards-search {
+  width: 335px;
+
+  .form-control {
+    display: inline-block;
+    width: 210px;
+  }
+}
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml
index de53a298f84..6b4bfe0c354 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/projects/boards/components/_board.html.haml
@@ -21,11 +21,6 @@
               %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
                 = icon("trash")
           = icon("spinner spin", class: "board-header-loading-spinner pull-right", "v-show" => "list.loadingMore")
-      .board-inner-container.board-search-container{ "v-if" => "list.canSearch()" }
-        %input.form-control{ type: "text", placeholder: "Search issues", "v-model" => "query", "debounce" => "250" }
-        = icon("search", class: "board-search-icon", "v-show" => "!query")
-        %button.board-search-clear-btn{ type: "button", role: "button", "aria-label" => "Clear search", "@click" => "query = ''", "v-show" => "query" }
-          = icon("times", class: "board-search-clear")
       %board-list{ "inline-template" => true,
         "v-if" => "list.type !== 'blank'",
         ":list" => "list",
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 4f8ea7e7cef..0f4f744a71f 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -27,15 +27,18 @@
           = render "shared/issuable/label_dropdown"
 
         .pull-right
-          - if controller.controller_name == 'boards' && can?(current_user, :admin_list, @project)
-            .dropdown
-              %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } }
-                Create new list
-              .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
-                = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" }
-                - if can?(current_user, :admin_label, @project)
-                  = render partial: "shared/issuable/label_page_create"
-                = dropdown_loading
+          - if controller.controller_name == 'boards'
+            #js-boards-seach.issue-boards-search
+              %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" }
+              - if can?(current_user, :admin_list, @project)
+                .dropdown.pull-right
+                  %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } }
+                    Create new list
+                  .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
+                    = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" }
+                    - if can?(current_user, :admin_label, @project)
+                      = render partial: "shared/issuable/label_page_create"
+                    = dropdown_loading
           - else
             = render 'shared/sort_dropdown'
 
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 5d777895542..55e5dc15428 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -110,6 +110,45 @@ describe 'Issue Boards', feature: true, js: true do
       end
     end
 
+    it 'search backlog list' do
+      page.within('#js-boards-seach') do
+        find('.form-control').set(issue1.title)
+      end
+
+      wait_for_vue_resource
+
+      expect(find('.board:nth-child(1)')).to have_selector('.card', count: 1)
+      expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0)
+    end
+
+    it 'search done list' do
+      page.within('#js-boards-seach') do
+        find('.form-control').set(issue8.title)
+      end
+
+      wait_for_vue_resource
+
+      expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1)
+    end
+
+    it 'search list' do
+      page.within('#js-boards-seach') do
+        find('.form-control').set(issue5.title)
+      end
+
+      wait_for_vue_resource
+
+      expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1)
+      expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0)
+    end
+
     it 'allows user to delete board' do
       page.within(find('.board:nth-child(2)')) do
         find('.board-delete').click
@@ -162,32 +201,6 @@ describe 'Issue Boards', feature: true, js: true do
         end
       end
 
-      it 'is searchable' do
-        page.within(find('.board', match: :first)) do
-          find('.form-control').set issue1.title
-
-          wait_for_vue_resource(spinner: false)
-
-          expect(page).to have_selector('.card', count: 1)
-        end
-      end
-
-      it 'clears search' do
-        page.within(find('.board', match: :first)) do
-          find('.form-control').set issue1.title
-
-          expect(page).to have_selector('.card', count: 1)
-
-          find('.board-search-clear-btn').click
-        end
-
-        wait_for_vue_resource
-
-        page.within(find('.board', match: :first)) do
-          expect(page).to have_selector('.card', count: 6)
-        end
-      end
-
       it 'moves issue from backlog into list' do
         drag_to(list_to_index: 1)
 
diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6
index c206b794442..1688b996162 100644
--- a/spec/javascripts/boards/list_spec.js.es6
+++ b/spec/javascripts/boards/list_spec.js.es6
@@ -60,15 +60,6 @@ describe('List model', () => {
     }, 0);
   });
 
-  it('can\'t search when not backlog', () => {
-    expect(list.canSearch()).toBe(false);
-  });
-
-  it('can search when backlog', () => {
-    list.type = 'backlog';
-    expect(list.canSearch()).toBe(true);
-  });
-
   it('gets issue from list', (done) => {
     setTimeout(() => {
       const issue = list.findIssue(1);
-- 
GitLab