Commit 263ba45c authored by Patrick Bajao's avatar Patrick Bajao Committed by Mayra Cabrera

Add search support for protected branches API

A new `search` param is added to support searching for protected
branches by name.

This will be useful if user wants to look for a specific protected
branch but there are lots of protected branches per page.
parent 738e0e9e
# frozen_string_literal: true
# ProtectedBranchesFinder
#
# Used to filter protected branches by set of params
#
# Arguments:
# project - which project to scope to
# params:
# search: string
class ProtectedBranchesFinder
LIMIT = 100
attr_accessor :project, :params
def initialize(project, params = {})
@project = project
@params = params
end
def execute
protected_branches = project.limited_protected_branches(LIMIT)
protected_branches = by_name(protected_branches)
protected_branches
end
private
def by_name(protected_branches)
return protected_branches unless params[:search].present?
protected_branches.by_name(params[:search])
end
end
...@@ -2303,6 +2303,10 @@ class Project < ApplicationRecord ...@@ -2303,6 +2303,10 @@ class Project < ApplicationRecord
ci_config_path.blank? || ci_config_path == Gitlab::FileDetector::PATTERNS[:gitlab_ci] ci_config_path.blank? || ci_config_path == Gitlab::FileDetector::PATTERNS[:gitlab_ci]
end end
def limited_protected_branches(limit)
protected_branches.limit(limit)
end
private private
def closest_namespace_setting(name) def closest_namespace_setting(name)
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class ProtectedBranch < ApplicationRecord class ProtectedBranch < ApplicationRecord
include ProtectedRef include ProtectedRef
include Gitlab::SQL::Pattern
scope :requiring_code_owner_approval, scope :requiring_code_owner_approval,
-> { where(code_owner_approval_required: true) } -> { where(code_owner_approval_required: true) }
...@@ -45,6 +46,12 @@ class ProtectedBranch < ApplicationRecord ...@@ -45,6 +46,12 @@ class ProtectedBranch < ApplicationRecord
# NOOP # NOOP
# #
end end
def self.by_name(query)
return none if query.blank?
where(fuzzy_arel_match(:name, query.downcase))
end
end end
ProtectedBranch.prepend_if_ee('EE::ProtectedBranch') ProtectedBranch.prepend_if_ee('EE::ProtectedBranch')
---
title: Add search support for protected branches API
merge_request: 24137
author:
type: added
...@@ -19,10 +19,15 @@ module API ...@@ -19,10 +19,15 @@ module API
end end
params do params do
use :pagination use :pagination
optional :search, type: String, desc: 'Search for a protected branch by name'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get ':id/protected_branches' do get ':id/protected_branches' do
protected_branches = user_project.protected_branches.preload(:push_access_levels, :merge_access_levels) protected_branches =
ProtectedBranchesFinder
.new(user_project, params)
.execute
.preload(:push_access_levels, :merge_access_levels)
present paginate(protected_branches), with: Entities::ProtectedBranch, project: user_project present paginate(protected_branches), with: Entities::ProtectedBranch, project: user_project
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe ProtectedBranchesFinder do
let(:project) { create(:project) }
let!(:protected_branch) { create(:protected_branch, project: project) }
let!(:another_protected_branch) { create(:protected_branch, project: project) }
let!(:other_protected_branch) { create(:protected_branch) }
let(:params) { {} }
describe '#execute' do
subject { described_class.new(project, params).execute }
it 'returns all protected branches of project by default' do
expect(subject).to match_array([protected_branch, another_protected_branch])
end
context 'when search param is present' do
let(:params) { { search: protected_branch.name } }
it 'filters by search param' do
expect(subject).to eq([protected_branch])
end
end
context 'when there are more protected branches than the limit' do
before do
stub_const("#{described_class}::LIMIT", 1)
end
it 'returns limited protected branches of project' do
expect(subject).to eq([another_protected_branch])
end
end
end
end
...@@ -5507,6 +5507,18 @@ describe Project do ...@@ -5507,6 +5507,18 @@ describe Project do
end end
end end
describe '#limited_protected_branches' do
let(:project) { create(:project) }
let!(:protected_branch) { create(:protected_branch, project: project) }
let!(:another_protected_branch) { create(:protected_branch, project: project) }
subject { project.limited_protected_branches(1) }
it 'returns limited number of protected branches based on specified limit' do
expect(subject).to eq([another_protected_branch])
end
end
def rugged_config def rugged_config
rugged_repo(project.repository).config rugged_repo(project.repository).config
end end
......
...@@ -220,4 +220,32 @@ describe ProtectedBranch do ...@@ -220,4 +220,32 @@ describe ProtectedBranch do
end end
end end
end end
describe '.by_name' do
let!(:protected_branch) { create(:protected_branch, name: 'master') }
let!(:another_protected_branch) { create(:protected_branch, name: 'stable') }
it 'returns protected branches with a matching name' do
expect(described_class.by_name(protected_branch.name))
.to eq([protected_branch])
end
it 'returns protected branches with a partially matching name' do
expect(described_class.by_name(protected_branch.name[0..2]))
.to eq([protected_branch])
end
it 'returns protected branches with a matching name regardless of the casing' do
expect(described_class.by_name(protected_branch.name.upcase))
.to eq([protected_branch])
end
it 'returns nothing when nothing matches' do
expect(described_class.by_name('unknown')).to be_empty
end
it 'return nothing when query is blank' do
expect(described_class.by_name('')).to be_empty
end
end
end end
...@@ -12,18 +12,18 @@ describe API::ProtectedBranches do ...@@ -12,18 +12,18 @@ describe API::ProtectedBranches do
end end
describe "GET /projects/:id/protected_branches" do describe "GET /projects/:id/protected_branches" do
let(:params) { {} }
let(:route) { "/projects/#{project.id}/protected_branches" } let(:route) { "/projects/#{project.id}/protected_branches" }
shared_examples_for 'protected branches' do shared_examples_for 'protected branches' do
it 'returns the protected branches' do it 'returns the protected branches' do
get api(route, user), params: { per_page: 100 } get api(route, user), params: params.merge(per_page: 100)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array
protected_branch_names = json_response.map { |x| x['name'] } protected_branch_names = json_response.map { |x| x['name'] }
expected_branch_names = project.protected_branches.map { |x| x['name'] }
expect(protected_branch_names).to match_array(expected_branch_names) expect(protected_branch_names).to match_array(expected_branch_names)
end end
end end
...@@ -33,7 +33,19 @@ describe API::ProtectedBranches do ...@@ -33,7 +33,19 @@ describe API::ProtectedBranches do
project.add_maintainer(user) project.add_maintainer(user)
end end
it_behaves_like 'protected branches' context 'when search param is not present' do
it_behaves_like 'protected branches' do
let(:expected_branch_names) { project.protected_branches.map { |x| x['name'] } }
end
end
context 'when search param is present' do
it_behaves_like 'protected branches' do
let(:another_protected_branch) { create(:protected_branch, project: project, name: 'stable') }
let(:params) { { search: another_protected_branch.name } }
let(:expected_branch_names) { [another_protected_branch.name] }
end
end
end end
context 'when authenticated as a guest' do context 'when authenticated as a guest' do
......
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