diff options
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 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 |