diff options
23 files changed, 336 insertions, 278 deletions
diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js index ee5e778f63d..be64a9278e3 100644 --- a/app/assets/javascripts/search/store/actions.js +++ b/app/assets/javascripts/search/store/actions.js @@ -40,7 +40,7 @@ export const fetchProjects = ({ commit, state }, search) => { ); } else { // The .catch() is due to the API method not handling a rejection properly - Api.projects(search, { order_by: 'id' }, callback).catch(() => { + Api.projects(search, { order_by: 'similarity' }, callback).catch(() => { callback(); }); } diff --git a/app/models/clusters/agents/group_authorization.rb b/app/models/clusters/agents/group_authorization.rb index 96a9997be75..74c0cec3b7e 100644 --- a/app/models/clusters/agents/group_authorization.rb +++ b/app/models/clusters/agents/group_authorization.rb @@ -9,6 +9,8 @@ module Clusters belongs_to :group, class_name: '::Group', optional: false validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' } + + delegate :project, to: :agent end end end diff --git a/app/models/clusters/agents/implicit_authorization.rb b/app/models/clusters/agents/implicit_authorization.rb new file mode 100644 index 00000000000..967cc686045 --- /dev/null +++ b/app/models/clusters/agents/implicit_authorization.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Clusters + module Agents + class ImplicitAuthorization + attr_reader :agent + + delegate :id, to: :agent, prefix: true + delegate :project, to: :agent + + def initialize(agent:) + @agent = agent + end + + def config + nil + end + end + end +end diff --git a/app/services/packages/nuget/update_package_from_metadata_service.rb b/app/services/packages/nuget/update_package_from_metadata_service.rb index 6ffe4f097f4..d1e47ad00a1 100644 --- a/app/services/packages/nuget/update_package_from_metadata_service.rb +++ b/app/services/packages/nuget/update_package_from_metadata_service.rb @@ -21,11 +21,7 @@ module Packages try_obtain_lease do @package_file.transaction do - if use_new_package_file_updater? - new_execute - else - legacy_execute - end + process_package_update end end rescue ActiveRecord::RecordInvalid => e @@ -34,7 +30,7 @@ module Packages private - def new_execute + def process_package_update package_to_destroy = nil target_package = @package_file.package @@ -50,36 +46,11 @@ module Packages end update_package(target_package) - ::Packages::UpdatePackageFileService.new(@package_file, package_id: target_package.id, file_name: package_filename) .execute - package_to_destroy&.destroy! end - def legacy_execute - if existing_package - package = link_to_existing_package - elsif symbol_package? - raise InvalidMetadataError, 'symbol package is invalid, matching package does not exist' - else - package = update_linked_package - end - - update_package(package) - - # Updating file_name updates the path where the file is stored. - # We must pass the file again so that CarrierWave can handle the update - @package_file.update!( - file_name: package_filename, - file: @package_file.file - ) - end - - def use_new_package_file_updater? - ::Feature.enabled?(:packages_nuget_new_package_file_updater, @package_file.project, default_enabled: :yaml) - end - def update_package(package) return if symbol_package? diff --git a/app/views/admin/application_settings/_import_export_limits.html.haml b/app/views/admin/application_settings/_import_export_limits.html.haml index 6a49f884a80..bc4a1577f90 100644 --- a/app/views/admin/application_settings/_import_export_limits.html.haml +++ b/app/views/admin/application_settings/_import_export_limits.html.haml @@ -2,33 +2,37 @@ = form_errors(@application_setting) %fieldset + = html_escape(_("Set any rate limit to %{code_open}0%{code_close} to disable the limit.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe } + + + %fieldset .form-group - = f.label :project_import_limit, _('Max Project Import requests per minute per user'), class: 'label-bold' + = f.label :project_import_limit, _('Maximum project import requests per minute'), class: 'label-bold' = f.number_field :project_import_limit, class: 'form-control gl-form-input' %fieldset .form-group - = f.label :project_export_limit, _('Max Project Export requests per minute per user'), class: 'label-bold' + = f.label :project_export_limit, _('Maximum project export requests per minute'), class: 'label-bold' = f.number_field :project_export_limit, class: 'form-control gl-form-input' %fieldset .form-group - = f.label :project_download_export_limit, _('Max Project Export Download requests per minute per user'), class: 'label-bold' + = f.label :project_download_export_limit, _('Maximum project export download requests per minute'), class: 'label-bold' = f.number_field :project_download_export_limit, class: 'form-control gl-form-input' %fieldset .form-group - = f.label :group_import_limit, _('Max Group Import requests per minute per user'), class: 'label-bold' + = f.label :group_import_limit, _('Maximum group import requests per minute'), class: 'label-bold' = f.number_field :group_import_limit, class: 'form-control gl-form-input' %fieldset .form-group - = f.label :group_export_limit, _('Max Group Export requests per minute per user'), class: 'label-bold' + = f.label :group_export_limit, _('Maximum group export requests per minute'), class: 'label-bold' = f.number_field :group_export_limit, class: 'form-control gl-form-input' %fieldset .form-group - = f.label :group_download_export_limit, _('Max Group Export Download requests per minute per user'), class: 'label-bold' + = f.label :group_download_export_limit, _('Maximum group export download requests per minute'), class: 'label-bold' = f.number_field :group_download_export_limit, class: 'form-control gl-form-input' = f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' } diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml index 1bb8a278f2b..238e5c8b299 100644 --- a/app/views/admin/application_settings/network.html.haml +++ b/app/views/admin/application_settings/network.html.haml @@ -110,11 +110,12 @@ %section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) } .settings-header %h4 - = _('Import/Export Rate Limits') + = _('Import and export rate limits') %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded_by_default? ? _('Collapse') : _('Expand') %p - = _('Configure limits for Project/Group Import/Export.') + = _('Set per-user rate limits for imports and exports of projects and groups.') + = link_to _('Learn more.'), help_page_path('user/admin_area/settings/import_export_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer' .settings-content = render 'import_export_limits' diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml index 84ce40e240c..3e30dcaf35a 100644 --- a/app/views/shared/_visibility_level.html.haml +++ b/app/views/shared/_visibility_level.html.haml @@ -7,7 +7,7 @@ = _('Who can see this group?') - visibility_docs_path = help_page_path('public_access/public_access') - docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: visibility_docs_path } - = s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe } + = _('%{docs_link_start}Learn about visibility levels.%{docs_link_end}').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe } - if can_change_visibility_level = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) - else diff --git a/config/feature_flags/development/group_authorized_agents.yml b/config/feature_flags/development/group_authorized_agents.yml new file mode 100644 index 00000000000..e1c4620994d --- /dev/null +++ b/config/feature_flags/development/group_authorized_agents.yml @@ -0,0 +1,8 @@ +--- +name: group_authorized_agents +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69047 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340166 +milestone: '14.3' +type: development +group: group::configure +default_enabled: false diff --git a/config/feature_flags/development/packages_nuget_new_package_file_updater.yml b/config/feature_flags/development/packages_nuget_new_package_file_updater.yml deleted file mode 100644 index b1f8a79da9a..00000000000 --- a/config/feature_flags/development/packages_nuget_new_package_file_updater.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: packages_nuget_new_package_file_updater -introduced_by_url: -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336511 -milestone: '14.2' -type: development -group: group::package -default_enabled: true diff --git a/config/feature_flags/development/security_orchestration_policies_configuration.yml b/config/feature_flags/development/security_orchestration_policies_configuration.yml index ae64339607e..2570743c101 100644 --- a/config/feature_flags/development/security_orchestration_policies_configuration.yml +++ b/config/feature_flags/development/security_orchestration_policies_configuration.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321258 milestone: '13.9' type: development group: group::container security -default_enabled: false +default_enabled: true diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md index 9772f7504cf..ca3dc3660ee 100644 --- a/doc/development/integrations/jira_connect.md +++ b/doc/development/integrations/jira_connect.md @@ -54,7 +54,7 @@ To install the app in Jira: 1. Click **Upload**. - If the install was successful, you should see the **GitLab for Jira** app under **Manage apps**. + If the install was successful, you should see the **GitLab.com for Jira Cloud** app under **Manage apps**. You can also click **Getting Started** to open the configuration page rendered from your GitLab instance. _Note that any changes to the app descriptor requires you to uninstall then reinstall the app._ diff --git a/doc/integration/jira/connect-app.md b/doc/integration/jira/connect-app.md index 42ec2456c2d..7d32c080fff 100644 --- a/doc/integration/jira/connect-app.md +++ b/doc/integration/jira/connect-app.md @@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w NOTE: Only Jira users with administrator level access are able to install or configure -the GitLab app for Jira Cloud. +the GitLab.com for Jira Cloud app. ## GitLab.com for Jira Cloud app **(FREE SAAS)** diff --git a/doc/integration/jira/development_panel.md b/doc/integration/jira/development_panel.md index c8ea224f18e..6bae672db71 100644 --- a/doc/integration/jira/development_panel.md +++ b/doc/integration/jira/development_panel.md @@ -69,7 +69,7 @@ To simplify administration, we recommend that a GitLab group maintainer or group | Jira usage | GitLab.com customers need | GitLab self-managed customers need | |------------|---------------------------|------------------------------------| -| [Atlassian cloud](https://www.atlassian.com/cloud) | The [GitLab.com for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) application installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This offers real-time sync between GitLab and Jira. For more information, see the documentation for the [GitLab.com for Jira Cloud app](connect-app.md). | The [GitLab.com for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview), using a workaround process. See the documentation for [installing the GitLab Jira Cloud application for self-managed instances](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances) for more information. | +| [Atlassian cloud](https://www.atlassian.com/cloud) | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This offers real-time sync between GitLab.com and Jira. For more information, see the documentation for the [GitLab.com for Jira Cloud app](connect-app.md). | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview), using a workaround process. See the documentation for [installing the GitLab.com for Jira Cloud app for self-managed instances](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances) for more information. | | Your own server | The [Jira DVCS (distributed version control system) connector](dvcs.md). This syncs data hourly. | The [Jira DVCS Connector](dvcs.md). | Each GitLab project can be configured to connect to an entire Jira instance. That means after diff --git a/doc/integration/jira/index.md b/doc/integration/jira/index.md index 4ad9ab9f637..74dec70517c 100644 --- a/doc/integration/jira/index.md +++ b/doc/integration/jira/index.md @@ -40,7 +40,7 @@ displays in the [development panel](https://support.atlassian.com/jira-software- To set up the Jira development panel integration: - *If your installation uses Jira Cloud,* use the - [GitLab for Jira app](connect-app.md). + [GitLab.com for Jira Cloud app](connect-app.md). - *If either your Jira or GitLab installation is self-managed,* use the [Jira DVCS (distributed version control system) connector](dvcs.md). diff --git a/doc/user/admin_area/settings/img/import_export_rate_limits_v13_2.png b/doc/user/admin_area/settings/img/import_export_rate_limits_v13_2.png Binary files differdeleted file mode 100644 index 76015ce0ee3..00000000000 --- a/doc/user/admin_area/settings/img/import_export_rate_limits_v13_2.png +++ /dev/null diff --git a/doc/user/admin_area/settings/import_export_rate_limits.md b/doc/user/admin_area/settings/import_export_rate_limits.md index 12235bdb5ef..7d5a928eedf 100644 --- a/doc/user/admin_area/settings/import_export_rate_limits.md +++ b/doc/user/admin_area/settings/import_export_rate_limits.md @@ -5,28 +5,26 @@ group: Import info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Project/group import/export rate limits **(FREE SELF)** +# Rate limits for imports and exports of project and groups **(FREE SELF)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35728) in GitLab 13.2. -The following table includes configurable rate limits. The following table includes limits on a -per minute per user basis: +You can configure the rate limits for imports and exports of projects and groups: -| Limit | Default (per minute per user) | -|--------------------------|-------------------------------| -| Project Import | 6 | -| Project Export | 6 | -| Project Export Download | 1 | -| Group Import | 6 | -| Group Export | 6 | -| Group Export Download | 1 | +To change a rate limit: -All rate limits are: +1. On the top bar, select **Menu > Admin**. +1. On the left sidebar, select **Settings > Network**, then expand **Import and export rate limits**. +1. Change the value of any rate limit. The rate limits are per minute per user, not per IP address. + Set to `0` to disable a rate limit. -- Configurable through the top bar at **Menu > Admin > Settings > Network > Import/Export Rate Limits** -- Applied per minute per user -- Not applied per IP address -- Active by default. To disable, set the option to `0` -- Logged to `auth.log` file if exceed rate limit +| Limit | Default | +|-------------------------|---------| +| Project Import | 6 | +| Project Export | 6 | +| Project Export Download | 1 | +| Group Import | 6 | +| Group Export | 6 | +| Group Export Download | 1 | -![Import/Export rate limits](img/import_export_rate_limits_v13_2.png) +When a user exceeds a rate limit, it is logged in `auth.log`. diff --git a/lib/api/entities/clusters/agent_authorization.rb b/lib/api/entities/clusters/agent_authorization.rb new file mode 100644 index 00000000000..6c533fff105 --- /dev/null +++ b/lib/api/entities/clusters/agent_authorization.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module API + module Entities + module Clusters + class AgentAuthorization < Grape::Entity + expose :agent_id, as: :id + expose :project, with: Entities::ProjectIdentity, as: :config_project + expose :config, as: :configuration + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c5f2a7b6846..21e8d2fc0b9 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -559,6 +559,9 @@ msgstr "" msgid "%{doc_link_start}Advanced search%{doc_link_end} is enabled." msgstr "" +msgid "%{docs_link_start}Learn about visibility levels.%{docs_link_end}" +msgstr "" + msgid "%{due_date} (Past due)" msgstr "" @@ -8514,9 +8517,6 @@ msgstr "" msgid "Configure limit for notes created per minute by web and API requests." msgstr "" -msgid "Configure limits for Project/Group Import/Export." -msgstr "" - msgid "Configure limits for web and API requests." msgstr "" @@ -17001,6 +17001,9 @@ msgstr "" msgid "Import an exported GitLab project" msgstr "" +msgid "Import and export rate limits" +msgstr "" + msgid "Import failed due to a GitHub error: %{original}" msgstr "" @@ -17079,9 +17082,6 @@ msgstr "" msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds" msgstr "" -msgid "Import/Export Rate Limits" -msgstr "" - msgid "ImportButtons|Connect repositories from" msgstr "" @@ -20654,24 +20654,6 @@ msgstr "" msgid "Max 20 characters" msgstr "" -msgid "Max Group Export Download requests per minute per user" -msgstr "" - -msgid "Max Group Export requests per minute per user" -msgstr "" - -msgid "Max Group Import requests per minute per user" -msgstr "" - -msgid "Max Project Export Download requests per minute per user" -msgstr "" - -msgid "Max Project Export requests per minute per user" -msgstr "" - -msgid "Max Project Import requests per minute per user" -msgstr "" - msgid "Max authenticated API requests per period per user" msgstr "" @@ -20777,6 +20759,15 @@ msgstr "" msgid "Maximum files in a diff" msgstr "" +msgid "Maximum group export download requests per minute" +msgstr "" + +msgid "Maximum group export requests per minute" +msgstr "" + +msgid "Maximum group import requests per minute" +msgstr "" + msgid "Maximum import size" msgstr "" @@ -20822,6 +20813,15 @@ msgstr "" msgid "Maximum page size" msgstr "" +msgid "Maximum project export download requests per minute" +msgstr "" + +msgid "Maximum project export requests per minute" +msgstr "" + +msgid "Maximum project import requests per minute" +msgstr "" + msgid "Maximum push size" msgstr "" @@ -30628,6 +30628,9 @@ msgstr "" msgid "Set an instance-wide domain that will be available to all clusters when installing Knative." msgstr "" +msgid "Set any rate limit to %{code_open}0%{code_close} to disable the limit." +msgstr "" + msgid "Set default and restrict visibility levels. Configure import sources and git access protocol." msgstr "" @@ -30655,6 +30658,9 @@ msgstr "" msgid "Set parent epic to an epic" msgstr "" +msgid "Set per-user rate limits for imports and exports of projects and groups." +msgstr "" + msgid "Set projects and maximum size limits, session duration, user options, and check feature availability for namespace plan." msgstr "" diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js index 9f8c83f2873..b50248bb295 100644 --- a/spec/frontend/search/store/actions_spec.js +++ b/spec/frontend/search/store/actions_spec.js @@ -142,7 +142,13 @@ describe('Global Search Store Actions', () => { actions.fetchProjects({ commit: mockCommit, state }); expect(Api.groupProjects).not.toHaveBeenCalled(); - expect(Api.projects).toHaveBeenCalled(); + expect(Api.projects).toHaveBeenCalledWith( + state.query.search, + { + order_by: 'similarity', + }, + expect.any(Function), + ); }); }); }); diff --git a/spec/lib/api/entities/clusters/agent_authorization_spec.rb b/spec/lib/api/entities/clusters/agent_authorization_spec.rb new file mode 100644 index 00000000000..101a8af4ac4 --- /dev/null +++ b/spec/lib/api/entities/clusters/agent_authorization_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Entities::Clusters::AgentAuthorization do + let_it_be(:authorization) { create(:agent_group_authorization) } + + subject { described_class.new(authorization).as_json } + + it 'includes basic fields' do + expect(subject).to include( + id: authorization.agent_id, + config_project: a_hash_including(id: authorization.agent.project_id), + configuration: authorization.config + ) + end +end diff --git a/spec/models/clusters/agents/implicit_authorization_spec.rb b/spec/models/clusters/agents/implicit_authorization_spec.rb new file mode 100644 index 00000000000..69aa55a350e --- /dev/null +++ b/spec/models/clusters/agents/implicit_authorization_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Clusters::Agents::ImplicitAuthorization do + let_it_be(:agent) { create(:cluster_agent) } + + subject { described_class.new(agent: agent) } + + it { expect(subject.agent).to eq(agent) } + it { expect(subject.agent_id).to eq(agent.id) } + it { expect(subject.project).to eq(agent.project) } + it { expect(subject.config).to be_nil } +end diff --git a/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb b/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb index 66ff6a8d03f..d682ee12ed5 100644 --- a/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb +++ b/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do include ExclusiveLeaseHelpers - let(:package) { create(:nuget_package, :processing, :with_symbol_package) } + let!(:package) { create(:nuget_package, :processing, :with_symbol_package) } let(:package_file) { package.package_files.first } let(:service) { described_class.new(package_file) } let(:package_name) { 'DummyProject.DummyPackage' } @@ -63,234 +63,213 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_ end end - shared_examples 'handling all conditions' do - context 'with no existing package' do - let(:package_id) { package.id } + context 'with no existing package' do + let(:package_id) { package.id } + + it 'updates package and package file', :aggregate_failures do + expect { subject } + .to not_change { ::Packages::Package.count } + .and change { Packages::Dependency.count }.by(1) + .and change { Packages::DependencyLink.count }.by(1) + .and change { ::Packages::Nuget::Metadatum.count }.by(0) + + expect(package.reload.name).to eq(package_name) + expect(package.version).to eq(package_version) + expect(package).to be_default + expect(package_file.reload.file_name).to eq(package_file_name) + # hard reset needed to properly reload package_file.file + expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0 + end - it 'updates package and package file', :aggregate_failures do - expect { subject } - .to not_change { ::Packages::Package.count } - .and change { Packages::Dependency.count }.by(1) - .and change { Packages::DependencyLink.count }.by(1) - .and change { ::Packages::Nuget::Metadatum.count }.by(0) + it_behaves_like 'taking the lease' - expect(package.reload.name).to eq(package_name) - expect(package.version).to eq(package_version) - expect(package).to be_default - expect(package_file.reload.file_name).to eq(package_file_name) - # hard reset needed to properly reload package_file.file - expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0 - end + it_behaves_like 'not updating the package if the lease is taken' + end - it_behaves_like 'taking the lease' + context 'with existing package' do + let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) } + let(:package_id) { existing_package.id } - it_behaves_like 'not updating the package if the lease is taken' - end + it 'link existing package and updates package file', :aggregate_failures do + expect(service).to receive(:try_obtain_lease).and_call_original - context 'with existing package' do - let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) } - let(:package_id) { existing_package.id } + expect { subject } + .to change { ::Packages::Package.count }.by(-1) + .and change { Packages::Dependency.count }.by(0) + .and change { Packages::DependencyLink.count }.by(0) + .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0) + .and change { ::Packages::Nuget::Metadatum.count }.by(0) + expect(package_file.reload.file_name).to eq(package_file_name) + expect(package_file.package).to eq(existing_package) + end - it 'link existing package and updates package file', :aggregate_failures do - expect(service).to receive(:try_obtain_lease).and_call_original + it_behaves_like 'taking the lease' - expect { subject } - .to change { ::Packages::Package.count }.by(-1) - .and change { Packages::Dependency.count }.by(0) - .and change { Packages::DependencyLink.count }.by(0) - .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0) - .and change { ::Packages::Nuget::Metadatum.count }.by(0) - expect(package_file.reload.file_name).to eq(package_file_name) - expect(package_file.package).to eq(existing_package) - end + it_behaves_like 'not updating the package if the lease is taken' + end - it_behaves_like 'taking the lease' + context 'with a nuspec file with metadata' do + let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' } + let(:expected_tags) { %w(foo bar test tag1 tag2 tag3 tag4 tag5) } - it_behaves_like 'not updating the package if the lease is taken' + before do + allow_next_instance_of(Packages::Nuget::MetadataExtractionService) do |service| + allow(service) + .to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath)) + end end - context 'with a nuspec file with metadata' do - let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' } - let(:expected_tags) { %w(foo bar test tag1 tag2 tag3 tag4 tag5) } + it 'creates tags' do + expect(service).to receive(:try_obtain_lease).and_call_original + expect { subject }.to change { ::Packages::Tag.count }.by(8) + expect(package.reload.tags.map(&:name)).to contain_exactly(*expected_tags) + end - before do - allow_next_instance_of(Packages::Nuget::MetadataExtractionService) do |service| - allow(service) - .to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath)) - end - end + context 'with existing package and tags' do + let!(:existing_package) { create(:nuget_package, project: package.project, name: 'DummyProject.WithMetadata', version: '1.2.3') } + let!(:tag1) { create(:packages_tag, package: existing_package, name: 'tag1') } + let!(:tag2) { create(:packages_tag, package: existing_package, name: 'tag2') } + let!(:tag3) { create(:packages_tag, package: existing_package, name: 'tag_not_in_metadata') } - it 'creates tags' do + it 'creates tags and deletes those not in metadata' do expect(service).to receive(:try_obtain_lease).and_call_original - expect { subject }.to change { ::Packages::Tag.count }.by(8) - expect(package.reload.tags.map(&:name)).to contain_exactly(*expected_tags) + expect { subject }.to change { ::Packages::Tag.count }.by(5) + expect(existing_package.tags.map(&:name)).to contain_exactly(*expected_tags) end + end - context 'with existing package and tags' do - let!(:existing_package) { create(:nuget_package, project: package.project, name: 'DummyProject.WithMetadata', version: '1.2.3') } - let!(:tag1) { create(:packages_tag, package: existing_package, name: 'tag1') } - let!(:tag2) { create(:packages_tag, package: existing_package, name: 'tag2') } - let!(:tag3) { create(:packages_tag, package: existing_package, name: 'tag_not_in_metadata') } - - it 'creates tags and deletes those not in metadata' do - expect(service).to receive(:try_obtain_lease).and_call_original - expect { subject }.to change { ::Packages::Tag.count }.by(5) - expect(existing_package.tags.map(&:name)).to contain_exactly(*expected_tags) - end - end - - it 'creates nuget metadatum', :aggregate_failures do - expect { subject } - .to not_change { ::Packages::Package.count } - .and change { ::Packages::Nuget::Metadatum.count }.by(1) - - metadatum = package_file.reload.package.nuget_metadatum - expect(metadatum.license_url).to eq('https://opensource.org/licenses/MIT') - expect(metadatum.project_url).to eq('https://gitlab.com/gitlab-org/gitlab') - expect(metadatum.icon_url).to eq('https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png') - end + it 'creates nuget metadatum', :aggregate_failures do + expect { subject } + .to not_change { ::Packages::Package.count } + .and change { ::Packages::Nuget::Metadatum.count }.by(1) - context 'with too long url' do - let_it_be(:too_long_url) { "http://localhost/#{'bananas' * 50}" } + metadatum = package_file.reload.package.nuget_metadatum + expect(metadatum.license_url).to eq('https://opensource.org/licenses/MIT') + expect(metadatum.project_url).to eq('https://gitlab.com/gitlab-org/gitlab') + expect(metadatum.icon_url).to eq('https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png') + end - let(:metadata) { { package_name: package_name, package_version: package_version, license_url: too_long_url } } + context 'with too long url' do + let_it_be(:too_long_url) { "http://localhost/#{'bananas' * 50}" } - before do - allow(service).to receive(:metadata).and_return(metadata) - end + let(:metadata) { { package_name: package_name, package_version: package_version, license_url: too_long_url } } - it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError + before do + allow(service).to receive(:metadata).and_return(metadata) end + + it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError end + end - context 'with nuspec file with dependencies' do - let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' } - let(:package_name) { 'Test.Package' } - let(:package_version) { '3.5.2' } - let(:package_file_name) { 'test.package.3.5.2.nupkg' } + context 'with nuspec file with dependencies' do + let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' } + let(:package_name) { 'Test.Package' } + let(:package_version) { '3.5.2' } + let(:package_file_name) { 'test.package.3.5.2.nupkg' } - before do - allow_next_instance_of(Packages::Nuget::MetadataExtractionService) do |service| - allow(service) - .to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath)) - end + before do + allow_next_instance_of(Packages::Nuget::MetadataExtractionService) do |service| + allow(service) + .to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath)) end + end - it 'updates package and package file', :aggregate_failures do - expect { subject } - .to not_change { ::Packages::Package.count } - .and change { Packages::Dependency.count }.by(4) - .and change { Packages::DependencyLink.count }.by(4) - .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(2) - - expect(package.reload.name).to eq(package_name) - expect(package.version).to eq(package_version) - expect(package).to be_default - expect(package_file.reload.file_name).to eq(package_file_name) - # hard reset needed to properly reload package_file.file - expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0 - end + it 'updates package and package file', :aggregate_failures do + expect { subject } + .to not_change { ::Packages::Package.count } + .and change { Packages::Dependency.count }.by(4) + .and change { Packages::DependencyLink.count }.by(4) + .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(2) + + expect(package.reload.name).to eq(package_name) + expect(package.version).to eq(package_version) + expect(package).to be_default + expect(package_file.reload.file_name).to eq(package_file_name) + # hard reset needed to properly reload package_file.file + expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0 end + end - context 'with package file not containing a nuspec file' do - before do - allow_next_instance_of(Zip::File) do |file| - allow(file).to receive(:glob).and_return([]) - end + context 'with package file not containing a nuspec file' do + before do + allow_next_instance_of(Zip::File) do |file| + allow(file).to receive(:glob).and_return([]) end - - it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError end - context 'with a symbol package' do - let(:package_file) { package.package_files.last } - let(:package_file_name) { 'dummyproject.dummypackage.1.0.0.snupkg' } + it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError + end - context 'with no existing package' do - let(:package_id) { package.id } + context 'with a symbol package' do + let(:package_file) { package.package_files.last } + let(:package_file_name) { 'dummyproject.dummypackage.1.0.0.snupkg' } - it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError - end - - context 'with existing package' do - let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) } - let(:package_id) { existing_package.id } + context 'with no existing package' do + let(:package_id) { package.id } - it 'link existing package and updates package file', :aggregate_failures do - expect(service).to receive(:try_obtain_lease).and_call_original - expect(::Packages::Nuget::SyncMetadatumService).not_to receive(:new) - expect(::Packages::UpdateTagsService).not_to receive(:new) + it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError + end - expect { subject } - .to change { ::Packages::Package.count }.by(-1) - .and change { Packages::Dependency.count }.by(0) - .and change { Packages::DependencyLink.count }.by(0) - .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0) - .and change { ::Packages::Nuget::Metadatum.count }.by(0) - expect(package_file.reload.file_name).to eq(package_file_name) - expect(package_file.package).to eq(existing_package) - end + context 'with existing package' do + let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) } + let(:package_id) { existing_package.id } - it_behaves_like 'taking the lease' + it 'link existing package and updates package file', :aggregate_failures do + expect(service).to receive(:try_obtain_lease).and_call_original + expect(::Packages::Nuget::SyncMetadatumService).not_to receive(:new) + expect(::Packages::UpdateTagsService).not_to receive(:new) - it_behaves_like 'not updating the package if the lease is taken' + expect { subject } + .to change { ::Packages::Package.count }.by(-1) + .and change { Packages::Dependency.count }.by(0) + .and change { Packages::DependencyLink.count }.by(0) + .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0) + .and change { ::Packages::Nuget::Metadatum.count }.by(0) + expect(package_file.reload.file_name).to eq(package_file_name) + expect(package_file.package).to eq(existing_package) end - end - - context 'with an invalid package name' do - invalid_names = [ - '', - 'My/package', - '../../../my_package', - '%2e%2e%2fmy_package' - ] - invalid_names.each do |invalid_name| - before do - allow(service).to receive(:package_name).and_return(invalid_name) - end + it_behaves_like 'taking the lease' - it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError - end + it_behaves_like 'not updating the package if the lease is taken' end + end - context 'with an invalid package version' do - invalid_versions = [ - '', - '555', - '1.2', - '1./2.3', - '../../../../../1.2.3', - '%2e%2e%2f1.2.3' - ] - - invalid_versions.each do |invalid_version| - before do - allow(service).to receive(:package_version).and_return(invalid_version) - end - - it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError + context 'with an invalid package name' do + invalid_names = [ + '', + 'My/package', + '../../../my_package', + '%2e%2e%2fmy_package' + ] + + invalid_names.each do |invalid_name| + before do + allow(service).to receive(:package_name).and_return(invalid_name) end - end - end - context 'with packages_nuget_new_package_file_updater enabled' do - before do - expect(service).not_to receive(:legacy_execute) + it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError end - - it_behaves_like 'handling all conditions' end - context 'with packages_nuget_new_package_file_updater disabled' do - before do - stub_feature_flags(packages_nuget_new_package_file_updater: false) - expect(::Packages::UpdatePackageFileService) - .not_to receive(:new).with(package_file, instance_of(Hash)).and_call_original - expect(service).not_to receive(:new_execute) - end + context 'with an invalid package version' do + invalid_versions = [ + '', + '555', + '1.2', + '1./2.3', + '../../../../../1.2.3', + '%2e%2e%2f1.2.3' + ] + + invalid_versions.each do |invalid_version| + before do + allow(service).to receive(:package_version).and_return(invalid_version) + end - it_behaves_like 'handling all conditions' + it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError + end end end end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index e48c8125d84..ae0f48fbe47 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -23,12 +23,39 @@ module CycleAnalyticsHelpers end end + def select_event_label(sel) + page.within(sel) do + find('.dropdown-toggle').click + page.find(".dropdown-menu").all(".dropdown-item")[1].click + end + end + + def fill_in_custom_label_stage_fields + index = page.all('[data-testid="value-stream-stage-fields"]').length + last_stage = page.all('[data-testid="value-stream-stage-fields"]').last + + within last_stage do + find('[name*="custom-stage-name-"]').fill_in with: "Cool custom label stage - name #{index}" + select_dropdown_option_by_value "custom-stage-start-event-", :issue_label_added + select_dropdown_option_by_value "custom-stage-end-event-", :issue_label_removed + + select_event_label("[data-testid*='custom-stage-start-event-label-']") + select_event_label("[data-testid*='custom-stage-end-event-label-']") + end + end + def add_custom_stage_to_form page.find_button(s_('CreateValueStreamForm|Add another stage')).click fill_in_custom_stage_fields end + def add_custom_label_stage_to_form + page.find_button(s_('CreateValueStreamForm|Add another stage')).click + + fill_in_custom_label_stage_fields + end + def save_value_stream(custom_value_stream_name) fill_in 'create-value-stream-name', with: custom_value_stream_name |