diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2018-08-01 07:04:29 +0000 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2018-08-01 07:04:29 +0000 |
commit | 9b433c370688ddb4be261915da6aeb8327429966 (patch) | |
tree | 033c5f431affde7ead660f93efbf4612690a474a | |
parent | b690c268c2c34e1a7e34a9bbef264fe986e2f2d4 (diff) | |
parent | ac05ebc330375a9624f6f42f053db96116f3e8ba (diff) | |
download | gitlab-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.rb | 5 | ||||
-rw-r--r-- | app/finders/labels_finder.rb | 11 | ||||
-rw-r--r-- | app/models/label.rb | 12 | ||||
-rw-r--r-- | app/views/projects/labels/index.html.haml | 35 | ||||
-rw-r--r-- | changelogs/unreleased/dz-labels-search.yml | 5 | ||||
-rw-r--r-- | locale/gitlab.pot | 15 | ||||
-rw-r--r-- | spec/features/projects/labels/search_labels_spec.rb | 80 | ||||
-rw-r--r-- | spec/finders/labels_finder_spec.rb | 16 | ||||
-rw-r--r-- | spec/models/label_spec.rb | 16 |
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 |