Commit c807d28b authored by James Fargher's avatar James Fargher

Merge branch '214478-add-image-repository-search' into 'master'

Add search by name to Image Repository

See merge request gitlab-org/gitlab!29763
parents ad695683 07208ef9
......@@ -9,7 +9,9 @@ module Groups
respond_to do |format|
format.html
format.json do
@images = ContainerRepositoriesFinder.new(user: current_user, subject: group).execute.with_api_entity_associations
@images = ContainerRepositoriesFinder.new(user: current_user, subject: group, params: params.slice(:name))
.execute
.with_api_entity_associations
track_event(:list_repositories)
......
......@@ -10,7 +10,8 @@ module Projects
respond_to do |format|
format.html
format.json do
@images = project.container_repositories
@images = ContainerRepositoriesFinder.new(user: current_user, subject: project, params: params.slice(:name))
.execute
track_event(:list_repositories)
......
......@@ -3,17 +3,18 @@
class ContainerRepositoriesFinder
VALID_SUBJECTS = [Group, Project].freeze
def initialize(user:, subject:)
def initialize(user:, subject:, params: {})
@user = user
@subject = subject
@params = params
end
def execute
raise ArgumentError, "invalid subject_type" unless valid_subject_type?
return unless authorized?
return project_repositories if @subject.is_a?(Project)
return group_repositories if @subject.is_a?(Group)
repositories = @subject.is_a?(Project) ? project_repositories : group_repositories
filter_by_image_name(repositories)
end
private
......@@ -32,6 +33,12 @@ class ContainerRepositoriesFinder
ContainerRepository.for_group_and_its_subgroups(@subject)
end
def filter_by_image_name(repositories)
return repositories unless @params[:name]
repositories.search_by_name(@params[:name])
end
def authorized?
Ability.allowed?(@user, :read_container_image, @subject)
end
......
......@@ -2,6 +2,7 @@
class ContainerRepository < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include Gitlab::SQL::Pattern
belongs_to :project
......@@ -17,6 +18,7 @@ class ContainerRepository < ApplicationRecord
scope :for_group_and_its_subgroups, ->(group) do
where(project_id: Project.for_group_and_its_subgroups(group).with_container_registry.select(:id))
end
scope :search_by_name, ->(query) { fuzzy_search(query, [:name], use_minimum_char_limit: false) }
def self.exists_by_path?(path)
where(
......
---
title: Add search by name to registry image repositories
merge_request: 29763
author:
type: added
# frozen_string_literal: true
class AddIndexContainerRepositoryOnNameTrigramToContainerRepository < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_container_repository_on_name_trigram'
disable_ddl_transaction!
def up
add_concurrent_index :container_repositories, :name, name: INDEX_NAME, using: :gin, opclass: { name: :gin_trgm_ops }
end
def down
remove_concurrent_index_by_name(:container_repositories, INDEX_NAME)
end
end
......@@ -9247,6 +9247,8 @@ CREATE INDEX index_container_repositories_on_project_id ON public.container_repo
CREATE UNIQUE INDEX index_container_repositories_on_project_id_and_name ON public.container_repositories USING btree (project_id, name);
CREATE INDEX index_container_repository_on_name_trigram ON public.container_repositories USING gin (name public.gin_trgm_ops);
CREATE UNIQUE INDEX index_daily_report_results_unique_columns ON public.ci_daily_report_results USING btree (project_id, ref_path, param_type, date, title);
CREATE INDEX index_dependency_proxy_blobs_on_group_id_and_file_name ON public.dependency_proxy_blobs USING btree (group_id, file_name);
......@@ -13518,6 +13520,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200420172752
20200420172927
20200420201933
20200421092907
20200421233150
20200422213749
20200423075720
......
......@@ -6,12 +6,13 @@ describe Groups::Registry::RepositoriesController do
let_it_be(:user) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:group, reload: true) { create(:group) }
let(:additional_parameters) { {} }
subject do
get :index, params: {
get :index, params: additional_parameters.merge({
group_id: group,
format: format
}
})
end
before do
......@@ -36,6 +37,25 @@ describe Groups::Registry::RepositoriesController do
end
end
shared_examples 'with name parameter' do
let_it_be(:project) { create(:project, group: test_group) }
let_it_be(:repo) { create(:container_repository, project: project, name: 'my_searched_image') }
let_it_be(:another_repo) { create(:container_repository, project: project, name: 'bar') }
let(:additional_parameters) { { name: 'my_searched_image' } }
it 'returns the searched repo' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.length).to eq 1
expect(json_response.first).to include(
'id' => repo.id,
'name' => repo.name
)
end
end
shared_examples 'renders correctly' do
context 'when user has access to registry' do
let_it_be(:test_group) { group }
......@@ -64,6 +84,8 @@ describe Groups::Registry::RepositoriesController do
it_behaves_like 'renders a list of repositories'
it_behaves_like 'with name parameter'
it_behaves_like 'a gitlab tracking event', described_class.name, 'list_repositories'
context 'with project in subgroup' do
......@@ -71,6 +93,8 @@ describe Groups::Registry::RepositoriesController do
it_behaves_like 'renders a list of repositories'
it_behaves_like 'with name parameter'
context 'with project in subgroup and group' do
let_it_be(:repo_in_test_group) { create_project_with_repo(test_group) }
let_it_be(:repo_in_group) { create_project_with_repo(group) }
......@@ -81,6 +105,8 @@ describe Groups::Registry::RepositoriesController do
expect(json_response).to be_kind_of(Array)
expect(json_response.length).to eq 2
end
it_behaves_like 'with name parameter'
end
end
end
......
......@@ -3,8 +3,8 @@
require 'spec_helper'
describe Projects::Registry::RepositoriesController do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
before do
sign_in(user)
......@@ -16,6 +16,22 @@ describe Projects::Registry::RepositoriesController do
project.add_developer(user)
end
shared_examples 'with name parameter' do
let_it_be(:repo) { create(:container_repository, project: project, name: 'my_searched_image') }
let_it_be(:another_repo) { create(:container_repository, project: project, name: 'bar') }
it 'returns the searched repo' do
go_to_index(format: :json, params: { name: 'my_searched_image' })
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.length).to eq 1
expect(json_response.first).to include(
'id' => repo.id,
'name' => repo.name
)
end
end
shared_examples 'renders a list of repositories' do
context 'when root container repository exists' do
before do
......@@ -60,6 +76,8 @@ describe Projects::Registry::RepositoriesController do
expect(response).to match_response_schema('registry/repositories')
expect(response).to include_pagination_headers
end
it_behaves_like 'with name parameter'
end
context 'when there are no tags for this repository' do
......@@ -138,11 +156,11 @@ describe Projects::Registry::RepositoriesController do
end
end
def go_to_index(format: :html)
get :index, params: {
def go_to_index(format: :html, params: {} )
get :index, params: params.merge({
namespace_id: project.namespace,
project_id: project
},
}),
format: format
end
......
......@@ -6,18 +6,35 @@ describe ContainerRepositoriesFinder do
let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let!(:project_repository) { create(:container_repository, project: project) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:project_repository) { create(:container_repository, name: 'my_image', project: project) }
let(:params) { {} }
before do
group.add_reporter(reporter)
project.add_reporter(reporter)
end
shared_examples 'with name search' do
let_it_be(:not_searched_repository) do
create(:container_repository, name: 'foo_bar_baz', project: project)
end
%w[my_image my_imag _image _imag].each do |name|
context "with name set to #{name}" do
let(:params) { { name: name } }
it { is_expected.to contain_exactly(project_repository)}
it { is_expected.not_to include(not_searched_repository)}
end
end
end
describe '#execute' do
context 'with authorized user' do
subject { described_class.new(user: reporter, subject: subject_object).execute }
subject { described_class.new(user: reporter, subject: subject_object, params: params).execute }
context 'when subject_type is group' do
let(:subject_object) { group }
......@@ -28,12 +45,16 @@ describe ContainerRepositoriesFinder do
end
it { is_expected.to match_array([project_repository, other_repository]) }
it_behaves_like 'with name search'
end
context 'when subject_type is project' do
let(:subject_object) { project }
it { is_expected.to match_array([project_repository]) }
it_behaves_like 'with name search'
end
context 'with invalid subject_type' do
......
......@@ -309,4 +309,14 @@ describe ContainerRepository do
it { is_expected.to eq([]) }
end
end
describe '.search_by_name' do
let!(:another_repository) do
create(:container_repository, name: 'my_foo_bar', project: project)
end
subject { described_class.search_by_name('my_image') }
it { is_expected.to contain_exactly(repository) }
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