Commit 6c8079d4 authored by Adam Hegyi's avatar Adam Hegyi

Merge branch '20444-limit-full-path-search' into 'master'

Limit full path search to certain places only

See merge request gitlab-org/gitlab!21910
parents e64f2227 6ca84a86
......@@ -64,6 +64,7 @@ export default {
this.groupId,
term,
{
search_namespaces: true,
with_issues_enabled: true,
with_shared: false,
include_subgroups: true,
......
......@@ -54,6 +54,7 @@ const projectSelect = () => {
this.groupId,
query.term,
{
search_namespaces: true,
with_issues_enabled: this.withIssuesEnabled,
with_merge_requests_enabled: this.withMergeRequestsEnabled,
with_shared: this.withShared,
......
......@@ -25,7 +25,7 @@ module Autocomplete
def execute
current_user
.projects_where_can_admin_issues
.optionally_search(search)
.optionally_search(search, include_namespace: true)
.excluding_project(project_id)
.eager_load_namespace_and_owner
.sorted_by_name_asc_limited(LIMIT)
......
......@@ -17,6 +17,7 @@
# tags: string[]
# personal: boolean
# search: string
# search_namespaces: boolean
# non_archived: boolean
# archived: 'only' or boolean
# min_access_level: integer
......@@ -171,7 +172,7 @@ class ProjectsFinder < UnionFinder
def by_search(items)
params[:search] ||= params[:name]
params[:search].present? ? items.search(params[:search]) : items
items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?)
end
def by_deleted_status(items)
......
......@@ -12,8 +12,8 @@ module OptionallySearch
end
# Optionally limits a result set to those matching the given search query.
def optionally_search(query = nil)
query.present? ? search(query) : all
def optionally_search(query = nil, **options)
query.present? ? search(query, **options) : all
end
end
end
......@@ -591,9 +591,9 @@ class Project < ApplicationRecord
# case-insensitive.
#
# query - The search query as a String.
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])
def search(query, include_namespace: false)
if include_namespace && Feature.enabled?(:project_search_by_full_path, default_enabled: true)
joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description])
else
fuzzy_search(query, [:path, :name, :description])
end
......
---
title: Only enable searching of projects by full path / name on certain dropdowns
merge_request: 21910
author:
type: changed
......@@ -46,6 +46,7 @@ GET /projects
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of projects matching the search criteria |
| `search_namespaces` | boolean | no | Include ancestor namespaces when matching search criteria. Default is `false` |
| `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. |
| `owned` | boolean | no | Limit by projects explicitly owned by the current user |
| `membership` | boolean | no | Limit by projects that the current user is a member of |
......
......@@ -505,6 +505,7 @@ module API
finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
finder_params[:archived] = archived_param unless params[:archived].nil?
finder_params[:search] = params[:search] if params[:search]
finder_params[:search_namespaces] = true if params[:search_namespaces].present?
finder_params[:user] = params.delete(:user) if params[:user]
finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level]
......
......@@ -63,6 +63,7 @@ module API
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of projects matching the search criteria'
optional :search_namespaces, type: Boolean, desc: "Include ancestor namespaces when matching search criteria"
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
......
......@@ -100,6 +100,8 @@ describe 'Group issues page' do
find('.empty-state .js-lazy-loaded')
find('.new-project-item-link').click
find('.select2-input').set(group.name)
page.within('.select2-results') do
expect(page).to have_content(project.full_name)
expect(page).not_to have_content(project_with_issues_disabled.full_name)
......
......@@ -3,8 +3,8 @@
require 'spec_helper'
describe Autocomplete::MoveToProjectFinder do
let(:user) { create(:user) }
let(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:no_access_project) { create(:project) }
let(:guest_project) { create(:project) }
......@@ -92,6 +92,15 @@ describe Autocomplete::MoveToProjectFinder do
expect(described_class.new(user, project_id: project.id, search: 'wadus').execute.to_a)
.to eq([wadus_project])
end
it 'allows searching by parent namespace' do
group = create(:group)
other_project = create(:project, group: group)
other_project.add_maintainer(user)
expect(described_class.new(user, project_id: project.id, search: group.name).execute.to_a)
.to contain_exactly(other_project)
end
end
end
end
......@@ -6,22 +6,22 @@ describe ProjectsFinder, :do_not_mock_admin_mode do
include AdminModeHelper
describe '#execute' do
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let!(:private_project) do
let_it_be(:private_project) do
create(:project, :private, name: 'A', path: 'A')
end
let!(:internal_project) do
let_it_be(:internal_project) do
create(:project, :internal, group: group, name: 'B', path: 'B')
end
let!(:public_project) do
let_it_be(:public_project) do
create(:project, :public, group: group, name: 'C', path: 'C')
end
let!(:shared_project) do
let_it_be(:shared_project) do
create(:project, :private, name: 'D', path: 'D')
end
......@@ -139,6 +139,12 @@ describe ProjectsFinder, :do_not_mock_admin_mode do
it { is_expected.to eq([public_project]) }
end
describe 'filter by group name' do
let(:params) { { name: group.name, search_namespaces: true } }
it { is_expected.to eq([public_project, internal_project]) }
end
describe 'filter by archived' do
let!(:archived_project) { create(:project, :public, :archived, name: 'E', path: 'E') }
......
......@@ -22,12 +22,22 @@ describe OptionallySearch do
it 'delegates to the search method' do
expect(model)
.to receive(:search)
.with('foo')
.with('foo', {})
model.optionally_search('foo')
end
end
context 'when an option is provided' do
it 'delegates to the search method' do
expect(model)
.to receive(:search)
.with('foo', some_option: true)
model.optionally_search('foo', some_option: true)
end
end
context 'when no query is given' do
it 'returns the current relation' do
expect(model.optionally_search).to be_a_kind_of(ActiveRecord::Relation)
......
......@@ -1757,7 +1757,7 @@ describe Project do
expect(described_class.search(project.path.upcase)).to eq([project])
end
context 'by full path' do
context 'when include_namespace is true' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
......@@ -1767,11 +1767,11 @@ describe Project do
end
it 'returns projects that match the group path' do
expect(described_class.search(group.path)).to eq([project])
expect(described_class.search(group.path, include_namespace: true)).to eq([project])
end
it 'returns projects that match the full path' do
expect(described_class.search(project.full_path)).to eq([project])
expect(described_class.search(project.full_path, include_namespace: true)).to eq([project])
end
end
......@@ -1781,11 +1781,11 @@ describe Project do
end
it 'returns no results when searching by group path' do
expect(described_class.search(group.path)).to be_empty
expect(described_class.search(group.path, include_namespace: true)).to be_empty
end
it 'returns no results when searching by full path' do
expect(described_class.search(project.full_path)).to be_empty
expect(described_class.search(project.full_path, include_namespace: true)).to be_empty
end
end
end
......
......@@ -362,6 +362,21 @@ describe API::Projects do
end
end
context 'and using search and search_namespaces is true' do
let(:group) { create(:group) }
let!(:project_in_group) { create(:project, group: group) }
before do
group.add_guest(user)
end
it_behaves_like 'projects response' do
let(:filter) { { search: group.name, search_namespaces: true } }
let(:current_user) { user }
let(:projects) { [project_in_group] }
end
end
context 'and using id_after' do
it_behaves_like 'projects response' do
let(:filter) { { id_after: project2.id } }
......
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