summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2018-08-01 07:04:29 +0000
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2018-08-01 07:04:29 +0000
commit9b433c370688ddb4be261915da6aeb8327429966 (patch)
tree033c5f431affde7ead660f93efbf4612690a474a
parentb690c268c2c34e1a7e34a9bbef264fe986e2f2d4 (diff)
parentac05ebc330375a9624f6f42f053db96116f3e8ba (diff)
downloadgitlab-ce-9b433c370688ddb4be261915da6aeb8327429966.tar.gz
Merge branch 'dz-labels-search' into 'master'
Search for label on project labels page by title or description See merge request gitlab-org/gitlab-ce!20749
-rw-r--r--app/controllers/projects/labels_controller.rb5
-rw-r--r--app/finders/labels_finder.rb11
-rw-r--r--app/models/label.rb12
-rw-r--r--app/views/projects/labels/index.html.haml35
-rw-r--r--changelogs/unreleased/dz-labels-search.yml5
-rw-r--r--locale/gitlab.pot15
-rw-r--r--spec/features/projects/labels/search_labels_spec.rb80
-rw-r--r--spec/finders/labels_finder_spec.rb16
-rw-r--r--spec/models/label_spec.rb16
9 files changed, 188 insertions, 7 deletions
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index ce03b2d8d1d..8a2bce6e7b5 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -160,7 +160,10 @@ class Projects::LabelsController < Projects::ApplicationController
def find_labels
@available_labels ||=
- LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: params[:include_ancestor_groups]).execute
+ LabelsFinder.new(current_user,
+ project_id: @project.id,
+ include_ancestor_groups: params[:include_ancestor_groups],
+ search: params[:search]).execute
end
def authorize_admin_labels!
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index afd1f824b32..1d05bf28438 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -14,6 +14,7 @@ class LabelsFinder < UnionFinder
@skip_authorization = skip_authorization
items = find_union(label_ids, Label) || Label.none
items = with_title(items)
+ items = by_search(items)
sort(items)
end
@@ -63,6 +64,12 @@ class LabelsFinder < UnionFinder
items.where(title: title)
end
+ def by_search(labels)
+ return labels unless search?
+
+ labels.search(params[:search])
+ end
+
# Gets redacted array of group ids
# which can include the ancestors and descendants of the requested group.
def group_ids_for(group)
@@ -106,6 +113,10 @@ class LabelsFinder < UnionFinder
params[:only_group_labels]
end
+ def search?
+ params[:search].present?
+ end
+
def title
params[:title] || params[:name]
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 7bbcaa121ca..7b08547fa6e 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -2,6 +2,7 @@ class Label < ActiveRecord::Base
include CacheMarkdownField
include Referable
include Subscribable
+ include Gitlab::SQL::Pattern
# Represents a "No Label" state used for filtering Issues and Merge
# Requests that have no label assigned.
@@ -103,6 +104,17 @@ class Label < ActiveRecord::Base
nil
end
+ # Searches for labels with a matching title or description.
+ #
+ # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+ #
+ # query - The search query as a String.
+ #
+ # Returns an ActiveRecord::Relation.
+ def self.search(query)
+ fuzzy_search(query, [:title, :description])
+ end
+
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index fb5b0fc15c9..768ce9bd103 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -2,32 +2,45 @@
- page_title "Labels"
- can_admin_label = can?(current_user, :admin_label, @project)
- hide_class = ''
+- search = params[:search]
- if can_admin_label
- content_for(:header_content) do
.nav-controls
= link_to _('New label'), new_project_label_path(@project), class: "btn btn-new"
-- if @labels.exists? || @prioritized_labels.exists?
+- if @labels.exists? || @prioritized_labels.exists? || search.present?
#promote-label-modal
%div{ class: container_class }
.top-area.adjust
.nav-text
= _('Labels can be applied to issues and merge requests.')
- - if can_admin_label
- = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
- .labels-container.prepend-top-5
+ .nav-controls
+ = form_tag project_labels_path(@project), method: :get do
+ .input-group
+ = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false }
+ %span.input-group-append
+ %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') }
+ = icon("search")
+
+ .labels-container.prepend-top-10
- if can_admin_label
+ - if search.blank?
+ %p.text-muted
+ = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
-# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) }
%h5.prepend-top-10= _('Prioritized Labels')
.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) }
- #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
+ #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
= render 'shared/empty_states/priority_labels'
- if @prioritized_labels.present?
= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label, locals: { force_priority: true }
+ - elsif search.present?
+ .nothing-here-block
+ = _('No prioritised labels with such name or description')
- if @labels.present?
.other-labels
@@ -36,6 +49,18 @@
.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
+ - elsif search.present?
+ .other-labels
+ - if @available_labels.any?
+ %h5
+ = _('Other Labels')
+ .nothing-here-block
+ = _('No other labels with such name or description')
+ - else
+ .nothing-here-block
+ = _('No labels with such name or description')
+
+
- else
= render 'shared/empty_states/labels'
diff --git a/changelogs/unreleased/dz-labels-search.yml b/changelogs/unreleased/dz-labels-search.yml
new file mode 100644
index 00000000000..49c1b6c1a86
--- /dev/null
+++ b/changelogs/unreleased/dz-labels-search.yml
@@ -0,0 +1,5 @@
+---
+title: Search for labels by title or description on project labels page
+merge_request: 20749
+author:
+type: added
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 5ac5d0535ad..b003f22f26c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2522,6 +2522,9 @@ msgstr ""
msgid "Files (%{human_size})"
msgstr ""
+msgid "Filter"
+msgstr ""
+
msgid "Filter by commit message"
msgstr ""
@@ -3562,12 +3565,21 @@ msgstr ""
msgid "No files found."
msgstr ""
+msgid "No labels with such name or description"
+msgstr ""
+
msgid "No merge requests found"
msgstr ""
msgid "No messages were logged"
msgstr ""
+msgid "No other labels with such name or description"
+msgstr ""
+
+msgid "No prioritised labels with such name or description"
+msgstr ""
+
msgid "No public groups"
msgstr ""
@@ -4901,6 +4913,9 @@ msgstr ""
msgid "Submit as spam"
msgstr ""
+msgid "Submit search"
+msgstr ""
+
msgid "Subscribe"
msgstr ""
diff --git a/spec/features/projects/labels/search_labels_spec.rb b/spec/features/projects/labels/search_labels_spec.rb
new file mode 100644
index 00000000000..2d5a138c3cc
--- /dev/null
+++ b/spec/features/projects/labels/search_labels_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Search for labels', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let!(:label1) { create(:label, title: 'Foo', description: 'Lorem ipsum', project: project) }
+ let!(:label2) { create(:label, title: 'Bar', description: 'Fusce consequat', project: project) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit project_labels_path(project)
+ end
+
+ it 'searches for label by title' do
+ fill_in 'label-search', with: 'Bar'
+ find('#label-search').native.send_keys(:enter)
+
+ expect(page).to have_content(label2.title)
+ expect(page).to have_content(label2.description)
+ expect(page).not_to have_content(label1.title)
+ expect(page).not_to have_content(label1.description)
+ end
+
+ it 'searches for label by title' do
+ fill_in 'label-search', with: 'Lorem'
+ find('#label-search').native.send_keys(:enter)
+
+ expect(page).to have_content(label1.title)
+ expect(page).to have_content(label1.description)
+ expect(page).not_to have_content(label2.title)
+ expect(page).not_to have_content(label2.description)
+ end
+
+ it 'shows nothing found message' do
+ fill_in 'label-search', with: 'nonexistent'
+ find('#label-search').native.send_keys(:enter)
+
+ expect(page).to have_content('No labels with such name or description')
+ expect(page).not_to have_content(label1.title)
+ expect(page).not_to have_content(label1.description)
+ expect(page).not_to have_content(label2.title)
+ expect(page).not_to have_content(label2.description)
+ end
+
+ context 'priority labels' do
+ let!(:label_priority) { create(:label_priority, label: label1, project: project) }
+
+ it 'searches for priority label' do
+ fill_in 'label-search', with: 'Foo'
+ find('#label-search').native.send_keys(:enter)
+
+ page.within('.prioritized-labels') do
+ expect(page).to have_content(label1.title)
+ expect(page).to have_content(label1.description)
+ end
+
+ page.within('.other-labels') do
+ expect(page).to have_content('No other labels with such name or description')
+ end
+ end
+
+ it 'searches for other label' do
+ fill_in 'label-search', with: 'Bar'
+ find('#label-search').native.send_keys(:enter)
+
+ page.within('.prioritized-labels') do
+ expect(page).to have_content('No prioritised labels with such name or description')
+ end
+
+ page.within('.other-labels') do
+ expect(page).to have_content(label2.title)
+ expect(page).to have_content(label2.description)
+ end
+ end
+ end
+end
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 899d0d22819..eb2a4576e30 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -14,7 +14,7 @@ describe LabelsFinder do
let(:project_4) { create(:project, :public) }
let(:project_5) { create(:project, namespace: group_1) }
- let!(:project_label_1) { create(:label, project: project_1, title: 'Label 1') }
+ let!(:project_label_1) { create(:label, project: project_1, title: 'Label 1', description: 'awesome label') }
let!(:project_label_2) { create(:label, project: project_2, title: 'Label 2') }
let!(:project_label_4) { create(:label, project: project_4, title: 'Label 4') }
let!(:project_label_5) { create(:label, project: project_5, title: 'Label 5') }
@@ -196,5 +196,19 @@ describe LabelsFinder do
expect(finder.execute).to be_empty
end
end
+
+ context 'search by title and description' do
+ it 'returns labels with a partially matching title' do
+ finder = described_class.new(user, search: '(group)')
+
+ expect(finder.execute).to eq [group_label_1]
+ end
+
+ it 'returns labels with a partially matching description' do
+ finder = described_class.new(user, search: 'awesome')
+
+ expect(finder.execute).to eq [project_label_1]
+ end
+ end
end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 8914845ea82..99670af786a 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -139,4 +139,20 @@ describe Label do
end
end
end
+
+ describe '.search' do
+ let(:label) { create(:label, title: 'bug', description: 'incorrect behavior') }
+
+ it 'returns labels with a partially matching title' do
+ expect(described_class.search(label.title[0..2])).to eq([label])
+ end
+
+ it 'returns labels with a partially matching description' do
+ expect(described_class.search(label.description[0..5])).to eq([label])
+ end
+
+ it 'returns nothing' do
+ expect(described_class.search('feature')).to be_empty
+ end
+ end
end