summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js10
-rw-r--r--app/assets/javascripts/pages/search/init_filtered_search.js2
-rw-r--r--app/assets/javascripts/snippets/components/snippet_title.vue6
-rw-r--r--app/assets/stylesheets/utilities.scss6
-rw-r--r--app/controllers/groups/registry/repositories_controller.rb4
-rw-r--r--app/controllers/projects/registry/repositories_controller.rb3
-rw-r--r--app/finders/container_repositories_finder.rb13
-rw-r--r--app/models/container_repository.rb2
-rw-r--r--app/views/projects/issues/_issue.html.haml3
-rw-r--r--changelogs/unreleased/214478-add-image-repository-search.yml5
-rw-r--r--db/migrate/20200421092907_add_index_container_repository_on_name_trigram_to_container_repository.rb18
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/releases/links.md10
-rw-r--r--doc/development/integrations/secure.md47
-rw-r--r--lib/gitlab/database/migration_helpers.rb28
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/qa/resource/api_fabricator.rb28
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb2
-rw-r--r--spec/controllers/groups/registry/repositories_controller_spec.rb30
-rw-r--r--spec/controllers/projects/registry/repositories_controller_spec.rb28
-rw-r--r--spec/finders/container_repositories_finder_spec.rb29
-rw-r--r--spec/frontend/helpers/filtered_search_spec_helper.js69
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js70
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb46
-rw-r--r--spec/models/container_repository_spec.rb10
26 files changed, 354 insertions, 123 deletions
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index b1b4b1c5508..ca85e54eb89 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -1,6 +1,6 @@
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import FilteredSearchContainer from '../filtered_search/container';
-import FilteredSearchManager from '../filtered_search/filtered_search_manager';
+import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
import boardsStore from './stores/boards_store';
export default class FilteredSearchBoards extends FilteredSearchManager {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 724f80f8866..30eea372a35 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -710,13 +710,17 @@ export default class FilteredSearchManager {
}
}
- search(state = null) {
- const paths = [];
+ getSearchTokens() {
const searchQuery = DropdownUtils.getSearchQuery();
this.saveCurrentSearchQuery();
const tokenKeys = this.filteredSearchTokenKeys.getKeys();
- const { tokens, searchToken } = this.tokenizer.processTokens(searchQuery, tokenKeys);
+ return this.tokenizer.processTokens(searchQuery, tokenKeys);
+ }
+
+ search(state = null) {
+ const paths = [];
+ const { tokens, searchToken } = this.getSearchTokens();
const currentState = state || getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`);
diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js
index 7fdf4ee0bf3..e54e32199f0 100644
--- a/app/assets/javascripts/pages/search/init_filtered_search.js
+++ b/app/assets/javascripts/pages/search/init_filtered_search.js
@@ -1,4 +1,4 @@
-import FilteredSearchManager from '~/filtered_search/filtered_search_manager';
+import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
export default ({
page,
diff --git a/app/assets/javascripts/snippets/components/snippet_title.vue b/app/assets/javascripts/snippets/components/snippet_title.vue
index 1fc0423a06c..06484ad5110 100644
--- a/app/assets/javascripts/snippets/components/snippet_title.vue
+++ b/app/assets/javascripts/snippets/components/snippet_title.vue
@@ -20,7 +20,11 @@ export default {
<h2 class="snippet-title prepend-top-0 mb-3" data-qa-selector="snippet_title">
{{ snippet.title }}
</h2>
- <div v-if="snippet.description" class="description" data-qa-selector="snippet_description">
+ <div
+ v-if="snippet.description"
+ class="description"
+ data-qa-selector="snippet_description_field"
+ >
<div class="md js-snippet-description" v-html="snippet.descriptionHtml"></div>
</div>
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 3fcf9a74cb2..e072adc54cf 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -7,6 +7,12 @@
#{'.text-#{$variant}-#{$suffix}'} {
color: $color;
}
+
+ #{'.hover-text-#{$variant}-#{$suffix}'} {
+ &:hover {
+ color: $color;
+ }
+ }
}
}
diff --git a/app/controllers/groups/registry/repositories_controller.rb b/app/controllers/groups/registry/repositories_controller.rb
index 16aa6e50320..14651e0794a 100644
--- a/app/controllers/groups/registry/repositories_controller.rb
+++ b/app/controllers/groups/registry/repositories_controller.rb
@@ -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)
diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb
index 2418ea97409..19d0cb9acdc 100644
--- a/app/controllers/projects/registry/repositories_controller.rb
+++ b/app/controllers/projects/registry/repositories_controller.rb
@@ -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)
diff --git a/app/finders/container_repositories_finder.rb b/app/finders/container_repositories_finder.rb
index 34921df840b..5109efb361b 100644
--- a/app/finders/container_repositories_finder.rb
+++ b/app/finders/container_repositories_finder.rb
@@ -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
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 3bff7cb06c1..455c672cea3 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -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(
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 2176d163489..1bf0c8eb031 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -6,12 +6,13 @@
= check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected-issuable"
.issuable-info-container
.issuable-main-info
- .issue-title.title
+ .issue-title.title.d-flex.align-items-center
%span.issue-title-text.js-onboarding-issue-item{ dir: "auto" }
- if issue.confidential?
%span.has-tooltip{ title: _('Confidential') }
= confidential_icon(issue)
= link_to issue.title, issue_path(issue)
+ = render_if_exists 'projects/issues/subepic_flag', issue: issue
- if issue.tasks?
%span.task-status.d-none.d-sm-inline-block
&nbsp;
diff --git a/changelogs/unreleased/214478-add-image-repository-search.yml b/changelogs/unreleased/214478-add-image-repository-search.yml
new file mode 100644
index 00000000000..6352c931393
--- /dev/null
+++ b/changelogs/unreleased/214478-add-image-repository-search.yml
@@ -0,0 +1,5 @@
+---
+title: Add search by name to registry image repositories
+merge_request: 29763
+author:
+type: added
diff --git a/db/migrate/20200421092907_add_index_container_repository_on_name_trigram_to_container_repository.rb b/db/migrate/20200421092907_add_index_container_repository_on_name_trigram_to_container_repository.rb
new file mode 100644
index 00000000000..debca8c6008
--- /dev/null
+++ b/db/migrate/20200421092907_add_index_container_repository_on_name_trigram_to_container_repository.rb
@@ -0,0 +1,18 @@
+# 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
diff --git a/db/structure.sql b/db/structure.sql
index f2b2d5e0f35..0384b3e6bcf 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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
diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md
index bf882ef35c0..f4fb2dfd05f 100644
--- a/doc/api/releases/links.md
+++ b/doc/api/releases/links.md
@@ -21,7 +21,7 @@ GET /projects/:id/releases/:tag_name/assets/links
Example request:
```shell
-curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links"
+curl --header "PRIVATE-TOKEN: n671WNGecHugsdEDPsyo" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links"
```
Example response:
@@ -60,7 +60,7 @@ GET /projects/:id/releases/:tag_name/assets/links/:link_id
Example request:
```shell
-curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
+curl --header "PRIVATE-TOKEN: n671WNGecHugsdEDPsyo" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
```
Example response:
@@ -93,7 +93,7 @@ Example request:
```shell
curl --request POST \
- --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \
+ --header "PRIVATE-TOKEN: n671WNGecHugsdEDPsyo" \
--data name="awesome-v0.2.dmg" \
--data url="http://192.168.10.15:3000" \
"https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links"
@@ -132,7 +132,7 @@ You have to specify at least one of `name` or `url`
Example request:
```shell
-curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
+curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: n671WNGecHugsdEDPsyo" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
```
Example response:
@@ -163,7 +163,7 @@ DELETE /projects/:id/releases/:tag_name/assets/links/:link_id
Example request:
```shell
-curl --request DELETE --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
+curl --request DELETE --header "PRIVATE-TOKEN: n671WNGecHugsdEDPsyo" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
```
Example response:
diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md
index b38e45778fb..48e93b498c1 100644
--- a/doc/development/integrations/secure.md
+++ b/doc/development/integrations/secure.md
@@ -470,18 +470,57 @@ Valid values are: `Ignore`, `Unknown`, `Experimental`, `Low`, `Medium`, `High`,
### Remediations
The `remediations` field of the report is an array of remediation objects.
-Each remediation describes a patch that can be applied to automatically fix
+Each remediation describes a patch that can be applied to
+[automatically fix](../../user/application_security/#solutions-for-vulnerabilities-auto-remediation)
a set of vulnerabilities.
+Here is an example of a report that contains remediations.
+
+```json
+{
+ "vulnerabilities": [
+ {
+ "category": "dependency_scanning",
+ "name": "Regular Expression Denial of Service",
+ "id": "123e4567-e89b-12d3-a456-426655440000",
+ "solution": "Upgrade to new versions.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-642735a5-1425-428d-8d4e-3c854885a3c9",
+ "value": "642735a5-1425-428d-8d4e-3c854885a3c9"
+ }
+ ]
+ }
+ ],
+ "remediations": [
+ {
+ "fixes": [
+ {
+ "id": "123e4567-e89b-12d3-a456-426655440000"
+ }
+ ],
+ "summary": "Upgrade to new version",
+ "diff": "ZGlmZiAtLWdpdCBhL3lhcm4ubG9jayBiL3lhcm4ubG9jawppbmRleCAwZWNjOTJmLi43ZmE0NTU0IDEwMDY0NAotLS0gYS95Y=="
+ }
+ ]
+}
+```
+
#### Summary
-The `summary` field is an overview of how the vulnerabilities can be fixed.
+The `summary` field is an overview of how the vulnerabilities can be fixed. This field is required.
#### Fixed vulnerabilities
The `fixes` field is an array of objects that reference the vulnerabilities fixed by the
-remediation. `fixes[].id` contains a fixed vulnerability's unique identifier.
+remediation. `fixes[].id` contains a fixed vulnerability's [unique identifier](#id). This field is required.
#### Diff
-The `diff` field is a base64-encoded remediation code diff, compatible with [`git apply`](https://git-scm.com/docs/git-format-patch#_discussion).
+The `diff` field is a base64-encoded remediation code diff, compatible with
+[`git apply`](https://git-scm.com/docs/git-format-patch#_discussion). This field is required.
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 075edcde0e5..710da8282ff 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -378,7 +378,7 @@ module Gitlab
# make things _more_ complex).
#
# `batch_column_name` option is for tables without primary key, in this
- # case an other unique integer column can be used. Example: :user_id
+ # case another unique integer column can be used. Example: :user_id
#
# rubocop: disable Metrics/AbcSize
def update_column_in_batches(table, column, value, batch_size: nil, batch_column_name: :id)
@@ -519,14 +519,20 @@ module Gitlab
# new - The new column name.
# type - The type of the new column. If no type is given the old column's
# type is used.
- def rename_column_concurrently(table, old, new, type: nil)
+ # batch_column_name - option is for tables without primary key, in this
+ # case another unique integer column can be used. Example: :user_id
+ def rename_column_concurrently(table, old, new, type: nil, batch_column_name: :id)
+ unless column_exists?(table, batch_column_name)
+ raise "Column #{batch_column_name} does not exist on #{table}"
+ end
+
if transaction_open?
raise 'rename_column_concurrently can not be run inside a transaction'
end
check_trigger_permissions!(table)
- create_column_from(table, old, new, type: type)
+ create_column_from(table, old, new, type: type, batch_column_name: batch_column_name)
install_rename_triggers(table, old, new)
end
@@ -626,14 +632,20 @@ module Gitlab
# new - The new column name.
# type - The type of the old column. If no type is given the new column's
# type is used.
- def undo_cleanup_concurrent_column_rename(table, old, new, type: nil)
+ # batch_column_name - option is for tables without primary key, in this
+ # case another unique integer column can be used. Example: :user_id
+ def undo_cleanup_concurrent_column_rename(table, old, new, type: nil, batch_column_name: :id)
+ unless column_exists?(table, batch_column_name)
+ raise "Column #{batch_column_name} does not exist on #{table}"
+ end
+
if transaction_open?
raise 'undo_cleanup_concurrent_column_rename can not be run inside a transaction'
end
check_trigger_permissions!(table)
- create_column_from(table, new, old, type: type)
+ create_column_from(table, new, old, type: type, batch_column_name: batch_column_name)
install_rename_triggers(table, old, new)
end
@@ -1090,7 +1102,7 @@ into similar problems in the future (e.g. when new tables are created).
delay_interval = BackgroundMigrationWorker.minimum_interval
end
- final_delay = nil
+ final_delay = 0
model_class.each_batch(of: batch_size) do |relation, index|
start_id, end_id = relation.pluck(Arel.sql('MIN(id), MAX(id)')).first
@@ -1355,7 +1367,7 @@ into similar problems in the future (e.g. when new tables are created).
"ON DELETE #{on_delete.upcase}"
end
- def create_column_from(table, old, new, type: nil)
+ def create_column_from(table, old, new, type: nil, batch_column_name: :id)
old_col = column_for(table, old)
new_type = type || old_col.type
@@ -1369,7 +1381,7 @@ into similar problems in the future (e.g. when new tables are created).
# necessary since we copy over old values further down.
change_column_default(table, new, old_col.default) unless old_col.default.nil?
- update_column_in_batches(table, new, Arel::Table.new(table)[old])
+ update_column_in_batches(table, new, Arel::Table.new(table)[old], batch_column_name: batch_column_name)
change_column_null(table, new, false) unless old_col.null
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index fc12d0098f3..b8c6a824d85 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -21203,6 +21203,9 @@ msgstr ""
msgid "This issue is currently blocked by the following issues: %{issues}."
msgstr ""
+msgid "This issue is in a child epic of the filtered epic"
+msgstr ""
+
msgid "This issue is locked."
msgstr ""
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index cac58c599ea..a949e447848 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -54,11 +54,23 @@ module QA
end
end
- private
-
include Support::Api
attr_writer :api_resource, :api_response
+ def api_put(body = api_put_body)
+ response = put(
+ Runtime::API::Request.new(api_client, api_put_path).url,
+ body)
+
+ unless response.code == HTTP_STATUS_OK
+ raise ResourceFabricationFailedError, "Updating #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
+ end
+
+ process_api_response(parse_body(response))
+ end
+
+ private
+
def resource_web_url(resource)
resource.fetch(:web_url) do
raise ResourceURLMissingError, "API resource for #{self.class.name} does not expose a `web_url` property: `#{resource}`."
@@ -92,18 +104,6 @@ module QA
process_api_response(parse_body(response))
end
- def api_put
- response = put(
- Runtime::API::Request.new(api_client, api_put_path).url,
- api_put_body)
-
- unless response.code == HTTP_STATUS_OK
- raise ResourceFabricationFailedError, "Updating #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
- end
-
- process_api_response(parse_body(response))
- end
-
def api_delete
url = Runtime::API::Request.new(api_client, api_delete_path).url
response = delete(url)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 7d4e6b7efbc..3964ae7eada 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -10,6 +10,8 @@ module QA
merge_request.fork_branch = 'feature-branch'
end
+ merge_request.project.api_put(auto_devops_enabled: false)
+
Page::Main::Menu.perform(&:sign_out)
Page::Main::Login.perform(&:sign_in_using_credentials)
diff --git a/spec/controllers/groups/registry/repositories_controller_spec.rb b/spec/controllers/groups/registry/repositories_controller_spec.rb
index a84664c6c04..7b78aeadbd8 100644
--- a/spec/controllers/groups/registry/repositories_controller_spec.rb
+++ b/spec/controllers/groups/registry/repositories_controller_spec.rb
@@ -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
diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb
index c641a45a216..badb84f9b50 100644
--- a/spec/controllers/projects/registry/repositories_controller_spec.rb
+++ b/spec/controllers/projects/registry/repositories_controller_spec.rb
@@ -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
diff --git a/spec/finders/container_repositories_finder_spec.rb b/spec/finders/container_repositories_finder_spec.rb
index 08c241186d6..d0c91a8f734 100644
--- a/spec/finders/container_repositories_finder_spec.rb
+++ b/spec/finders/container_repositories_finder_spec.rb
@@ -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
diff --git a/spec/frontend/helpers/filtered_search_spec_helper.js b/spec/frontend/helpers/filtered_search_spec_helper.js
new file mode 100644
index 00000000000..ceb7982bbc3
--- /dev/null
+++ b/spec/frontend/helpers/filtered_search_spec_helper.js
@@ -0,0 +1,69 @@
+export default class FilteredSearchSpecHelper {
+ static createFilterVisualTokenHTML(name, operator, value, isSelected) {
+ return FilteredSearchSpecHelper.createFilterVisualToken(name, operator, value, isSelected)
+ .outerHTML;
+ }
+
+ static createFilterVisualToken(name, operator, value, isSelected = false) {
+ const li = document.createElement('li');
+ li.classList.add('js-visual-token', 'filtered-search-token', `search-token-${name}`);
+
+ li.innerHTML = `
+ <div class="selectable ${isSelected ? 'selected' : ''}" role="button">
+ <div class="name">${name}</div>
+ <div class="operator">${operator}</div>
+ <div class="value-container">
+ <div class="value">${value}</div>
+ <div class="remove-token" role="button">
+ <i class="fa fa-close"></i>
+ </div>
+ </div>
+ </div>
+ `;
+
+ return li;
+ }
+
+ static createNameFilterVisualTokenHTML(name) {
+ return `
+ <li class="js-visual-token filtered-search-token">
+ <div class="name">${name}</div>
+ </li>
+ `;
+ }
+
+ static createNameOperatorFilterVisualTokenHTML(name, operator) {
+ return `
+ <li class="js-visual-token filtered-search-token">
+ <div class="name">${name}</div>
+ <div class="operator">${operator}</div>
+ </li>
+ `;
+ }
+
+ static createSearchVisualToken(name) {
+ const li = document.createElement('li');
+ li.classList.add('js-visual-token', 'filtered-search-term');
+ li.innerHTML = `<div class="name">${name}</div>`;
+ return li;
+ }
+
+ static createSearchVisualTokenHTML(name) {
+ return FilteredSearchSpecHelper.createSearchVisualToken(name).outerHTML;
+ }
+
+ static createInputHTML(placeholder = '', value = '') {
+ return `
+ <li class="input-token">
+ <input type='text' class='filtered-search' placeholder='${placeholder}' value='${value}'/>
+ </li>
+ `;
+ }
+
+ static createTokensContainerHTML(html, inputPlaceholder) {
+ return `
+ ${html}
+ ${FilteredSearchSpecHelper.createInputHTML(inputPlaceholder)}
+ `;
+ }
+}
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
index ceb7982bbc3..de17518ea51 100644
--- a/spec/javascripts/helpers/filtered_search_spec_helper.js
+++ b/spec/javascripts/helpers/filtered_search_spec_helper.js
@@ -1,69 +1 @@
-export default class FilteredSearchSpecHelper {
- static createFilterVisualTokenHTML(name, operator, value, isSelected) {
- return FilteredSearchSpecHelper.createFilterVisualToken(name, operator, value, isSelected)
- .outerHTML;
- }
-
- static createFilterVisualToken(name, operator, value, isSelected = false) {
- const li = document.createElement('li');
- li.classList.add('js-visual-token', 'filtered-search-token', `search-token-${name}`);
-
- li.innerHTML = `
- <div class="selectable ${isSelected ? 'selected' : ''}" role="button">
- <div class="name">${name}</div>
- <div class="operator">${operator}</div>
- <div class="value-container">
- <div class="value">${value}</div>
- <div class="remove-token" role="button">
- <i class="fa fa-close"></i>
- </div>
- </div>
- </div>
- `;
-
- return li;
- }
-
- static createNameFilterVisualTokenHTML(name) {
- return `
- <li class="js-visual-token filtered-search-token">
- <div class="name">${name}</div>
- </li>
- `;
- }
-
- static createNameOperatorFilterVisualTokenHTML(name, operator) {
- return `
- <li class="js-visual-token filtered-search-token">
- <div class="name">${name}</div>
- <div class="operator">${operator}</div>
- </li>
- `;
- }
-
- static createSearchVisualToken(name) {
- const li = document.createElement('li');
- li.classList.add('js-visual-token', 'filtered-search-term');
- li.innerHTML = `<div class="name">${name}</div>`;
- return li;
- }
-
- static createSearchVisualTokenHTML(name) {
- return FilteredSearchSpecHelper.createSearchVisualToken(name).outerHTML;
- }
-
- static createInputHTML(placeholder = '', value = '') {
- return `
- <li class="input-token">
- <input type='text' class='filtered-search' placeholder='${placeholder}' value='${value}'/>
- </li>
- `;
- }
-
- static createTokensContainerHTML(html, inputPlaceholder) {
- return `
- ${html}
- ${FilteredSearchSpecHelper.createInputHTML(inputPlaceholder)}
- `;
- }
-}
+export { default } from '../../frontend/helpers/filtered_search_spec_helper';
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index d450d2953cc..46181038445 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -790,6 +790,25 @@ describe Gitlab::Database::MigrationHelpers do
model.rename_column_concurrently(:users, :old, :new)
end
+ it 'passes the batch_column_name' do
+ expect(model).to receive(:column_exists?).with(:users, :other_batch_column).and_return(true)
+ expect(model).to receive(:check_trigger_permissions!).and_return(true)
+
+ expect(model).to receive(:create_column_from).with(
+ :users, :old, :new, type: nil, batch_column_name: :other_batch_column
+ ).and_return(true)
+
+ expect(model).to receive(:install_rename_triggers).and_return(true)
+
+ model.rename_column_concurrently(:users, :old, :new, batch_column_name: :other_batch_column)
+ end
+
+ it 'raises an error with invalid batch_column_name' do
+ expect do
+ model.rename_column_concurrently(:users, :old, :new, batch_column_name: :invalid)
+ end.to raise_error(RuntimeError, /Column invalid does not exist on users/)
+ end
+
context 'when default is false' do
let(:old_column) do
double(:column,
@@ -904,6 +923,25 @@ describe Gitlab::Database::MigrationHelpers do
model.undo_cleanup_concurrent_column_rename(:users, :old, :new)
end
+ it 'passes the batch_column_name' do
+ expect(model).to receive(:column_exists?).with(:users, :other_batch_column).and_return(true)
+ expect(model).to receive(:check_trigger_permissions!).and_return(true)
+
+ expect(model).to receive(:create_column_from).with(
+ :users, :new, :old, type: nil, batch_column_name: :other_batch_column
+ ).and_return(true)
+
+ expect(model).to receive(:install_rename_triggers).and_return(true)
+
+ model.undo_cleanup_concurrent_column_rename(:users, :old, :new, batch_column_name: :other_batch_column)
+ end
+
+ it 'raises an error with invalid batch_column_name' do
+ expect do
+ model.undo_cleanup_concurrent_column_rename(:users, :old, :new, batch_column_name: :invalid)
+ end.to raise_error(RuntimeError, /Column invalid does not exist on users/)
+ end
+
context 'when default is false' do
let(:new_column) do
double(:column,
@@ -1373,6 +1411,14 @@ describe Gitlab::Database::MigrationHelpers do
end
end
+ it 'returns zero when nothing gets queued' do
+ Sidekiq::Testing.fake! do
+ final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User.none, 'FooJob', 10.minutes)
+
+ expect(final_delay).to eq(0)
+ end
+ end
+
context 'with batch_size option' do
it 'queues jobs correctly' do
Sidekiq::Testing.fake! do
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 5bcd9dfd396..59bade3c841 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -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