Commit 9b433c37 authored by Dmytro Zaporozhets's avatar Dmytro Zaporozhets

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
parents b690c268 ac05ebc3
Pipeline #26930808 passed with stages
in 37 minutes and 16 seconds
......@@ -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!
......
......@@ -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
......
......@@ -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
......
......@@ -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'
......
---
title: Search for labels by title or description on project labels page
merge_request: 20749
author:
type: added
......@@ -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 ""
......
# 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
......@@ -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
......@@ -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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment