Commit 2d5f8ac2 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'hly-search-by-project-full-path' into 'master'

Enable searching of projects by full path

See merge request gitlab-org/gitlab!20659
parents 14a16e2e df4eb7db
...@@ -543,8 +543,12 @@ class Project < ApplicationRecord ...@@ -543,8 +543,12 @@ class Project < ApplicationRecord
# #
# query - The search query as a String. # query - The search query as a String.
def search(query) def search(query)
if Feature.enabled?(:project_search_by_full_path, default_enabled: true)
joins(:route).fuzzy_search(query, [Route.arel_table[:path], :name, :description])
else
fuzzy_search(query, [:path, :name, :description]) fuzzy_search(query, [:path, :name, :description])
end end
end
def search_by_title(query) def search_by_title(query)
non_archived.fuzzy_search(query, [:name]) non_archived.fuzzy_search(query, [:name])
......
---
title: Allow searching of projects by full path
merge_request: 20659
author:
type: added
...@@ -35,7 +35,7 @@ module Gitlab ...@@ -35,7 +35,7 @@ module Gitlab
query.length >= min_chars_for_partial_matching query.length >= min_chars_for_partial_matching
end end
# column - The column name to search in. # column - The column name / Arel column to search in.
# query - The text to search for. # query - The text to search for.
# lower_exact_match - When set to `true` we'll fall back to using # lower_exact_match - When set to `true` we'll fall back to using
# `LOWER(column) = query` instead of using `ILIKE`. # `LOWER(column) = query` instead of using `ILIKE`.
...@@ -43,19 +43,21 @@ module Gitlab ...@@ -43,19 +43,21 @@ module Gitlab
query = query.squish query = query.squish
return unless query.present? return unless query.present?
arel_column = column.is_a?(Arel::Attributes::Attribute) ? column : arel_table[column]
words = select_fuzzy_words(query, use_minimum_char_limit: use_minimum_char_limit) words = select_fuzzy_words(query, use_minimum_char_limit: use_minimum_char_limit)
if words.any? if words.any?
words.map { |word| arel_table[column].matches(to_pattern(word, use_minimum_char_limit: use_minimum_char_limit)) }.reduce(:and) words.map { |word| arel_column.matches(to_pattern(word, use_minimum_char_limit: use_minimum_char_limit)) }.reduce(:and)
else else
# No words of at least 3 chars, but we can search for an exact # No words of at least 3 chars, but we can search for an exact
# case insensitive match with the query as a whole # case insensitive match with the query as a whole
if lower_exact_match if lower_exact_match
Arel::Nodes::NamedFunction Arel::Nodes::NamedFunction
.new('LOWER', [arel_table[column]]) .new('LOWER', [arel_column])
.eq(query) .eq(query)
else else
arel_table[column].matches(sanitize_sql_like(query)) arel_column.matches(sanitize_sql_like(query))
end end
end end
end end
......
...@@ -207,5 +207,15 @@ describe Gitlab::SQL::Pattern do ...@@ -207,5 +207,15 @@ describe Gitlab::SQL::Pattern do
expect(fuzzy_arel_match.to_sql).to match(/title.+I?LIKE '\%foo\%' AND .*title.*I?LIKE '\%baz\%' AND .*title.*I?LIKE '\%really bar\%'/) expect(fuzzy_arel_match.to_sql).to match(/title.+I?LIKE '\%foo\%' AND .*title.*I?LIKE '\%baz\%' AND .*title.*I?LIKE '\%really bar\%'/)
end end
end end
context 'when passing an Arel column' do
let(:query) { 'foo' }
subject(:fuzzy_arel_match) { Project.fuzzy_arel_match(Route.arel_table[:path], query) }
it 'returns a condition with the table and column name' do
expect(fuzzy_arel_match.to_sql).to match(/"routes"."path".*ILIKE '\%foo\%'/)
end
end
end end
end end
...@@ -1662,7 +1662,7 @@ describe Project do ...@@ -1662,7 +1662,7 @@ describe Project do
end end
describe '.search' do describe '.search' do
let(:project) { create(:project, description: 'kitten mittens') } let_it_be(:project) { create(:project, description: 'kitten mittens') }
it 'returns projects with a matching name' do it 'returns projects with a matching name' do
expect(described_class.search(project.name)).to eq([project]) expect(described_class.search(project.name)).to eq([project])
...@@ -1700,6 +1700,39 @@ describe Project do ...@@ -1700,6 +1700,39 @@ describe Project do
expect(described_class.search(project.path.upcase)).to eq([project]) expect(described_class.search(project.path.upcase)).to eq([project])
end end
context 'by full path' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
context 'when feature is enabled' do
before do
stub_feature_flags(project_search_by_full_path: true)
end
it 'returns projects that match the group path' do
expect(described_class.search(group.path)).to eq([project])
end
it 'returns projects that match the full path' do
expect(described_class.search(project.full_path)).to eq([project])
end
end
context 'when feature is disabled' do
before do
stub_feature_flags(project_search_by_full_path: false)
end
it 'returns no results when searching by group path' do
expect(described_class.search(group.path)).to be_empty
end
it 'returns no results when searching by full path' do
expect(described_class.search(project.full_path)).to be_empty
end
end
end
describe 'with pending_delete project' do describe 'with pending_delete project' do
let(:pending_delete_project) { create(:project, pending_delete: true) } let(:pending_delete_project) { create(:project, pending_delete: true) }
......
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