From 79ecd9a7489305e8357ca1df74ac7d7cc775b0d3 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 13 Aug 2021 21:09:54 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../projects/merge_requests_controller.rb | 9 ++- app/finders/ci/pipelines_finder.rb | 9 +++ app/helpers/groups_helper.rb | 7 +- app/models/ci/pipeline.rb | 1 + app/views/groups/dependency_proxies/_url.html.haml | 4 +- app/workers/pipeline_hooks_worker.rb | 2 +- .../development/pipeline_source_filter.yml | 8 +++ config/initializers/1_settings.rb | 2 +- config/initializers/content_security_policy.rb | 9 ++- doc/operations/error_tracking.md | 25 ++++--- .../img/pipeline_security_dashboard_v13_10.png | Bin 80367 -> 0 bytes .../img/pipeline_security_dashboard_v14_2.png | Bin 0 -> 83851 bytes .../security_dashboard/index.md | 2 +- .../img/group_vulnerability_report_v13_9.png | Bin 54478 -> 0 bytes .../img/group_vulnerability_report_v14_2.png | Bin 0 -> 109933 bytes ...ect_security_dashboard_status_change_v13_10.png | Bin 41154 -> 0 bytes ...ject_security_dashboard_status_change_v14_2.png | Bin 0 -> 63558 bytes .../vulnerability_report/index.md | 20 +++--- doc/user/packages/helm_repository/index.md | 9 +++ lib/api/ci/pipelines.rb | 3 +- lib/api/entities/ci/pipeline_basic.rb | 2 + .../backfill_integrations_type_new.rb | 30 +++++++- .../content_security_policy/config_loader.rb | 78 ++++++++++----------- lib/gitlab/data_builder/pipeline.rb | 26 ++++++- lib/gitlab/database/async_indexes/index_creator.rb | 2 + locale/gitlab.pot | 23 +++--- spec/factories/ci/builds.rb | 9 ++- spec/finders/ci/pipelines_finder_spec.rb | 23 ++++++ spec/fixtures/api/schemas/pipeline_schedule.json | 1 + spec/helpers/groups_helper_spec.rb | 10 ++- .../backfill_integrations_type_new_spec.rb | 22 +++++- .../content_security_policy/config_loader_spec.rb | 47 ++++++------- spec/lib/gitlab/data_builder/pipeline_spec.rb | 36 ++++++++++ spec/models/ci/pipeline_spec.rb | 14 ++++ spec/requests/api/ci/pipelines_spec.rb | 65 ++++++++++++++++- spec/requests/projects/merge_requests_spec.rb | 54 +++++++------- 36 files changed, 409 insertions(+), 143 deletions(-) create mode 100644 config/feature_flags/development/pipeline_source_filter.yml delete mode 100644 doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_10.png create mode 100644 doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v14_2.png delete mode 100644 doc/user/application_security/vulnerability_report/img/group_vulnerability_report_v13_9.png create mode 100644 doc/user/application_security/vulnerability_report/img/group_vulnerability_report_v14_2.png delete mode 100644 doc/user/application_security/vulnerability_report/img/project_security_dashboard_status_change_v13_10.png create mode 100644 doc/user/application_security/vulnerability_report/img/project_security_dashboard_status_change_v14_2.png diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ca7c675f4a8..1a14b2f99da 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -131,9 +131,16 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo Gitlab::PollingInterval.set_header(response, interval: 10_000) if params[:serializer] == 'sidebar_extras' && Feature.enabled?(:merge_request_show_render_cached, @project, default_enabled: :yaml) + cache_context = [ + params[:serializer], + current_user&.cache_key, + @merge_request.assignees.map(&:cache_key), + @merge_request.reviewers.map(&:cache_key) + ] + render_cached(@merge_request, with: serializer, - cache_context: -> (_) { [params[:serializer], current_user&.cache_key, project.emails_disabled?, issuable.subscribed?(current_user, project)] }, + cache_context: -> (_) { [Digest::SHA256.hexdigest(cache_context.to_s)] }, serializer: params[:serializer]) else render json: serializer.represent(@merge_request, serializer: params[:serializer]) diff --git a/app/finders/ci/pipelines_finder.rb b/app/finders/ci/pipelines_finder.rb index a4f4a4395f7..a79840216da 100644 --- a/app/finders/ci/pipelines_finder.rb +++ b/app/finders/ci/pipelines_finder.rb @@ -29,6 +29,9 @@ module Ci items = by_username(items) items = by_yaml_errors(items) items = by_updated_at(items) + + items = by_source(items) if Feature.enabled?(:pipeline_source_filter, project, default_enabled: :yaml) + sort_items(items) end @@ -87,6 +90,12 @@ module Ci end # rubocop: enable CodeReuse/ActiveRecord + def by_source(items) + return items unless ::Ci::Pipeline.sources.key?(params[:source]) + + items.with_pipeline_source(params[:source]) + end + # rubocop: disable CodeReuse/ActiveRecord def by_ref(items) if params[:ref].present? diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 9670fe13ac8..25dd6a357d5 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -64,10 +64,13 @@ module GroupsHelper .count end - def group_dependency_proxy_url(group) + def group_dependency_proxy_image_prefix(group) # The namespace path can include uppercase letters, which # Docker doesn't allow. The proxy expects it to be downcased. - "#{group_url(group).downcase}#{DependencyProxy::URL_SUFFIX}" + url = "#{group_url(group).downcase}#{DependencyProxy::URL_SUFFIX}" + + # Docker images do not include the protocol + url.partition('//').last end def group_icon_url(group, options = {}) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 53003152791..40adccead18 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -318,6 +318,7 @@ module Ci scope :created_before_id, -> (id) { where('ci_pipelines.id < ?', id) } scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) } scope :eager_load_project, -> { eager_load(project: [:route, { namespace: :route }]) } + scope :with_pipeline_source, -> (source) { where(source: source)} scope :outside_pipeline_family, ->(pipeline) do where.not(id: pipeline.same_family_pipeline_ids) diff --git a/app/views/groups/dependency_proxies/_url.html.haml b/app/views/groups/dependency_proxies/_url.html.haml index b3fb1cf05a2..a8034c50ed8 100644 --- a/app/views/groups/dependency_proxies/_url.html.haml +++ b/app/views/groups/dependency_proxies/_url.html.haml @@ -1,6 +1,6 @@ -- proxy_url = group_dependency_proxy_url(@group) +- proxy_url = group_dependency_proxy_image_prefix(@group) -%h5.prepend-top-20= _('Dependency proxy URL') +%h5.prepend-top-20= _('Dependency proxy image prefix') .row .col-lg-8.col-md-12.input-group diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb index 40d138752b4..322f92d376b 100644 --- a/app/workers/pipeline_hooks_worker.rb +++ b/app/workers/pipeline_hooks_worker.rb @@ -12,7 +12,7 @@ class PipelineHooksWorker # rubocop:disable Scalability/IdempotentWorker # rubocop: disable CodeReuse/ActiveRecord def perform(pipeline_id) - Ci::Pipeline.includes({ builds: { runner: :tags } }) + Ci::Pipeline .find_by(id: pipeline_id) .try(:execute_hooks) end diff --git a/config/feature_flags/development/pipeline_source_filter.yml b/config/feature_flags/development/pipeline_source_filter.yml new file mode 100644 index 00000000000..be24f6936e4 --- /dev/null +++ b/config/feature_flags/development/pipeline_source_filter.yml @@ -0,0 +1,8 @@ +--- +name: pipeline_source_filter +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67846 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338347 +milestone: '14.2' +type: development +group: group::pipeline execution +default_enabled: false diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 3c520515e8b..7aa1acb1c0f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -210,7 +210,7 @@ Settings.gitlab.default_projects_features['visibility_level'] = Settings.__sen Settings.gitlab['domain_allowlist'] ||= [] Settings.gitlab['import_sources'] ||= Gitlab::ImportSources.values Settings.gitlab['trusted_proxies'] ||= [] -Settings.gitlab['content_security_policy'] ||= Gitlab::ContentSecurityPolicy::ConfigLoader.default_settings_hash(Settings.gitlab['cdn_host']) +Settings.gitlab['content_security_policy'] ||= {} Settings.gitlab['allowed_hosts'] ||= [] Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml')) Settings.gitlab['impersonation_enabled'] ||= true if Settings.gitlab['impersonation_enabled'].nil? diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index c19fb65017f..3c6606066cd 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -2,11 +2,16 @@ csp_settings = Settings.gitlab.content_security_policy +csp_settings['enabled'] = Gitlab::ContentSecurityPolicy::ConfigLoader.default_enabled if csp_settings['enabled'].nil? +csp_settings['report_only'] = false if csp_settings['report_only'].nil? +csp_settings['directives'] ||= {} + if csp_settings['enabled'] + csp_settings['directives'] = ::Gitlab::ContentSecurityPolicy::ConfigLoader.default_directives if csp_settings['directives'].empty? + # See https://guides.rubyonrails.org/security.html#content-security-policy Rails.application.config.content_security_policy do |policy| - directives = csp_settings.fetch('directives', {}) - loader = ::Gitlab::ContentSecurityPolicy::ConfigLoader.new(directives) + loader = ::Gitlab::ContentSecurityPolicy::ConfigLoader.new(csp_settings['directives'].to_h) loader.load(policy) end diff --git a/doc/operations/error_tracking.md b/doc/operations/error_tracking.md index 4cbcb6d2f13..9234ca36634 100644 --- a/doc/operations/error_tracking.md +++ b/doc/operations/error_tracking.md @@ -27,16 +27,21 @@ least Maintainer [permissions](../user/permissions.md) to enable the Sentry inte 1. [Create](https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/) a new Sentry project. For each GitLab project that you want to integrate, we recommend that you create a new Sentry project. 1. [Find or generate](https://docs.sentry.io/api/auth/) a Sentry auth token for your Sentry project. Make sure to give the token at least the following scopes: `event:read`, `project:read`, and `event:write` (for resolving events). -1. In GitLab, navigate to your project's **Monitor > Error Tracking** page, and - click **Enable Error Tracking**. -1. Navigate to your project's **Settings > Monitor**. In the **Error Tracking** section, - ensure the **Active** checkbox is set. -1. In the **Sentry API URL** field, enter your Sentry hostname. For example, enter `https://sentry.example.com` if this is the address at which your Sentry instance is available. For the SaaS version of Sentry, the hostname is `https://sentry.io`. -1. In the **Auth Token** field, enter the token you previously generated. -1. Click the **Connect** button to test the connection to Sentry and populate the **Project** dropdown. -1. From the **Project** dropdown, choose a Sentry project to link to your GitLab project. -1. Click **Save changes** for the changes to take effect. -1. You can now visit **Monitor > Error Tracking** in your project's sidebar to [view a list](#error-tracking-list) of Sentry errors. +1. In GitLab, enable error tracking: + 1. On the top bar, select **Menu > Projects** and find your project. + 1. On the left sidebar, select **Monitor > Error Tracking**. + 1. Select **Enable error tracking**. +1. In GitLab, ensure error tracking is active. + 1. On the left sidebar, select **Settings > Monitor**. + 1. Expand **Error Tracking**. + 1. Ensure the **Active** checkbox is selected. +1. In the **Sentry API URL** box, enter your Sentry hostname. For example, enter `https://sentry.example.com`. For the SaaS version of Sentry, the hostname is `https://sentry.io`. +1. In the **Auth Token** box, enter the token you previously generated. +1. To test the connection to Sentry and populate the **Project** dropdown, select **Connect**. +1. From the **Project** list, choose a Sentry project to link to your GitLab project. +1. Select **Save changes**. + +You can now visit **Monitor > Error Tracking** in your project's sidebar to [view a list](#error-tracking-list) of Sentry errors. ### Enabling GitLab issues links diff --git a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_10.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_10.png deleted file mode 100644 index 72b24a3fd28..00000000000 Binary files a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_10.png and /dev/null differ diff --git a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v14_2.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v14_2.png new file mode 100644 index 00000000000..3a195a5ce8d Binary files /dev/null and b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v14_2.png differ diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md index 806bc03e30e..b799177ec5a 100644 --- a/doc/user/application_security/security_dashboard/index.md +++ b/doc/user/application_security/security_dashboard/index.md @@ -51,7 +51,7 @@ The security dashboard and vulnerability report displays information about vulne At the pipeline level, the Security section displays the vulnerabilities present in the branch of the project the pipeline ran against. -![Pipeline Security Dashboard](img/pipeline_security_dashboard_v13_10.png) +![Pipeline Security Dashboard](img/pipeline_security_dashboard_v14_2.png) Visit the page for any pipeline that ran any of the [supported reports](#supported-reports). To view the pipeline's security findings, select the **Security** tab when viewing the pipeline. diff --git a/doc/user/application_security/vulnerability_report/img/group_vulnerability_report_v13_9.png b/doc/user/application_security/vulnerability_report/img/group_vulnerability_report_v13_9.png deleted file mode 100644 index 72443180e09..00000000000 Binary files a/doc/user/application_security/vulnerability_report/img/group_vulnerability_report_v13_9.png and /dev/null differ diff --git a/doc/user/application_security/vulnerability_report/img/group_vulnerability_report_v14_2.png b/doc/user/application_security/vulnerability_report/img/group_vulnerability_report_v14_2.png new file mode 100644 index 00000000000..193efe9c386 Binary files /dev/null and b/doc/user/application_security/vulnerability_report/img/group_vulnerability_report_v14_2.png differ diff --git a/doc/user/application_security/vulnerability_report/img/project_security_dashboard_status_change_v13_10.png b/doc/user/application_security/vulnerability_report/img/project_security_dashboard_status_change_v13_10.png deleted file mode 100644 index f9f60810f20..00000000000 Binary files a/doc/user/application_security/vulnerability_report/img/project_security_dashboard_status_change_v13_10.png and /dev/null differ diff --git a/doc/user/application_security/vulnerability_report/img/project_security_dashboard_status_change_v14_2.png b/doc/user/application_security/vulnerability_report/img/project_security_dashboard_status_change_v14_2.png new file mode 100644 index 00000000000..056e051363d Binary files /dev/null and b/doc/user/application_security/vulnerability_report/img/project_security_dashboard_status_change_v14_2.png differ diff --git a/doc/user/application_security/vulnerability_report/index.md b/doc/user/application_security/vulnerability_report/index.md index da59c0fbe79..c2c2e7459ba 100644 --- a/doc/user/application_security/vulnerability_report/index.md +++ b/doc/user/application_security/vulnerability_report/index.md @@ -16,7 +16,7 @@ At all levels, the Vulnerability Report contains: - Filters for common vulnerability attributes. - Details of each vulnerability, presented in tabular layout. -![Vulnerability Report](img/group_vulnerability_report_v13_9.png) +![Vulnerability Report](img/group_vulnerability_report_v14_2.png) ## Project-level Vulnerability Report @@ -49,7 +49,7 @@ You can filter the vulnerabilities table by: |:---------|:------------------| | Status | Detected, Confirmed, Dismissed, Resolved. | | Severity | Critical, High, Medium, Low, Info, Unknown. | -| Scanner | For more details, see [Scanner filter](#scanner-filter). | +| Tool | For more details, see [Tool filter](#tool-filter). | | Project | For more details, see [Project filter](#project-filter). | | Activity | For more details, see [Activity filter](#activity-filter). | @@ -70,17 +70,17 @@ The filters' criteria are combined to show only vulnerabilities matching all cri An exception to this behavior is the Activity filter. For more details about how it works, see [Activity filter](#activity-filter). -## Scanner filter +## Tool filter -The scanner filter allows you to focus on vulnerabilities detected by selected scanners. +The tool filter allows you to focus on vulnerabilities detected by selected tools. -When using the scanner filter, you can choose: +When using the tool filter, you can choose: -- **All scanners** (default). -- Individual GitLab-provided scanners. -- Any integrated 3rd-party scanner. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229661) in GitLab 13.12. +- **All tools** (default). +- Individual GitLab-provided tools. +- Any integrated 3rd-party tool. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229661) in GitLab 13.12. -For details of each of the available scanners, see [Security scanning tools](../index.md#security-scanning-tools). +For details of each of the available tools, see [Security scanning tools](../index.md#security-scanning-tools). ### Project filter @@ -143,7 +143,7 @@ To change the status of vulnerabilities in the table: 1. Select the checkbox for each vulnerability you want to update the status of. 1. In the dropdown that appears select the desired status, then select **Change status**. -![Project Vulnerability Report](img/project_security_dashboard_status_change_v13_10.png) +![Project Vulnerability Report](img/project_security_dashboard_status_change_v14_2.png) ## Export vulnerability details diff --git a/doc/user/packages/helm_repository/index.md b/doc/user/packages/helm_repository/index.md index 87c8afc7826..f98fc352ab5 100644 --- a/doc/user/packages/helm_repository/index.md +++ b/doc/user/packages/helm_repository/index.md @@ -90,3 +90,12 @@ helm repo update To update the Helm client with the most currently available charts. See [Using Helm](https://helm.sh/docs/intro/using_helm/) for more information. + +## Troubleshooting + +### The chart is not visible in the Package Registry after uploading + +Check the [Sidekiq log](../../../administration/logs.md#sidekiqlog) +for any related errors. If you see `Validation failed: Version is invalid`, it means that the +version in your `Chart.yaml` file does not follow [Helm Chart versioning specifications](https://helm.sh/docs/topics/charts/#charts-and-versioning). +To fix the error, use the correct version syntax and upload the chart again. diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 3abeceba8f3..4d6d38f2dce 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -52,13 +52,14 @@ module API desc: 'Order pipelines' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort pipelines' + optional :source, type: String, values: ::Ci::Pipeline.sources.keys end get ':id/pipelines' do authorize! :read_pipeline, user_project authorize! :read_build, user_project pipelines = ::Ci::PipelinesFinder.new(user_project, current_user, params).execute - present paginate(pipelines), with: Entities::Ci::PipelineBasic + present paginate(pipelines), with: Entities::Ci::PipelineBasic, project: user_project end desc 'Create a new pipeline' do diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb index f4f2356c812..8086062dc9b 100644 --- a/lib/api/entities/ci/pipeline_basic.rb +++ b/lib/api/entities/ci/pipeline_basic.rb @@ -7,6 +7,8 @@ module API expose :id, :project_id, :sha, :ref, :status expose :created_at, :updated_at + expose :source, if: ->(pipeline, options) { ::Feature.enabled?(:pipeline_source_filter, options[:project], default_enabled: :yaml) } + expose :web_url do |pipeline, _options| Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline) end diff --git a/lib/gitlab/background_migration/backfill_integrations_type_new.rb b/lib/gitlab/background_migration/backfill_integrations_type_new.rb index 6a2d82aaeee..d1a939af58e 100644 --- a/lib/gitlab/background_migration/backfill_integrations_type_new.rb +++ b/lib/gitlab/background_migration/backfill_integrations_type_new.rb @@ -6,8 +6,32 @@ module Gitlab # the real class name, rather than the legacy class name in `type` # which is mapped via `Gitlab::Integrations::StiType`. class BackfillIntegrationsTypeNew - def perform(start_id, stop_id, *args) - ActiveRecord::Base.connection.execute(<<~SQL) + include Gitlab::Database::DynamicModelHelpers + + def perform(start_id, stop_id, batch_table, batch_column, sub_batch_size, pause_ms) + parent_batch_relation = define_batchable_model(batch_table) + .where(batch_column => start_id..stop_id) + + parent_batch_relation.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch| + process_sub_batch(sub_batch) + + sleep(pause_ms * 0.001) if pause_ms > 0 + end + end + + private + + def connection + ActiveRecord::Base.connection + end + + def process_sub_batch(sub_batch) + # Extract the start/stop IDs from the current sub-batch + sub_start_id, sub_stop_id = sub_batch.pluck(Arel.sql('MIN(id), MAX(id)')).first + + # This matches the mapping from the INSERT trigger added in + # db/migrate/20210721135638_add_triggers_to_integrations_type_new.rb + connection.execute(<<~SQL) WITH mapping(old_type, new_type) AS (VALUES ('AsanaService', 'Integrations::Asana'), ('AssemblaService', 'Integrations::Assembla'), @@ -53,7 +77,7 @@ module Gitlab UPDATE integrations SET type_new = mapping.new_type FROM mapping - WHERE integrations.id BETWEEN #{start_id} AND #{stop_id} + WHERE integrations.id BETWEEN #{sub_start_id} AND #{sub_stop_id} AND integrations.type = mapping.old_type SQL end diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index 2c7839f55b0..bdcedd1896d 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -7,40 +7,40 @@ module Gitlab form_action frame_ancestors frame_src img_src manifest_src media_src object_src report_uri script_src style_src worker_src).freeze - def self.default_settings_hash(cdn_host) - settings_hash = { - 'enabled' => Rails.env.development? || Rails.env.test?, - 'report_only' => false, - 'directives' => { - 'default_src' => "'self'", - 'base_uri' => "'self'", - 'connect_src' => "'self'", - 'font_src' => "'self'", - 'form_action' => "'self' https: http:", - 'frame_ancestors' => "'self'", - 'frame_src' => "'self' https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com", - 'img_src' => "'self' data: blob: http: https:", - 'manifest_src' => "'self'", - 'media_src' => "'self'", - 'script_src' => "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com", - 'style_src' => "'self' 'unsafe-inline'", - 'worker_src' => "'self' blob: data:", - 'object_src' => "'none'", - 'report_uri' => nil - } + def self.default_enabled + Rails.env.development? || Rails.env.test? + end + + def self.default_directives + directives = { + 'default_src' => "'self'", + 'base_uri' => "'self'", + 'connect_src' => "'self'", + 'font_src' => "'self'", + 'form_action' => "'self' https: http:", + 'frame_ancestors' => "'self'", + 'frame_src' => "'self' https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com", + 'img_src' => "'self' data: blob: http: https:", + 'manifest_src' => "'self'", + 'media_src' => "'self'", + 'script_src' => "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com", + 'style_src' => "'self' 'unsafe-inline'", + 'worker_src' => "'self' blob: data:", + 'object_src' => "'none'", + 'report_uri' => nil } # frame-src was deprecated in CSP level 2 in favor of child-src # CSP level 3 "undeprecated" frame-src and browsers fall back on child-src if it's missing # However Safari seems to read child-src first so we'll just keep both equal - settings_hash['directives']['child_src'] = settings_hash['directives']['frame_src'] + directives['child_src'] = directives['frame_src'] - allow_webpack_dev_server(settings_hash) if Rails.env.development? - allow_cdn(settings_hash, cdn_host) if cdn_host.present? - allow_customersdot(settings_hash) if Rails.env.development? && ENV['CUSTOMER_PORTAL_URL'].present? - allow_sentry(settings_hash) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn + allow_webpack_dev_server(directives) if Rails.env.development? + allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present? + allow_customersdot(directives) if Rails.env.development? && ENV['CUSTOMER_PORTAL_URL'].present? + allow_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn - settings_hash + directives end def initialize(csp_directives) @@ -67,37 +67,37 @@ module Gitlab arguments.strip.split(' ').map(&:strip) end - def self.allow_webpack_dev_server(settings_hash) + def self.allow_webpack_dev_server(directives) secure = Settings.webpack.dev_server['https'] host_and_port = "#{Settings.webpack.dev_server['host']}:#{Settings.webpack.dev_server['port']}" http_url = "#{secure ? 'https' : 'http'}://#{host_and_port}" ws_url = "#{secure ? 'wss' : 'ws'}://#{host_and_port}" - append_to_directive(settings_hash, 'connect_src', "#{http_url} #{ws_url}") + append_to_directive(directives, 'connect_src', "#{http_url} #{ws_url}") end - def self.allow_cdn(settings_hash, cdn_host) - append_to_directive(settings_hash, 'script_src', cdn_host) - append_to_directive(settings_hash, 'style_src', cdn_host) - append_to_directive(settings_hash, 'font_src', cdn_host) + def self.allow_cdn(directives, cdn_host) + append_to_directive(directives, 'script_src', cdn_host) + append_to_directive(directives, 'style_src', cdn_host) + append_to_directive(directives, 'font_src', cdn_host) end - def self.append_to_directive(settings_hash, directive, text) - settings_hash['directives'][directive] = "#{settings_hash['directives'][directive]} #{text}".strip + def self.append_to_directive(directives, directive, text) + directives[directive] = "#{directives[directive]} #{text}".strip end - def self.allow_customersdot(settings_hash) + def self.allow_customersdot(directives) customersdot_host = ENV['CUSTOMER_PORTAL_URL'] - append_to_directive(settings_hash, 'frame_src', customersdot_host) + append_to_directive(directives, 'frame_src', customersdot_host) end - def self.allow_sentry(settings_hash) + def self.allow_sentry(directives) sentry_dsn = Gitlab.config.sentry.clientside_dsn sentry_uri = URI(sentry_dsn) sentry_uri.user = nil - append_to_directive(settings_hash, 'connect_src', sentry_uri.to_s) + append_to_directive(directives, 'connect_src', sentry_uri.to_s) end end end diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb index b8ccef03e63..385f1e57705 100644 --- a/lib/gitlab/data_builder/pipeline.rb +++ b/lib/gitlab/data_builder/pipeline.rb @@ -19,18 +19,40 @@ module Gitlab user: pipeline.user.try(:hook_attrs), project: pipeline.project.hook_attrs(backward: false), commit: pipeline.commit.try(:hook_attrs), - builds: Gitlab::Lazy.new { pipeline.builds.latest.map(&method(:build_hook_attrs)) } + builds: Gitlab::Lazy.new do + preload_builds(pipeline, :latest_builds) + pipeline.latest_builds.map(&method(:build_hook_attrs)) + end ) end def with_retried_builds merge( - builds: Gitlab::Lazy.new { @pipeline.builds.map(&method(:build_hook_attrs)) } + builds: Gitlab::Lazy.new do + preload_builds(@pipeline, :builds) + @pipeline.builds.map(&method(:build_hook_attrs)) + end ) end private + # rubocop: disable CodeReuse/ActiveRecord + def preload_builds(pipeline, association) + ActiveRecord::Associations::Preloader.new.preload(pipeline, + { + association => { + **::Ci::Pipeline::PROJECT_ROUTE_AND_NAMESPACE_ROUTE, + runner: :tags, + job_artifacts_archive: [], + user: [], + metadata: [] + } + } + ) + end + # rubocop: enable CodeReuse/ActiveRecord + def hook_attrs(pipeline) { id: pipeline.id, diff --git a/lib/gitlab/database/async_indexes/index_creator.rb b/lib/gitlab/database/async_indexes/index_creator.rb index 82f2a921f10..00de79ec970 100644 --- a/lib/gitlab/database/async_indexes/index_creator.rb +++ b/lib/gitlab/database/async_indexes/index_creator.rb @@ -23,6 +23,8 @@ module Gitlab set_statement_timeout do connection.execute(async_index.definition) end + + log_index_info('Finished creating async index') end async_index.destroy diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3ee31ff6e59..f394359f7ac 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -663,15 +663,15 @@ msgstr "" msgid "%{labelStart}Scan Type:%{labelEnd} %{reportType}" msgstr "" -msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}" -msgstr "" - msgid "%{labelStart}Sent request:%{labelEnd} %{headers}" msgstr "" msgid "%{labelStart}Severity:%{labelEnd} %{severity}" msgstr "" +msgid "%{labelStart}Tool:%{labelEnd} %{scanner}" +msgstr "" + msgid "%{labelStart}Unmodified response:%{labelEnd} %{headers}" msgstr "" @@ -11011,10 +11011,10 @@ msgstr "" msgid "Dependency proxy" msgstr "" -msgid "Dependency proxy URL" +msgid "Dependency proxy feature is limited to public groups for now." msgstr "" -msgid "Dependency proxy feature is limited to public groups for now." +msgid "Dependency proxy image prefix" msgstr "" msgid "DependencyProxy|Toggle Dependency Proxy" @@ -28092,6 +28092,9 @@ msgstr "" msgid "Reports|Test summary results are being parsed" msgstr "" +msgid "Reports|Tool" +msgstr "" + msgid "Reports|Vulnerability" msgstr "" @@ -29757,9 +29760,6 @@ msgstr "" msgid "SecurityReports|Scan details" msgstr "" -msgid "SecurityReports|Scanner" -msgstr "" - msgid "SecurityReports|Security Dashboard" msgstr "" @@ -29826,6 +29826,9 @@ msgstr "" msgid "SecurityReports|To widen your search, change or remove filters above" msgstr "" +msgid "SecurityReports|Tool" +msgstr "" + msgid "SecurityReports|Unable to add %{invalidProjectsMessage}: %{errorMessage}" msgstr "" @@ -38904,10 +38907,10 @@ msgstr "" msgid "ciReport|All projects" msgstr "" -msgid "ciReport|All scanners" +msgid "ciReport|All severities" msgstr "" -msgid "ciReport|All severities" +msgid "ciReport|All tools" msgstr "" msgid "ciReport|Automatically apply the patch in a new branch" diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index c83f60bf8b2..f3500301e22 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -237,8 +237,13 @@ FactoryBot.define do # to the job. If `build.deployment` has already been set, it doesn't # build a new instance. environment = Gitlab::Ci::Pipeline::Seed::Environment.new(build).to_resource - build.deployment = - Gitlab::Ci::Pipeline::Seed::Deployment.new(build, environment).to_resource + + build.assign_attributes( + deployment: Gitlab::Ci::Pipeline::Seed::Deployment.new(build, environment).to_resource, + metadata_attributes: { + expanded_environment_name: environment.name + } + ) end end diff --git a/spec/finders/ci/pipelines_finder_spec.rb b/spec/finders/ci/pipelines_finder_spec.rb index 16561aa65b6..c7bd52576e8 100644 --- a/spec/finders/ci/pipelines_finder_spec.rb +++ b/spec/finders/ci/pipelines_finder_spec.rb @@ -252,6 +252,29 @@ RSpec.describe Ci::PipelinesFinder do end end + context 'when source is specified' do + let(:params) { { source: 'web' } } + let!(:web_pipeline) { create(:ci_pipeline, project: project, source: 'web') } + let!(:push_pipeline) { create(:ci_pipeline, project: project, source: 'push') } + let!(:api_pipeline) { create(:ci_pipeline, project: project, source: 'api') } + + context 'when `pipeline_source_filter` feature flag is disabled' do + before do + stub_feature_flags(pipeline_source_filter: false) + end + + it 'returns all the pipelines' do + is_expected.to contain_exactly(web_pipeline, push_pipeline, api_pipeline) + end + end + + context 'when `pipeline_source_filter` feature flag is enabled' do + it 'returns only the matched pipeline' do + is_expected.to eq([web_pipeline]) + end + end + end + describe 'ordering' do using RSpec::Parameterized::TableSyntax diff --git a/spec/fixtures/api/schemas/pipeline_schedule.json b/spec/fixtures/api/schemas/pipeline_schedule.json index 8a175ba081f..cdb4aea76da 100644 --- a/spec/fixtures/api/schemas/pipeline_schedule.json +++ b/spec/fixtures/api/schemas/pipeline_schedule.json @@ -18,6 +18,7 @@ "sha": { "type": "string" }, "ref": { "type": "string" }, "status": { "type": "string" }, + "source": { "type": "string" }, "web_url": { "type": ["string", "null"] }, "created_at": { "type": ["string", "null"], "format": "date-time" }, "updated_at": { "type": ["string", "null"], "format": "date-time" } diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 8447b92adbf..0b037a45945 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -19,11 +19,15 @@ RSpec.describe GroupsHelper do end end - describe '#group_dependency_proxy_url' do + describe '#group_dependency_proxy_image_prefix' do + let_it_be(:group) { build_stubbed(:group, path: 'GroupWithUPPERcaseLetters') } + it 'converts uppercase letters to lowercase' do - group = build_stubbed(:group, path: 'GroupWithUPPERcaseLetters') + expect(group_dependency_proxy_image_prefix(group)).to end_with("/groupwithuppercaseletters#{DependencyProxy::URL_SUFFIX}") + end - expect(group_dependency_proxy_url(group)).to end_with("/groupwithuppercaseletters#{DependencyProxy::URL_SUFFIX}") + it 'removes the protocol' do + expect(group_dependency_proxy_image_prefix(group)).not_to include('http') end end diff --git a/spec/lib/gitlab/background_migration/backfill_integrations_type_new_spec.rb b/spec/lib/gitlab/background_migration/backfill_integrations_type_new_spec.rb index eaad5f8158b..8f765a7a536 100644 --- a/spec/lib/gitlab/background_migration/backfill_integrations_type_new_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_integrations_type_new_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::BackfillIntegrationsTypeNew do + let(:migration) { described_class.new } let(:integrations) { table(:integrations) } let(:namespaced_integrations) { Gitlab::Integrations::StiType.namespaced_integrations } @@ -12,12 +13,31 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillIntegrationsTypeNew do namespaced_integrations.each_with_index do |type, i| integrations.create!(id: i + 1, type: "#{type}Service") end + + integrations.create!(id: namespaced_integrations.size + 1, type: 'LegacyService') ensure integrations.connection.execute 'ALTER TABLE integrations ENABLE TRIGGER "trigger_type_new_on_insert"' end it 'backfills `type_new` for the selected records' do - described_class.new.perform(2, 10) + # We don't want to mock `Kernel.sleep`, so instead we mock it on the migration + # class before it gets forwarded. + expect(migration).to receive(:sleep).with(0.05).exactly(5).times + + queries = ActiveRecord::QueryRecorder.new do + migration.perform(2, 10, :integrations, :id, 2, 50) + end + + expect(queries.count).to be(16) + expect(queries.log.grep(/^SELECT/).size).to be(11) + expect(queries.log.grep(/^UPDATE/).size).to be(5) + expect(queries.log.grep(/^UPDATE/).join.scan(/WHERE .*/)).to eq([ + 'WHERE integrations.id BETWEEN 2 AND 3', + 'WHERE integrations.id BETWEEN 4 AND 5', + 'WHERE integrations.id BETWEEN 6 AND 7', + 'WHERE integrations.id BETWEEN 8 AND 9', + 'WHERE integrations.id BETWEEN 10 AND 10' + ]) expect(integrations.where(id: 2..10).pluck(:type, :type_new)).to contain_exactly( ['AssemblaService', 'Integrations::Assembla'], diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 436666c3011..239eff11bf3 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do let(:policy) { ActionDispatch::ContentSecurityPolicy.new } - let(:cdn_host) { nil } let(:csp_config) do { enabled: true, @@ -20,14 +19,28 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do } end - describe '.default_settings_hash' do - let(:settings) { described_class.default_settings_hash(cdn_host) } + describe '.default_enabled' do + let(:enabled) { described_class.default_enabled } - it 'returns defaults for all keys' do - expect(settings['enabled']).to be_truthy - expect(settings['report_only']).to be_falsey + it 'is enabled' do + expect(enabled).to be_truthy + end - directives = settings['directives'] + context 'when in production' do + before do + allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production')) + end + + it 'is disabled' do + expect(enabled).to be_falsey + end + end + end + + describe '.default_directives' do + let(:directives) { described_class.default_directives } + + it 'returns default directives' do directive_names = (described_class::DIRECTIVES - ['report_uri']) directive_names.each do |directive| expect(directives.has_key?(directive)).to be_truthy @@ -39,22 +52,12 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do expect(directives['child_src']).to eq(directives['frame_src']) end - context 'when in production' do + context 'when CDN host is defined' do before do - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production')) - end - - it 'is disabled' do - expect(settings['enabled']).to be_falsey + stub_config_setting(cdn_host: 'https://example.com') end - end - - context 'when CDN host is defined' do - let(:cdn_host) { 'https://example.com' } it 'adds CDN host to CSP' do - directives = settings['directives'] - expect(directives['script_src']).to eq("'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://example.com") expect(directives['style_src']).to eq("'self' 'unsafe-inline' https://example.com") expect(directives['font_src']).to eq("'self' https://example.com") @@ -67,8 +70,6 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do end it 'adds sentry path to CSP without user' do - directives = settings['directives'] - expect(directives['connect_src']).to eq("'self' dummy://example.com/43") end end @@ -84,8 +85,6 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do end it 'does not add CUSTOMER_PORTAL_URL to CSP' do - directives = settings['directives'] - expect(directives['frame_src']).to eq("'self' https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com") end end @@ -96,8 +95,6 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do end it 'adds CUSTOMER_PORTAL_URL to CSP' do - directives = settings['directives'] - expect(directives['frame_src']).to eq("'self' https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://customers.example.com") end end diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index 4c23b24aa31..0e574c7aa84 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -131,5 +131,41 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do expect(build_environment_data[:deployment_tier]).to eq(build.persisted_environment.try(:tier)) end end + + context 'avoids N+1 database queries' do + it "with multiple builds" do + # Preparing the pipeline with the minimal builds + pipeline = create(:ci_pipeline, user: user, project: project) + create(:ci_build, user: user, project: project, pipeline: pipeline) + create(:ci_build, :deploy_to_production, :with_deployment, user: user, project: project, pipeline: pipeline) + + # We need `.to_json` as the build hook data is wrapped within `Gitlab::Lazy` + control_count = ActiveRecord::QueryRecorder.new { described_class.build(pipeline.reload).to_json }.count + + # Adding more builds to the pipeline and serializing the data again + create_list(:ci_build, 3, user: user, project: project, pipeline: pipeline) + create(:ci_build, :start_review_app, :with_deployment, user: user, project: project, pipeline: pipeline) + create(:ci_build, :stop_review_app, :with_deployment, user: user, project: project, pipeline: pipeline) + + expect { described_class.build(pipeline.reload).to_json }.not_to exceed_query_limit(control_count) + end + + it "with multiple retried builds" do + # Preparing the pipeline with the minimal builds + pipeline = create(:ci_pipeline, user: user, project: project) + create(:ci_build, :retried, user: user, project: project, pipeline: pipeline) + create(:ci_build, :deploy_to_production, :retried, :with_deployment, user: user, project: project, pipeline: pipeline) + + # We need `.to_json` as the build hook data is wrapped within `Gitlab::Lazy` + control_count = ActiveRecord::QueryRecorder.new { described_class.build(pipeline.reload).with_retried_builds.to_json }.count + + # Adding more builds to the pipeline and serializing the data again + create_list(:ci_build, 3, :retried, user: user, project: project, pipeline: pipeline) + create(:ci_build, :start_review_app, :retried, :with_deployment, user: user, project: project, pipeline: pipeline) + create(:ci_build, :stop_review_app, :retried, :with_deployment, user: user, project: project, pipeline: pipeline) + + expect { described_class.build(pipeline.reload).with_retried_builds.to_json }.not_to exceed_query_limit(control_count) + end + end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 74a476a6422..1d0b67c6902 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -263,6 +263,20 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end + describe '.with_pipeline_source' do + subject { described_class.with_pipeline_source(source) } + + let(:source) { 'web' } + + let_it_be(:push_pipeline) { create(:ci_pipeline, source: :push) } + let_it_be(:web_pipeline) { create(:ci_pipeline, source: :web) } + let_it_be(:api_pipeline) { create(:ci_pipeline, source: :api) } + + it 'contains pipelines created due to specified source' do + expect(subject).to contain_exactly(web_pipeline) + end + end + describe '.ci_sources' do subject { described_class.ci_sources } diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb index 6c0a1b2502f..640e1ee6422 100644 --- a/spec/requests/api/ci/pipelines_spec.rb +++ b/spec/requests/api/ci/pipelines_spec.rb @@ -34,7 +34,28 @@ RSpec.describe API::Ci::Pipelines do expect(json_response.first['sha']).to match(/\A\h{40}\z/) expect(json_response.first['id']).to eq pipeline.id expect(json_response.first['web_url']).to be_present - expect(json_response.first.keys).to contain_exactly(*%w[id project_id sha ref status web_url created_at updated_at]) + end + + describe 'keys in the response' do + context 'when `pipeline_source_filter` feature flag is disabled' do + before do + stub_feature_flags(pipeline_source_filter: false) + end + + it 'does not includes pipeline source' do + get api("/projects/#{project.id}/pipelines", user) + + expect(json_response.first.keys).to contain_exactly(*%w[id project_id sha ref status web_url created_at updated_at]) + end + end + + context 'when `pipeline_source_filter` feature flag is disabled' do + it 'includes pipeline source' do + get api("/projects/#{project.id}/pipelines", user) + + expect(json_response.first.keys).to contain_exactly(*%w[id project_id sha ref status web_url created_at updated_at source]) + end + end end context 'when parameter is passed' do @@ -294,6 +315,48 @@ RSpec.describe API::Ci::Pipelines do end end end + + context 'when a source is specified' do + before do + create(:ci_pipeline, project: project, source: :push) + create(:ci_pipeline, project: project, source: :web) + create(:ci_pipeline, project: project, source: :api) + end + + context 'when `pipeline_source_filter` feature flag is disabled' do + before do + stub_feature_flags(pipeline_source_filter: false) + end + + it 'returns all pipelines' do + get api("/projects/#{project.id}/pipelines", user), params: { source: 'web' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).not_to be_empty + expect(json_response.length).to be >= 3 + end + end + + context 'when `pipeline_source_filter` feature flag is enabled' do + it 'returns matched pipelines' do + get api("/projects/#{project.id}/pipelines", user), params: { source: 'web' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).not_to be_empty + json_response.each { |r| expect(r['source']).to eq('web') } + end + + context 'when source is invalid' do + it 'returns bad_request' do + get api("/projects/#{project.id}/pipelines", user), params: { source: 'invalid-source' } + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end + end end end diff --git a/spec/requests/projects/merge_requests_spec.rb b/spec/requests/projects/merge_requests_spec.rb index 1c5104b39cb..59fde803560 100644 --- a/spec/requests/projects/merge_requests_spec.rb +++ b/spec/requests/projects/merge_requests_spec.rb @@ -5,10 +5,18 @@ require 'spec_helper' RSpec.describe 'merge requests actions' do let_it_be(:project) { create(:project, :repository) } - let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } + let(:merge_request) do + create(:merge_request_with_diffs, target_project: project, + source_project: project, + assignees: [user], + reviewers: [user2]) + end + let(:user) { project.owner } + let(:user2) { create(:user) } before do + project.add_maintainer(user2) sign_in(user) end @@ -61,17 +69,11 @@ RSpec.describe 'merge requests actions' do end context 'when the merge request is updated' do - let(:user2) { create(:user) } - - before do - project.add_maintainer(user2) - end - def update_service(params) MergeRequests::UpdateService.new(project: project, current_user: user, params: params).execute(merge_request) end - context 'when the user is different' do + context 'when the logged in user is different' do before do sign_in(user2) end @@ -79,61 +81,61 @@ RSpec.describe 'merge requests actions' do it_behaves_like 'a non-cached request' end - context 'when the reviewer is changed' do + context 'when the assignee is changed' do before do - update_service(reviewer_ids: [user2.id]) + update_service( assignee_ids: [] ) end it_behaves_like 'a non-cached request' end - context 'when the assignee is changed' do + context 'when the existing assignee gets updated' do before do - update_service( assignee_ids: [user2.id] ) + user.update_attribute(:avatar, 'uploads/avatar.png') end it_behaves_like 'a non-cached request' end - context 'when the time_estimate is changed' do + context 'when the reviewer is changed' do before do - update_service(time_estimate: 7200) + update_service(reviewer_ids: []) end it_behaves_like 'a non-cached request' end - context 'when the spend_time is changed' do + context 'when the existing reviewer gets updated' do before do - update_service(spend_time: { duration: 7200, user_id: user.id, spent_at: Time.now, note_id: nil }) + user2.update_attribute(:avatar, 'uploads/avatar.png') end it_behaves_like 'a non-cached request' end - context 'when a user leaves a note' do + context 'when the time_estimate is changed' do before do - # We have 1 minute ThrottledTouch to account for. - # It's not ideal as it means that our participants cache could be stale for about a day if a new note is created by another person or gets a mention. - travel_to(Time.current + 61) do - Notes::CreateService.new(project, user2, { note: 'Looks good', noteable_type: 'MergeRequest', noteable_id: merge_request.id }).execute - end + update_service(time_estimate: 7200) end it_behaves_like 'a non-cached request' end - context 'when the email setting has changed in project' do + context 'when the spend_time is changed' do before do - project.namespace.update_attribute(:emails_disabled, true) + update_service(spend_time: { duration: 7200, user_id: user.id, spent_at: Time.now, note_id: nil }) end it_behaves_like 'a non-cached request' end - context 'when the user changes unsubscribes' do + context 'when a user leaves a note' do before do - merge_request.set_subscription(user, false, project) + # We have 1 minute ThrottledTouch to account for. + # It's not ideal as it means that our participants cache could be stale for about a day if a new note is created by another person or gets a mention. + travel_to(Time.current + 61) do + Notes::CreateService.new(project, user2, { note: 'Looks good', noteable_type: 'MergeRequest', noteable_id: merge_request.id }).execute + end end it_behaves_like 'a non-cached request' -- cgit v1.2.3