summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-21 09:08:52 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-21 09:08:52 +0000
commitea037b91577f1b645267df9e034f6da3e389626c (patch)
tree4cf78baf7034278157a4faa9d37409b0cf299a24
parent25c07d7230471a8cc7062e83662a300fb4902fce (diff)
downloadgitlab-ce-ea037b91577f1b645267df9e034f6da3e389626c.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/issue_templates/Adoption Engineering.md14
-rw-r--r--app/controllers/whats_new_controller.rb5
-rw-r--r--app/models/merge_request.rb3
-rw-r--r--app/views/admin/application_settings/general.html.haml1
-rw-r--r--app/views/admin/application_settings/integrations.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml3
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml2
-rw-r--r--app/views/layouts/header/_whats_new_dropdown_item.html.haml5
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml3
-rw-r--r--changelogs/unreleased/212322-remove-duplicate-vulnerabilities.yml5
-rw-r--r--changelogs/unreleased/21686_filter_duplicates.yml5
-rw-r--r--changelogs/unreleased/292714-allow-reviewers-to-be-updated-via-api.yml5
-rw-r--r--changelogs/unreleased/jswain_whats_new_remove_feature_flag.yml5
-rw-r--r--changelogs/unreleased/philipcunningham-create-dast-scan-model-295243.yml5
-rw-r--r--changelogs/unreleased/ui-text-protected-environments.yml5
-rw-r--r--config/metrics/counts_28d/deployments.yml5
-rw-r--r--config/metrics/counts_7d/g_project_management_issue_title_changed_weekly.yml5
-rw-r--r--config/metrics/counts_all/deployments.yml5
-rw-r--r--config/metrics/license/recorded_at.yml5
-rw-r--r--config/metrics/license/uuid.yml6
-rw-r--r--config/metrics/schema.json10
-rw-r--r--config/metrics/settings/database_adapter.yml5
-rw-r--r--db/migrate/20210111051045_create_dast_profiles.rb35
-rw-r--r--db/migrate/20210111053308_add_project_fk_for_dast_profile.rb19
-rw-r--r--db/post_migrate/20201112130710_schedule_remove_duplicate_vulnerabilities_findings.rb38
-rw-r--r--db/schema_migrations/202011121307101
-rw-r--r--db/schema_migrations/202101110510451
-rw-r--r--db/schema_migrations/202101110533081
-rw-r--r--db/structure.sql46
-rw-r--r--doc/api/merge_requests.md4
-rw-r--r--doc/development/feature_flags/development.md22
-rw-r--r--doc/development/usage_ping/metrics_dictionary.md15
-rw-r--r--doc/integration/elasticsearch.md28
-rw-r--r--doc/subscriptions/index.md16
-rw-r--r--lib/api/merge_requests.rb4
-rw-r--r--lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings.rb50
-rw-r--r--lib/gitlab/usage/metric.rb10
-rw-r--r--lib/gitlab/usage/metric_definition.rb3
-rw-r--r--locale/gitlab.pot14
-rw-r--r--spec/features/whats_new_spec.rb35
-rw-r--r--spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb135
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb9
-rw-r--r--spec/lib/gitlab/usage/metric_spec.rb8
-rw-r--r--spec/migrations/20201112130710_schedule_remove_duplicate_vulnerabilities_findings_spec.rb140
-rw-r--r--spec/models/merge_request_spec.rb11
-rw-r--r--spec/requests/api/merge_requests_spec.rb58
-rw-r--r--spec/requests/whats_new_controller_spec.rb62
47 files changed, 728 insertions, 146 deletions
diff --git a/.gitlab/issue_templates/Adoption Engineering.md b/.gitlab/issue_templates/Adoption Engineering.md
new file mode 100644
index 00000000000..01e9d0ea033
--- /dev/null
+++ b/.gitlab/issue_templates/Adoption Engineering.md
@@ -0,0 +1,14 @@
+#Design
+<!-- This should include the contexts that determine the reproducibility (stickiness) of an experiment. This means that if you want the same behavior for a user, the context would be user, or if you want all users when viewing a specific project, the context would be the project being viewed, etc. -->
+
+
+#Rollout strategy
+<!-- This is currently called A/B test, which isn't accurate for multi-variants. Let's call this rollout strategy. It should outline the percentages for variants and if there's more than one step to this, each of those steps and the timing for those steps (e.g. 30 days after initial rollout). -->
+
+#Inclusions and exclusions
+<!-- These would be the rules for which given context (and are limited to context or resolvable at experiment time details) is included or excluded from the test. An example of this would be to only run an experiment on groups less than N number of days old. -->
+
+#Segmentation
+<!-- Rules for always saying context with these criteria always get this variant. For instance, if you want to always give groups less than N number of days old the experiment experience, they are specified here. This is different from the exclusion rules above. -->
+
+#Tracking
diff --git a/app/controllers/whats_new_controller.rb b/app/controllers/whats_new_controller.rb
index cba86c65848..9d9811d792d 100644
--- a/app/controllers/whats_new_controller.rb
+++ b/app/controllers/whats_new_controller.rb
@@ -5,7 +5,6 @@ class WhatsNewController < ApplicationController
skip_before_action :authenticate_user!
- before_action :check_feature_flag
before_action :check_valid_page_param, :set_pagination_headers, unless: -> { has_version_param? }
feature_category :navigation
@@ -20,10 +19,6 @@ class WhatsNewController < ApplicationController
private
- def check_feature_flag
- render_404 unless Feature.enabled?(:whats_new_drawer, current_user)
- end
-
def check_valid_page_param
render_404 if current_page < 1
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 64b8223a1f0..725a8edbcda 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -270,8 +270,7 @@ class MergeRequest < ApplicationRecord
by_commit_sha(sha),
by_squash_commit_sha(sha),
by_merge_commit_sha(sha)
- ],
- remove_duplicates: false
+ ]
)
end
scope :by_cherry_pick_sha, -> (sha) do
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index 8f15dcac40a..794e02787f5 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -104,7 +104,6 @@
= f.submit _('Save changes'), class: "gl-button btn btn-success"
= render_if_exists 'admin/application_settings/maintenance_mode_settings_form'
-= render_if_exists 'admin/application_settings/elasticsearch_form'
= render 'admin/application_settings/gitpod'
= render 'admin/application_settings/kroki'
= render 'admin/application_settings/plantuml'
diff --git a/app/views/admin/application_settings/integrations.html.haml b/app/views/admin/application_settings/integrations.html.haml
index 6ed6709eca8..949908b09a7 100644
--- a/app/views/admin/application_settings/integrations.html.haml
+++ b/app/views/admin/application_settings/integrations.html.haml
@@ -9,7 +9,7 @@
= sprite_icon('close', css_class: 'gl-icon')
.gl-alert-body
%h4.gl-alert-title= s_('AdminSettings|Some settings have moved')
- = html_escape_once(s_('AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings &gt; General.')).html_safe
+ = html_escape_once(s_('AdminSettings|PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings &gt; General.')).html_safe
.gl-alert-actions
= link_to s_('AdminSettings|Go to General Settings'), general_admin_application_settings_path, class: 'btn gl-alert-action btn-info gl-button'
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index f7e93182ca2..ecadc9c466e 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -120,8 +120,7 @@
= sprite_icon('ellipsis_h', size: 12, css_class: 'more-icon js-navbar-toggle-right')
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
-- if ::Feature.enabled?(:whats_new_drawer, current_user)
- #whats-new-app{ data: { storage_key: whats_new_storage_key, versions: whats_new_versions, gitlab_dot_com: Gitlab.dev_env_org_or_com? } }
+#whats-new-app{ data: { storage_key: whats_new_storage_key, versions: whats_new_versions, gitlab_dot_com: Gitlab.dev_env_org_or_com? } }
- if can?(current_user, :update_user_status, current_user)
.js-set-status-modal-wrapper{ data: user_status_data }
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index 40bf45db80d..c3769dd2993 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -1,6 +1,6 @@
%ul
- if current_user_menu?(:help)
- = render_if_exists 'layouts/header/whats_new_dropdown_item'
+ = render 'layouts/header/whats_new_dropdown_item'
%li
= link_to _("Help"), help_path
%li
diff --git a/app/views/layouts/header/_whats_new_dropdown_item.html.haml b/app/views/layouts/header/_whats_new_dropdown_item.html.haml
new file mode 100644
index 00000000000..f79b741ced0
--- /dev/null
+++ b/app/views/layouts/header/_whats_new_dropdown_item.html.haml
@@ -0,0 +1,5 @@
+%li
+ %button.gl-justify-content-space-between.gl-align-items-center.js-whats-new-trigger{ type: 'button', data: { storage_key: whats_new_storage_key }, class: 'gl-display-flex!' }
+ = _("What's new")
+ %span.js-whats-new-notification-count.whats-new-notification-count
+ = whats_new_most_recent_release_items_count
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index da16be707eb..323cbcdb24d 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -260,6 +260,9 @@
= link_to general_admin_application_settings_path, title: _('General'), class: 'qa-admin-settings-general-item' do
%span
= _('General')
+
+ = render_if_exists 'layouts/nav/sidebar/advanced_search', class: 'qa-admin-settings-advanced-search'
+
- if instance_level_integrations?
= nav_link(path: ['application_settings#integrations', 'integrations#edit']) do
= link_to integrations_admin_application_settings_path, title: _('Integrations'), data: { qa_selector: 'integration_settings_link' } do
diff --git a/changelogs/unreleased/212322-remove-duplicate-vulnerabilities.yml b/changelogs/unreleased/212322-remove-duplicate-vulnerabilities.yml
new file mode 100644
index 00000000000..4e8fd48b152
--- /dev/null
+++ b/changelogs/unreleased/212322-remove-duplicate-vulnerabilities.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicates from vulnerability_occurrences
+merge_request: 49937
+author:
+type: other
diff --git a/changelogs/unreleased/21686_filter_duplicates.yml b/changelogs/unreleased/21686_filter_duplicates.yml
new file mode 100644
index 00000000000..b1e0da45806
--- /dev/null
+++ b/changelogs/unreleased/21686_filter_duplicates.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicates from related_commit_sha query
+merge_request: 51888
+author:
+type: fixed
diff --git a/changelogs/unreleased/292714-allow-reviewers-to-be-updated-via-api.yml b/changelogs/unreleased/292714-allow-reviewers-to-be-updated-via-api.yml
new file mode 100644
index 00000000000..aa491ecfdcb
--- /dev/null
+++ b/changelogs/unreleased/292714-allow-reviewers-to-be-updated-via-api.yml
@@ -0,0 +1,5 @@
+---
+title: Allow reviewers to be updated via MergeRequest API
+merge_request: 51186
+author:
+type: added
diff --git a/changelogs/unreleased/jswain_whats_new_remove_feature_flag.yml b/changelogs/unreleased/jswain_whats_new_remove_feature_flag.yml
new file mode 100644
index 00000000000..36c20c4eb09
--- /dev/null
+++ b/changelogs/unreleased/jswain_whats_new_remove_feature_flag.yml
@@ -0,0 +1,5 @@
+---
+title: Add "What's new" item to the help dropdown
+merge_request: 52020
+author:
+type: changed
diff --git a/changelogs/unreleased/philipcunningham-create-dast-scan-model-295243.yml b/changelogs/unreleased/philipcunningham-create-dast-scan-model-295243.yml
new file mode 100644
index 00000000000..2aaebc076d5
--- /dev/null
+++ b/changelogs/unreleased/philipcunningham-create-dast-scan-model-295243.yml
@@ -0,0 +1,5 @@
+---
+title: Add dast_profiles database table
+merge_request: 51296
+author:
+type: added
diff --git a/changelogs/unreleased/ui-text-protected-environments.yml b/changelogs/unreleased/ui-text-protected-environments.yml
new file mode 100644
index 00000000000..9f8fa029993
--- /dev/null
+++ b/changelogs/unreleased/ui-text-protected-environments.yml
@@ -0,0 +1,5 @@
+---
+title: Updated UI text to match style guidelines
+merge_request: 52152
+author:
+type: other
diff --git a/config/metrics/counts_28d/deployments.yml b/config/metrics/counts_28d/deployments.yml
index dabd50ef5be..85fdc407156 100644
--- a/config/metrics/counts_28d/deployments.yml
+++ b/config/metrics/counts_28d/deployments.yml
@@ -1,11 +1,8 @@
-name: deployments
+key_path: counts_monthy.deployments
description: Total deployments count for recent 28 days
value_type: integer
stage: release
status: data_available
-default_generation: generation_1
-full_path:
- generation_1: counts_monthy.deployments
milestone: 13.2
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35493
group: 'group::ops release'
diff --git a/config/metrics/counts_7d/g_project_management_issue_title_changed_weekly.yml b/config/metrics/counts_7d/g_project_management_issue_title_changed_weekly.yml
index 997263f9e30..5f8492f13c1 100644
--- a/config/metrics/counts_7d/g_project_management_issue_title_changed_weekly.yml
+++ b/config/metrics/counts_7d/g_project_management_issue_title_changed_weekly.yml
@@ -1,12 +1,9 @@
-name: g_project_management_issue_title_changed_weekly
+key_path: redis_hll_counters.issues_edit.g_project_management_issue_title_changed_weekly
description: Distinct users count that changed issue title in a group for last recent week
value_type: integer
product_category: issue_tracking
stage: plan
status: data_available
-default_generation: generation_1
-full_path:
- generation_1: redis_hll_counters.issues_edit.g_project_management_issue_title_changed_weekly
milestone: 13.6
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/229918
group: 'group::project management'
diff --git a/config/metrics/counts_all/deployments.yml b/config/metrics/counts_all/deployments.yml
index bb78e8d6144..abddb09c3dd 100644
--- a/config/metrics/counts_all/deployments.yml
+++ b/config/metrics/counts_all/deployments.yml
@@ -1,11 +1,8 @@
-name: deployments
+key_path: counts.deployments
description: Total deployments count
value_type: integer
stage: release
status: data_available
-default_generation: generation_1
-full_path:
- generation_1: counts.deployments
milestone: 8.12
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/735
group: 'group::ops release'
diff --git a/config/metrics/license/recorded_at.yml b/config/metrics/license/recorded_at.yml
index 5b2b3b37290..0ffb65de671 100644
--- a/config/metrics/license/recorded_at.yml
+++ b/config/metrics/license/recorded_at.yml
@@ -1,12 +1,9 @@
-name: recorded_at
+key_path: recorded_at
description: When the Usage Ping computation was started
value_type: string
product_category: collection
stage: growth
status: data_available
-default_generation: generation_1
-full_path:
- generation_1: recorded_at
milestone: 8.10
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/557
group: group::product analytics
diff --git a/config/metrics/license/uuid.yml b/config/metrics/license/uuid.yml
index 38e0d74fc22..86b38a26cbc 100644
--- a/config/metrics/license/uuid.yml
+++ b/config/metrics/license/uuid.yml
@@ -1,13 +1,9 @@
-name: uuid
+key_path: uuid
description: GitLab instance unique identifier
value_type: string
product_category: collection
stage: growth
status: data_available
-default_generation: generation_1
-full_path:
- generation_1: uuid
- generation_2: license.uuid
milestone: 9.1
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1521
group: group::product analytics
diff --git a/config/metrics/schema.json b/config/metrics/schema.json
index 4d1120a7d8d..634c74982cc 100644
--- a/config/metrics/schema.json
+++ b/config/metrics/schema.json
@@ -1,8 +1,8 @@
{
"type": "object",
- "required": ["name", "description", "value_type", "status", "default_generation", "full_path", "group", "time_frame", "data_source", "distribution", "tier"],
+ "required": ["key_path", "description", "value_type", "status", "group", "time_frame", "data_source", "distribution", "tier"],
"properties": {
- "name": {
+ "key_path": {
"type": "string"
},
"description": {
@@ -22,12 +22,6 @@
"type": ["string"],
"enum": ["data_available", "planned", "in_progress", "implmented"]
},
- "default_generation": {
- "type": "string"
- },
- "full_path": {
- "type": "object"
- },
"milestone": {
"type": ["number", "null"]
},
diff --git a/config/metrics/settings/database_adapter.yml b/config/metrics/settings/database_adapter.yml
index b24fc933a08..b80906ab025 100644
--- a/config/metrics/settings/database_adapter.yml
+++ b/config/metrics/settings/database_adapter.yml
@@ -1,12 +1,9 @@
-name: adapter
+key_path: database.adapter
description: This metric only returns a value of PostgreSQL in supported versions of GitLab. It could be removed from the usage ping. Historically MySQL was also supported.
value_type: string
product_category: collection
stage: growth
status: data_available
-default_generation: generation_1
-full_path:
- generation_1: database.adapter
group: group::enablement distribution
time_frame: none
data_source: database
diff --git a/db/migrate/20210111051045_create_dast_profiles.rb b/db/migrate/20210111051045_create_dast_profiles.rb
new file mode 100644
index 00000000000..f2667e1222e
--- /dev/null
+++ b/db/migrate/20210111051045_create_dast_profiles.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class CreateDastProfiles < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ table_comment = { owner: 'group::dynamic analysis', description: 'Profile used to run a DAST on-demand scan' }
+
+ create_table_with_constraints :dast_profiles, comment: table_comment.to_json do |t| # rubocop:disable Migration/AddLimitToTextColumns
+ t.references :project, null: false, foreign_key: false, index: false
+ t.references :dast_site_profile, null: false, foreign_key: { on_delete: :cascade }
+ t.references :dast_scanner_profile, null: false, foreign_key: { on_delete: :cascade }
+
+ t.timestamps_with_timezone
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ t.text :name, null: false
+ t.text :description, null: false
+ # rubocop:enable Migration/AddLimitToTextColumns
+
+ t.index [:project_id, :name], unique: true
+
+ t.text_limit :name, 255
+ t.text_limit :description, 255
+ end
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :dast_profiles
+ end
+ end
+end
diff --git a/db/migrate/20210111053308_add_project_fk_for_dast_profile.rb b/db/migrate/20210111053308_add_project_fk_for_dast_profile.rb
new file mode 100644
index 00000000000..5dc057b5f70
--- /dev/null
+++ b/db/migrate/20210111053308_add_project_fk_for_dast_profile.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddProjectFkForDastProfile < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :dast_profiles, :projects, column: :project_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :dast_profiles, column: :project_id
+ end
+ end
+end
diff --git a/db/post_migrate/20201112130710_schedule_remove_duplicate_vulnerabilities_findings.rb b/db/post_migrate/20201112130710_schedule_remove_duplicate_vulnerabilities_findings.rb
new file mode 100644
index 00000000000..d05516bd255
--- /dev/null
+++ b/db/post_migrate/20201112130710_schedule_remove_duplicate_vulnerabilities_findings.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class ScheduleRemoveDuplicateVulnerabilitiesFindings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ INDEX_NAME = 'tmp_idx_deduplicate_vulnerability_occurrences'
+
+ MIGRATION = 'RemoveDuplicateVulnerabilitiesFindings'
+ DELAY_INTERVAL = 2.minutes.to_i
+ BATCH_SIZE = 5_000
+
+ disable_ddl_transaction!
+
+ class VulnerabilitiesFinding < ActiveRecord::Base
+ include ::EachBatch
+ self.table_name = "vulnerability_occurrences"
+ end
+
+ def up
+ add_concurrent_index :vulnerability_occurrences,
+ %i[project_id report_type location_fingerprint primary_identifier_id id],
+ name: INDEX_NAME
+
+ say "Scheduling #{MIGRATION} jobs"
+ queue_background_migration_jobs_by_range_at_intervals(
+ VulnerabilitiesFinding,
+ MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+ end
+
+ def down
+ remove_concurrent_index_by_name(:vulnerability_occurrences, INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20201112130710 b/db/schema_migrations/20201112130710
new file mode 100644
index 00000000000..a13668cf3ce
--- /dev/null
+++ b/db/schema_migrations/20201112130710
@@ -0,0 +1 @@
+322d7270e942c161cc8b50b8c3f531c93b6e6e938e415c1b6010a70b630bf82e \ No newline at end of file
diff --git a/db/schema_migrations/20210111051045 b/db/schema_migrations/20210111051045
new file mode 100644
index 00000000000..842c164fc20
--- /dev/null
+++ b/db/schema_migrations/20210111051045
@@ -0,0 +1 @@
+6075e469081fcca124c0c4b485071a086545b502c398314cca05052765072caf \ No newline at end of file
diff --git a/db/schema_migrations/20210111053308 b/db/schema_migrations/20210111053308
new file mode 100644
index 00000000000..b7968a03c32
--- /dev/null
+++ b/db/schema_migrations/20210111053308
@@ -0,0 +1 @@
+a98ca25378df3fc798b6ae361b3a47b697f6b853796975221329db023cb98466 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 2cdd93c8405..9c0e7e49208 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -11589,6 +11589,30 @@ CREATE SEQUENCE custom_emoji_id_seq
ALTER SEQUENCE custom_emoji_id_seq OWNED BY custom_emoji.id;
+CREATE TABLE dast_profiles (
+ id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ dast_site_profile_id bigint NOT NULL,
+ dast_scanner_profile_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ name text NOT NULL,
+ description text NOT NULL,
+ CONSTRAINT check_5fcf73bf61 CHECK ((char_length(name) <= 255)),
+ CONSTRAINT check_c34e505c24 CHECK ((char_length(description) <= 255))
+);
+
+COMMENT ON TABLE dast_profiles IS '{"owner":"group::dynamic analysis","description":"Profile used to run a DAST on-demand scan"}';
+
+CREATE SEQUENCE dast_profiles_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE dast_profiles_id_seq OWNED BY dast_profiles.id;
+
CREATE TABLE dast_scanner_profiles (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -18574,6 +18598,8 @@ ALTER TABLE ONLY csv_issue_imports ALTER COLUMN id SET DEFAULT nextval('csv_issu
ALTER TABLE ONLY custom_emoji ALTER COLUMN id SET DEFAULT nextval('custom_emoji_id_seq'::regclass);
+ALTER TABLE ONLY dast_profiles ALTER COLUMN id SET DEFAULT nextval('dast_profiles_id_seq'::regclass);
+
ALTER TABLE ONLY dast_scanner_profiles ALTER COLUMN id SET DEFAULT nextval('dast_scanner_profiles_id_seq'::regclass);
ALTER TABLE ONLY dast_site_profiles ALTER COLUMN id SET DEFAULT nextval('dast_site_profiles_id_seq'::regclass);
@@ -19723,6 +19749,9 @@ ALTER TABLE ONLY csv_issue_imports
ALTER TABLE ONLY custom_emoji
ADD CONSTRAINT custom_emoji_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY dast_profiles
+ ADD CONSTRAINT dast_profiles_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY dast_scanner_profiles
ADD CONSTRAINT dast_scanner_profiles_pkey PRIMARY KEY (id);
@@ -21546,6 +21575,12 @@ CREATE UNIQUE INDEX index_custom_emoji_on_namespace_id_and_name ON custom_emoji
CREATE UNIQUE INDEX index_daily_build_group_report_results_unique_columns ON ci_daily_build_group_report_results USING btree (project_id, ref_path, date, group_name);
+CREATE INDEX index_dast_profiles_on_dast_scanner_profile_id ON dast_profiles USING btree (dast_scanner_profile_id);
+
+CREATE INDEX index_dast_profiles_on_dast_site_profile_id ON dast_profiles USING btree (dast_site_profile_id);
+
+CREATE UNIQUE INDEX index_dast_profiles_on_project_id_and_name ON dast_profiles USING btree (project_id, name);
+
CREATE UNIQUE INDEX index_dast_scanner_profiles_on_project_id_and_name ON dast_scanner_profiles USING btree (project_id, name);
CREATE INDEX index_dast_site_profiles_on_dast_site_id ON dast_site_profiles USING btree (dast_site_id);
@@ -23380,6 +23415,8 @@ CREATE INDEX temporary_index_vulnerabilities_on_id ON vulnerabilities USING btre
CREATE UNIQUE INDEX term_agreements_unique_index ON term_agreements USING btree (user_id, term_id);
+CREATE INDEX tmp_idx_deduplicate_vulnerability_occurrences ON vulnerability_occurrences USING btree (project_id, report_type, location_fingerprint, primary_identifier_id, id);
+
CREATE INDEX tmp_index_oauth_applications_on_id_where_trusted ON oauth_applications USING btree (id) WHERE (trusted = true);
CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING btree (id) WHERE (state <> 2);
@@ -24095,6 +24132,9 @@ ALTER TABLE ONLY merge_requests
ALTER TABLE ONLY epics
ADD CONSTRAINT fk_aa5798e761 FOREIGN KEY (closed_by_id) REFERENCES users(id) ON DELETE SET NULL;
+ALTER TABLE ONLY dast_profiles
+ ADD CONSTRAINT fk_aa76ef30e9 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY alert_management_alerts
ADD CONSTRAINT fk_aad61aedca FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE SET NULL;
@@ -24569,6 +24609,9 @@ ALTER TABLE ONLY service_desk_settings
ALTER TABLE ONLY saml_group_links
ADD CONSTRAINT fk_rails_22e312c530 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY dast_profiles
+ ADD CONSTRAINT fk_rails_23cae5abe1 FOREIGN KEY (dast_scanner_profile_id) REFERENCES dast_scanner_profiles(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY group_custom_attributes
ADD CONSTRAINT fk_rails_246e0db83a FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -25643,6 +25686,9 @@ ALTER TABLE ONLY alert_management_alert_user_mentions
ALTER TABLE ONLY snippet_statistics
ADD CONSTRAINT fk_rails_ebc283ccf1 FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE;
+ALTER TABLE ONLY dast_profiles
+ ADD CONSTRAINT fk_rails_ed1e66fbbf FOREIGN KEY (dast_site_profile_id) REFERENCES dast_site_profiles(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY project_security_settings
ADD CONSTRAINT fk_rails_ed4abe1338 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 6879b510ec8..b6ae62dc47a 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -15,6 +15,7 @@ type: reference, api
> - `reference` was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354) in GitLab 12.10 in favour of `references`.
> - `with_merge_status_recheck` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) in GitLab 13.0.
> - `reviewer_username` and `reviewer_id` were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49341) in GitLab 13.8.
+> - `reviewer_ids` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51186) in GitLab 13.8.
Every API call to merge requests must be authenticated.
@@ -1060,6 +1061,8 @@ POST /projects/:id/merge_requests
| `title` | string | yes | Title of MR. |
| `assignee_id` | integer | no | Assignee user ID. |
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
+| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. If set to `0` or left empty, there will be no assignees added. |
+| `reviewer_ids` | integer array | no | The ID of the user(s) added as a reviewer to the MR. If set to `0` or left empty, there will be no reviewers added. |
| `description` | string | no | Description of MR. Limited to 1,048,576 characters. |
| `target_project_id` | integer | no | The target project (numeric ID). |
| `labels` | string | no | Labels for MR as a comma-separated list. |
@@ -1208,6 +1211,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| `title` | string | no | Title of MR. |
| `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. |
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
+| `reviewer_ids` | integer array | no | The ID of the user(s) set as a reviewer to the MR. Set the value to `0` or provide an empty value to unset all reviewers. |
| `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
| `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. |
| `add_labels` | string | no | Comma-separated label names to add to a merge request. |
diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md
index dd732a08c72..a1da284ec8f 100644
--- a/doc/development/feature_flags/development.md
+++ b/doc/development/feature_flags/development.md
@@ -321,6 +321,28 @@ Feature.enabled?(:feature_flag, group)
Feature.enabled?(:feature_flag, user)
```
+#### Selectively disable by actor
+
+By default you cannot selectively disable a feature flag by actor.
+
+```shell
+# This will not work how you would expect.
+/chatops run feature set some_feature true
+/chatops run feature set --project=gitlab-org/gitlab some_feature false
+```
+
+However, if you add two feature flags, you can write your conditional statement in such a way that the equivalent selective disable is possible.
+
+```ruby
+Feature.enabled?(:a_feature, project) && Feature.disabled?(:a_feature_override, project)
+```
+
+```shell
+# This will enable a feature flag globally, except for gitlab-org/gitlab
+/chatops run feature set a_feature true
+/chatops run feature set --project=gitlab-org/gitlab a_feature_override true
+```
+
### Enable additional objects as actors
To use feature gates based on actors, the model needs to respond to
diff --git a/doc/development/usage_ping/metrics_dictionary.md b/doc/development/usage_ping/metrics_dictionary.md
index bae79689f3b..1faa6aa0923 100644
--- a/doc/development/usage_ping/metrics_dictionary.md
+++ b/doc/development/usage_ping/metrics_dictionary.md
@@ -15,7 +15,7 @@ We are using [JSON Schema](https://gitlab.com/gitlab-org/gitlab/-/blob/master/co
This process is meant to ensure consistent and valid metrics defined for Usage Ping. All metrics *must*:
- Comply with the definied [JSON schema](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/schema.json).
-- Have a unique `full_path` .
+- Have a unique `key_path` .
- Have an owner.
All metrics are stored in YAML files:
@@ -26,12 +26,10 @@ Each metric is definied in a separate YAML file consisting of a number of fields
| Field | Required | Additional information |
|---------------------|----------|----------------------------------------------------------------|
-| `name` | yes | |
+| `key_path` | yes | JSON key path for the metric, location in Usage Ping payload. |
| `description` | yes | |
| `value_type` | yes | |
| `status` | yes | |
-| `default_generation`| yes | Default generation path of the metric. One full_path value. (1) |
-| `full_path` | yes | Full path of the metric for one or multiple generations. Path of the metric in Usage Ping payload. (1) |
| `group` | yes | The [group](https://about.gitlab.com/handbook/product/categories/#devops-stages) that owns the metric. |
| `time_frame` | yes | `string`; may be set to a value like "7d" |
| `data_source` | yes | `string`: may be set to a value like `database` or `redis_hll`. |
@@ -43,9 +41,6 @@ Each metric is definied in a separate YAML file consisting of a number of fields
| `milestone_removed` | no | The milestone when the metric is removed. |
| `introduced_by_url` | no | The URL to the Merge Request that introduced the metric. |
-1. The default generation path is the location of the metric in the Usage Ping payload.
- The `full_path` is the list locations for multiple Usage Ping generaations.
-
### Example metric definition
The linked [`uuid`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/license/uuid.yml)
@@ -53,16 +48,12 @@ YAML file includes an example metric definition, where the `uuid` metric is the
instance unique identifier.
```yaml
-name: uuid
+key_path: uuid
description: GitLab instance unique identifier
value_type: string
product_category: collection
stage: growth
status: data_available
-default_generation: generation_1
-full_path:
- generation_1: uuid
- generation_2: license.uuid
milestone: 9.1
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1521
group: group::product intelligence
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index b1fc2573bb0..162ce1f8e76 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -175,8 +175,7 @@ instances](#indexing-large-instances) below.
To enable Advanced Search, you need to have admin access to GitLab:
-1. Navigate to **Admin Area**, then **Settings > General**
- and expand the **Advanced Search** section.
+1. Navigate to **Admin Area**, then **Settings > Advanced Search**.
NOTE:
To see the Advanced Search section, you need an active Starter
@@ -186,7 +185,7 @@ To enable Advanced Search, you need to have admin access to GitLab:
your Elasticsearch cluster. Do not enable **Search with Elasticsearch enabled**
yet.
1. Now enable **Elasticsearch indexing** in **Admin Area > Settings >
- General > Advanced Search** and click **Save changes**. This will create
+ Advanced Search** and click **Save changes**. This will create
an empty index if one does not already exist.
1. Click **Index all projects**.
1. Click **Check progress** in the confirmation message to see the status of
@@ -202,7 +201,7 @@ To enable Advanced Search, you need to have admin access to GitLab:
```
1. After the indexing has completed, enable **Search with Elasticsearch enabled** in
- **Admin Area > Settings > General > Advanced Search** and click **Save
+ **Admin Area > Settings > Advanced Search** and click **Save
changes**.
NOTE:
@@ -265,8 +264,8 @@ You can improve the language support for Chinese and Japanese languages by utili
To enable language(s) support:
1. Install the desired plugin(s), please refer to [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/plugins/7.9/installation.html) for plugins installation instructions. The plugin(s) must be installed on every node in the cluster, and each node must be restarted after installation. For a list of plugins, see the table later in this section.
-1. Navigate to the **Admin Area**, then **Settings > General**..
-1. Expand the **Advanced Search** section and locate **Custom analyzers: language support**.
+1. Navigate to the **Admin Area**, then **Settings > Advanced Search**..
+1. Locate **Custom analyzers: language support**.
1. Enable plugin(s) support for **Indexing**.
1. Click **Save changes** for the changes to take effect.
1. Trigger [Zero downtime reindexing](#zero-downtime-reindexing) or reindex everything from scratch to create a new index with updated mappings.
@@ -285,9 +284,8 @@ For guidance on what to install, see the following Elasticsearch language plugin
To disable the Elasticsearch integration:
-1. Navigate to the **Admin Area**, then **Settings > General**.
-1. Expand the **Advanced Search** section and uncheck **Elasticsearch indexing**
- and **Search with Elasticsearch enabled**.
+1. Navigate to the **Admin Area**, then **Settings > Advanced Search**.
+1. Uncheck **Elasticsearch indexing** and **Search with Elasticsearch enabled**.
1. Click **Save changes** for the changes to take effect.
1. (Optional) Delete the existing indexes:
@@ -315,7 +313,7 @@ used by the GitLab Advanced Search integration.
### Pause the indexing
-In the **Admin Area > Settings > General > Advanced Search** section, select the
+In the **Admin Area > Settings > Advanced Search** section, select the
**Pause Elasticsearch Indexing** setting, and then save your change.
With this, all updates that should happen on your Elasticsearch index will be
buffered and caught up once unpaused.
@@ -332,7 +330,7 @@ This process involves several shell commands and curl invocations, so a good
initial setup will help for later:
```shell
-# You can find this value under Admin Area > Settings > General > Advanced Search > URL
+# You can find this value under Admin Area > Settings > Advanced Search > URL
export CLUSTER_URL="http://localhost:9200"
export PRIMARY_INDEX="gitlab-production"
export SECONDARY_INDEX="gitlab-production-$(date +%s)"
@@ -433,14 +431,14 @@ To trigger the re-index from `primary` index:
1. Unpause the indexing
- Under **Admin Area > Settings > General > Advanced Search**, uncheck the **Pause Elasticsearch Indexing** setting and save.
+ Under **Admin Area > Settings > Advanced Search**, uncheck the **Pause Elasticsearch Indexing** setting and save.
### Trigger the reindex via the Advanced Search administration
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34069) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2.
> - A scheduled index deletion and the ability to cancel it was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38914) in GitLab Starter 13.3.
-Under **Admin Area > Settings > General > Advanced Search > Elasticsearch zero-downtime reindexing**, click on **Trigger cluster reindexing**.
+Under **Admin Area > Settings > Advanced Search > Elasticsearch zero-downtime reindexing**, click on **Trigger cluster reindexing**.
Reindexing can be a lengthy process depending on the size of your Elasticsearch cluster.
@@ -463,7 +461,7 @@ Sometimes, you might want to abandon the unfinished reindex job and unpause the
bundle exec rake gitlab:elastic:mark_reindex_failed RAILS_ENV=production
```
-1. Uncheck the "Pause Elasticsearch indexing" checkbox in **Admin Area > Settings > General > Advanced Search**.
+1. Uncheck the "Pause Elasticsearch indexing" checkbox in **Admin Area > Settings > Advanced Search**.
## Background migrations
@@ -823,7 +821,7 @@ There are a couple of ways to achieve that:
This is always correctly identifying whether the current project/namespace
being searched is using Elasticsearch.
-- From the admin area under **Settings > General > Advanced Search** check that the
+- From the admin area under **Settings > Advanced Search** check that the
Advanced Search settings are checked.
Those same settings there can be obtained from the Rails console if necessary:
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index 383ba471df4..8df30fa4f76 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -76,7 +76,7 @@ With the [Customers Portal](https://customers.gitlab.com/) you can:
- [Change your company details](#change-your-company-details)
- [Change your payment method](#change-your-payment-method)
- [Change the linked account](#change-the-linked-account)
-- [Change the associated namespace](#change-the-associated-namespace)
+- [Change the namespace the subscription is linked to](#change-the-linked-namespace)
- [Change customers portal account password](#change-customers-portal-account-password)
### Change your personal details
@@ -130,8 +130,7 @@ method as the default:
### Change the linked account
-To change the GitLab.com account associated with your Customers Portal
-account:
+To change the GitLab.com account linked to your Customers Portal account:
1. Log in to the
[Customers Portal](https://customers.gitlab.com/customers/sign_in).
@@ -142,15 +141,16 @@ account:
1. Log in to the [GitLab.com](https://gitlab.com) account you want to link to the Customers Portal
account.
-### Change the associated namespace
+### Change the linked namespace
-With a linked GitLab.com account:
+To change the namespace linked to a subscription:
-1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
+1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) with a
+ [linked](#change-the-linked-account) GitLab.com account.
1. Navigate to the **Manage Purchases** page.
-1. Click **Change linked namespace**.
+1. Select **Change linked namespace**.
1. Select the desired group from the **This subscription is for** dropdown.
-1. Click **Proceed to checkout**.
+1. Select **Proceed to checkout**.
Subscription charges are calculated based on the total number of users in a group, including its subgroups and nested projects. If the total number of users exceeds the number of seats in your subscription, your account is charged for the additional users.
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index ab0e9b95e4a..d31c64ce7db 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -26,6 +26,7 @@ module API
%i[
assignee_id
assignee_ids
+ reviewer_ids
description
labels
add_labels
@@ -160,7 +161,8 @@ module API
helpers do
params :optional_params do
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
- optional :assignee_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The array of user IDs to assign issue'
+ optional :assignee_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Comma-separated list of assignee ids'
+ optional :reviewer_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Comma-separated list of reviewer ids'
optional :description, type: String, desc: 'The description of the merge request'
optional :labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
optional :add_labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
diff --git a/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings.rb b/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings.rb
new file mode 100644
index 00000000000..ca61118a06c
--- /dev/null
+++ b/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+# rubocop: disable Style/Documentation
+class Gitlab::BackgroundMigration::RemoveDuplicateVulnerabilitiesFindings
+ DELETE_BATCH_SIZE = 100
+
+ # rubocop:disable Gitlab/NamespacedClass
+ class VulnerabilitiesFinding < ActiveRecord::Base
+ self.table_name = "vulnerability_occurrences"
+ end
+ # rubocop:enable Gitlab/NamespacedClass
+
+ def perform(start_id, end_id)
+ batch = VulnerabilitiesFinding.where(id: start_id..end_id)
+
+ cte = Gitlab::SQL::CTE.new(:batch, batch.select(:report_type, :location_fingerprint, :primary_identifier_id, :project_id))
+
+ query = VulnerabilitiesFinding
+ .select('batch.report_type', 'batch.location_fingerprint', 'batch.primary_identifier_id', 'batch.project_id', 'array_agg(id) as ids')
+ .distinct
+ .with(cte.to_arel)
+ .from(cte.alias_to(Arel.sql('batch')))
+ .joins(
+ %(
+ INNER JOIN
+ vulnerability_occurrences ON
+ vulnerability_occurrences.report_type = batch.report_type AND
+ vulnerability_occurrences.location_fingerprint = batch.location_fingerprint AND
+ vulnerability_occurrences.primary_identifier_id = batch.primary_identifier_id AND
+ vulnerability_occurrences.project_id = batch.project_id
+ )).group('batch.report_type', 'batch.location_fingerprint', 'batch.primary_identifier_id', 'batch.project_id')
+ .having('COUNT(*) > 1')
+
+ ids_to_delete = []
+
+ query.to_a.each do |record|
+ # We want to keep the latest finding since it might have recent metadata
+ duplicate_ids = record.ids.uniq.sort
+ duplicate_ids.pop
+ ids_to_delete.concat(duplicate_ids)
+
+ if ids_to_delete.size == DELETE_BATCH_SIZE
+ VulnerabilitiesFinding.where(id: ids_to_delete).delete_all
+ ids_to_delete.clear
+ end
+ end
+
+ VulnerabilitiesFinding.where(id: ids_to_delete).delete_all if ids_to_delete.any?
+ end
+end
diff --git a/lib/gitlab/usage/metric.rb b/lib/gitlab/usage/metric.rb
index e1648c78168..f3469209f48 100644
--- a/lib/gitlab/usage/metric.rb
+++ b/lib/gitlab/usage/metric.rb
@@ -7,16 +7,16 @@ module Gitlab
InvalidMetricError = Class.new(RuntimeError)
- attr_accessor :default_generation_path, :value
+ attr_accessor :key_path, :value
- validates :default_generation_path, presence: true
+ validates :key_path, presence: true
def definition
- self.class.definitions[default_generation_path]
+ self.class.definitions[key_path]
end
- def unflatten_default_path
- unflatten(default_generation_path.split('.'), value)
+ def unflatten_key_path
+ unflatten(key_path.split('.'), value)
end
class << self
diff --git a/lib/gitlab/usage/metric_definition.rb b/lib/gitlab/usage/metric_definition.rb
index 96e572bb3db..dea29b4465f 100644
--- a/lib/gitlab/usage/metric_definition.rb
+++ b/lib/gitlab/usage/metric_definition.rb
@@ -13,9 +13,8 @@ module Gitlab
@attributes = opts
end
- # The key is defined by default_generation and full_path
def key
- full_path[default_generation.to_sym]
+ key_path
end
def to_h
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 978d471d41d..ffdb0ea9e4b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2023,9 +2023,6 @@ msgstr ""
msgid "AdminSettings|Disable feed token"
msgstr ""
-msgid "AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings &gt; General."
-msgstr ""
-
msgid "AdminSettings|Enable shared runners for new projects"
msgstr ""
@@ -2047,6 +2044,9 @@ msgstr ""
msgid "AdminSettings|No required pipeline"
msgstr ""
+msgid "AdminSettings|PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings &gt; General."
+msgstr ""
+
msgid "AdminSettings|Required pipeline configuration"
msgstr ""
@@ -23073,6 +23073,9 @@ msgstr ""
msgid "ProtectedEnvironment|Environment"
msgstr ""
+msgid "ProtectedEnvironment|Only specified users can execute deployments in a protected environment."
+msgstr ""
+
msgid "ProtectedEnvironment|Protect"
msgstr ""
@@ -23082,9 +23085,6 @@ msgstr ""
msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
msgstr ""
-msgid "ProtectedEnvironment|Protecting an environment restricts the users who can execute deployments."
-msgstr ""
-
msgid "ProtectedEnvironment|Select an environment"
msgstr ""
@@ -23094,7 +23094,7 @@ msgstr ""
msgid "ProtectedEnvironment|Select users to deploy and manage Feature Flag settings"
msgstr ""
-msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
+msgid "ProtectedEnvironment|There are currently no protected environments. Protect an environment with this form."
msgstr ""
msgid "ProtectedEnvironment|Unprotect"
diff --git a/spec/features/whats_new_spec.rb b/spec/features/whats_new_spec.rb
new file mode 100644
index 00000000000..7c5625486f5
--- /dev/null
+++ b/spec/features/whats_new_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "renders a `whats new` dropdown item", :js do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'shows notification dot and count and removes it once viewed' do
+ visit root_dashboard_path
+
+ page.within '.header-help' do
+ expect(page).to have_selector('.notification-dot', visible: true)
+
+ find('.header-help-dropdown-toggle').click
+
+ expect(page).to have_button(text: "What's new")
+ expect(page).to have_selector('.js-whats-new-notification-count')
+
+ find('button', text: "What's new").click
+ end
+
+ find('.whats-new-drawer .gl-drawer-close-button').click
+ find('.header-help-dropdown-toggle').click
+
+ page.within '.header-help' do
+ expect(page).not_to have_selector('.notification-dot', visible: true)
+ expect(page).to have_button(text: "What's new")
+ expect(page).not_to have_selector('.js-whats-new-notification-count')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb b/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb
new file mode 100644
index 00000000000..47e1d4620cd
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::RemoveDuplicateVulnerabilitiesFindings do
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:users) { table(:users) }
+ let(:user) { create_user! }
+ let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
+ let(:scanners) { table(:vulnerability_scanners) }
+ let!(:scanner) { scanners.create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
+ let!(:scanner2) { scanners.create!(project_id: project.id, external_id: 'test 2', name: 'test scanner 2') }
+ let!(:scanner3) { scanners.create!(project_id: project.id, external_id: 'test 3', name: 'test scanner 3') }
+ let!(:unrelated_scanner) { scanners.create!(project_id: project.id, external_id: 'unreleated_scanner', name: 'unrelated scanner') }
+ let(:vulnerabilities) { table(:vulnerabilities) }
+ let(:vulnerability_findings) { table(:vulnerability_occurrences) }
+ let(:vulnerability_identifiers) { table(:vulnerability_identifiers) }
+ let(:vulnerability_identifier) do
+ vulnerability_identifiers.create!(
+ project_id: project.id,
+ external_type: 'vulnerability-identifier',
+ external_id: 'vulnerability-identifier',
+ fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
+ name: 'vulnerability identifier')
+ end
+
+ let!(:first_finding) do
+ create_finding!(
+ uuid: "test1",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:first_duplicate) do
+ create_finding!(
+ uuid: "test2",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner2.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:second_duplicate) do
+ create_finding!(
+ uuid: "test3",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner3.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:unrelated_finding) do
+ create_finding!(
+ uuid: "unreleated_finding",
+ vulnerability_id: nil,
+ report_type: 1,
+ location_fingerprint: 'random_location_fingerprint',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: unrelated_scanner.id,
+ project_id: project.id
+ )
+ end
+
+ subject { described_class.new.perform(first_finding.id, unrelated_finding.id) }
+
+ before do
+ stub_const("#{described_class}::DELETE_BATCH_SIZE", 1)
+ end
+
+ it "removes entries which would result in duplicate UUIDv5" do
+ expect(vulnerability_findings.count).to eq(4)
+
+ expect { subject }.to change { vulnerability_findings.count }.from(4).to(2)
+
+ expect(vulnerability_findings.pluck(:id)).to eq([second_duplicate.id, unrelated_finding.id])
+ end
+
+ private
+
+ def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
+ vulnerabilities.create!(
+ project_id: project_id,
+ author_id: author_id,
+ title: title,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type
+ )
+ end
+
+ # rubocop:disable Metrics/ParameterLists
+ def create_finding!(
+ vulnerability_id:, project_id:, scanner_id:, primary_identifier_id:,
+ name: "test", severity: 7, confidence: 7, report_type: 0,
+ project_fingerprint: '123qweasdzxc', location_fingerprint: 'test',
+ metadata_version: 'test', raw_metadata: 'test', uuid: 'test')
+ vulnerability_findings.create!(
+ vulnerability_id: vulnerability_id,
+ project_id: project_id,
+ name: name,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type,
+ project_fingerprint: project_fingerprint,
+ scanner_id: scanner_id,
+ primary_identifier_id: vulnerability_identifier.id,
+ location_fingerprint: location_fingerprint,
+ metadata_version: metadata_version,
+ raw_metadata: raw_metadata,
+ uuid: uuid
+ )
+ end
+ # rubocop:enable Metrics/ParameterLists
+
+ def create_user!(name: "Example User", email: "user@example.com", user_type: nil, created_at: Time.zone.now, confirmed_at: Time.zone.now)
+ users.create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 0,
+ user_type: user_type,
+ confirmed_at: confirmed_at
+ )
+ end
+end
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index e101f837324..c50760317f5 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -5,17 +5,13 @@ require 'spec_helper'
RSpec.describe Gitlab::Usage::MetricDefinition do
let(:attributes) do
{
- name: 'uuid',
description: 'GitLab instance unique identifier',
value_type: 'string',
product_category: 'collection',
stage: 'growth',
status: 'data_available',
default_generation: 'generation_1',
- full_path: {
- generation_1: 'uuid',
- generation_2: 'license.uuid'
- },
+ key_path: 'uuid',
group: 'group::product analytics',
time_frame: 'none',
data_source: 'database',
@@ -44,12 +40,11 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
using RSpec::Parameterized::TableSyntax
where(:attribute, :value) do
- :name | nil
:description | nil
:value_type | nil
:value_type | 'test'
:status | nil
- :default_generation | nil
+ :key_path | nil
:group | nil
:time_frame | nil
:time_frame | '29d'
diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb
index 40671d980d6..d4a789419a4 100644
--- a/spec/lib/gitlab/usage/metric_spec.rb
+++ b/spec/lib/gitlab/usage/metric_spec.rb
@@ -4,15 +4,15 @@ require 'spec_helper'
RSpec.describe Gitlab::Usage::Metric do
describe '#definition' do
- it 'returns generation_1 metric definiton' do
- expect(described_class.new(default_generation_path: 'uuid').definition).to be_an(Gitlab::Usage::MetricDefinition)
+ it 'returns key_path metric definiton' do
+ expect(described_class.new(key_path: 'uuid').definition).to be_an(Gitlab::Usage::MetricDefinition)
end
end
describe '#unflatten_default_path' do
using RSpec::Parameterized::TableSyntax
- where(:default_generation_path, :value, :expected_hash) do
+ where(:key_path, :value, :expected_hash) do
'uuid' | nil | { uuid: nil }
'uuid' | '1111' | { uuid: '1111' }
'counts.issues' | nil | { counts: { issues: nil } }
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::Usage::Metric do
end
with_them do
- subject { described_class.new(default_generation_path: default_generation_path, value: value).unflatten_default_path }
+ subject { described_class.new(key_path: key_path, value: value).unflatten_key_path }
it { is_expected.to eq(expected_hash) }
end
diff --git a/spec/migrations/20201112130710_schedule_remove_duplicate_vulnerabilities_findings_spec.rb b/spec/migrations/20201112130710_schedule_remove_duplicate_vulnerabilities_findings_spec.rb
new file mode 100644
index 00000000000..ff27bdcf12d
--- /dev/null
+++ b/spec/migrations/20201112130710_schedule_remove_duplicate_vulnerabilities_findings_spec.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20201112130710_schedule_remove_duplicate_vulnerabilities_findings.rb')
+
+RSpec.describe ScheduleRemoveDuplicateVulnerabilitiesFindings, :migration do
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:users) { table(:users) }
+ let(:user) { create_user! }
+ let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
+ let(:scanners) { table(:vulnerability_scanners) }
+ let!(:scanner) { scanners.create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
+ let!(:scanner2) { scanners.create!(project_id: project.id, external_id: 'test 2', name: 'test scanner 2') }
+ let!(:scanner3) { scanners.create!(project_id: project.id, external_id: 'test 3', name: 'test scanner 3') }
+ let!(:unrelated_scanner) { scanners.create!(project_id: project.id, external_id: 'unreleated_scanner', name: 'unrelated scanner') }
+ let(:vulnerabilities) { table(:vulnerabilities) }
+ let(:vulnerability_findings) { table(:vulnerability_occurrences) }
+ let(:vulnerability_identifiers) { table(:vulnerability_identifiers) }
+ let(:vulnerability_identifier) do
+ vulnerability_identifiers.create!(
+ project_id: project.id,
+ external_type: 'vulnerability-identifier',
+ external_id: 'vulnerability-identifier',
+ fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
+ name: 'vulnerability identifier')
+ end
+
+ let!(:first_finding) do
+ create_finding!(
+ uuid: "test1",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:first_duplicate) do
+ create_finding!(
+ uuid: "test2",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner2.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:second_duplicate) do
+ create_finding!(
+ uuid: "test3",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner3.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:unrelated_finding) do
+ create_finding!(
+ uuid: "unreleated_finding",
+ vulnerability_id: nil,
+ report_type: 1,
+ location_fingerprint: 'random_location_fingerprint',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: unrelated_scanner.id,
+ project_id: project.id
+ )
+ end
+
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 1)
+ end
+
+ around do |example|
+ freeze_time { Sidekiq::Testing.fake! { example.run } }
+ end
+
+ it 'schedules background migration' do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(4)
+ expect(described_class::MIGRATION).to be_scheduled_migration(first_finding.id, first_finding.id)
+ expect(described_class::MIGRATION).to be_scheduled_migration(first_duplicate.id, first_duplicate.id)
+ expect(described_class::MIGRATION).to be_scheduled_migration(second_duplicate.id, second_duplicate.id)
+ expect(described_class::MIGRATION).to be_scheduled_migration(unrelated_finding.id, unrelated_finding.id)
+ end
+
+ private
+
+ def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
+ vulnerabilities.create!(
+ project_id: project_id,
+ author_id: author_id,
+ title: title,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type
+ )
+ end
+
+ # rubocop:disable Metrics/ParameterLists
+ def create_finding!(
+ vulnerability_id:, project_id:, scanner_id:, primary_identifier_id:,
+ name: "test", severity: 7, confidence: 7, report_type: 0,
+ project_fingerprint: '123qweasdzxc', location_fingerprint: 'test',
+ metadata_version: 'test', raw_metadata: 'test', uuid: 'test')
+ vulnerability_findings.create!(
+ vulnerability_id: vulnerability_id,
+ project_id: project_id,
+ name: name,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type,
+ project_fingerprint: project_fingerprint,
+ scanner_id: scanner_id,
+ primary_identifier_id: vulnerability_identifier.id,
+ location_fingerprint: location_fingerprint,
+ metadata_version: metadata_version,
+ raw_metadata: raw_metadata,
+ uuid: uuid
+ )
+ end
+ # rubocop:enable Metrics/ParameterLists
+
+ def create_user!(name: "Example User", email: "user@example.com", user_type: nil, created_at: Time.now, confirmed_at: Time.now)
+ users.create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 0,
+ user_type: user_type,
+ confirmed_at: confirmed_at
+ )
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index b8a3f3bef6e..094bb370100 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -451,6 +451,17 @@ RSpec.describe MergeRequest, factory_default: :keep do
it { is_expected.to be_empty }
end
+
+ context 'when commit is part of the merge request and a squash commit at the same time' do
+ let!(:merge_request) { create(:merge_request, :with_diffs) }
+ let(:sha) { merge_request.commits.first.id }
+
+ before do
+ merge_request.update!(squash_commit_sha: sha)
+ end
+
+ it { is_expected.to eq([merge_request]) }
+ end
end
describe '.by_cherry_pick_sha' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 3a3eae73932..c7d2f50fde2 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1736,6 +1736,36 @@ RSpec.describe API::MergeRequests do
end
end
+ context 'accepts reviewer_ids' do
+ let(:params) do
+ {
+ title: 'Test merge request',
+ source_branch: 'feature_conflict',
+ target_branch: 'master',
+ author_id: user.id,
+ reviewer_ids: [user2.id]
+ }
+ end
+
+ it 'creates a new merge request with a reviewer' do
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['title']).to eq('Test merge request')
+ expect(json_response['reviewers'].first['name']).to eq(user2.name)
+ end
+
+ it 'creates a new merge request with no reviewer' do
+ params[:reviewer_ids] = []
+
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['title']).to eq('Test merge request')
+ expect(json_response['reviewers']).to be_empty
+ end
+ end
+
context 'between branches projects' do
context 'different labels' do
let(:params) do
@@ -2081,6 +2111,34 @@ RSpec.describe API::MergeRequests do
it_behaves_like 'issuable update endpoint' do
let(:entity) { merge_request }
end
+
+ context 'accepts reviewer_ids' do
+ let(:params) do
+ {
+ title: 'Updated merge request',
+ reviewer_ids: [user2.id]
+ }
+ end
+
+ it 'adds a reviewer to the existing merge request' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['title']).to eq('Updated merge request')
+ expect(json_response['reviewers'].first['name']).to eq(user2.name)
+ end
+
+ it 'removes a reviewer from the existing merge request' do
+ merge_request.reviewers = [user2]
+ params[:reviewer_ids] = []
+
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['title']).to eq('Updated merge request')
+ expect(json_response['reviewers']).to be_empty
+ end
+ end
end
describe "POST /projects/:id/merge_requests/:merge_request_iid/context_commits" do
diff --git a/spec/requests/whats_new_controller_spec.rb b/spec/requests/whats_new_controller_spec.rb
index 8005d38dbb0..d91a0685bd0 100644
--- a/spec/requests/whats_new_controller_spec.rb
+++ b/spec/requests/whats_new_controller_spec.rb
@@ -7,59 +7,41 @@ RSpec.describe WhatsNewController do
let(:item) { double(:item) }
let(:highlights) { double(:highlight, items: [item], map: [item].map, next_page: 2) }
- context 'with whats_new_drawer feature enabled' do
- before do
- stub_feature_flags(whats_new_drawer: true)
- end
-
- context 'with no page param' do
- it 'responds with paginated data and headers' do
- allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(highlights)
- allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
+ context 'with no page param' do
+ it 'responds with paginated data and headers' do
+ allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(highlights)
+ allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
- get whats_new_path, xhr: true
+ get whats_new_path, xhr: true
- expect(response.body).to eq(highlights.items.to_json)
- expect(response.headers['X-Next-Page']).to eq(2)
- end
+ expect(response.body).to eq(highlights.items.to_json)
+ expect(response.headers['X-Next-Page']).to eq(2)
end
+ end
- context 'with page param' do
- it 'passes the page parameter' do
- expect(ReleaseHighlight).to receive(:paginated).with(page: 2).and_call_original
-
- get whats_new_path(page: 2), xhr: true
- end
-
- it 'returns a 404 if page param is negative' do
- get whats_new_path(page: -1), xhr: true
+ context 'with page param' do
+ it 'passes the page parameter' do
+ expect(ReleaseHighlight).to receive(:paginated).with(page: 2).and_call_original
- expect(response).to have_gitlab_http_status(:not_found)
- end
+ get whats_new_path(page: 2), xhr: true
end
- context 'with version param' do
- it 'returns items without pagination headers' do
- allow(ReleaseHighlight).to receive(:for_version).with(version: '42').and_return(highlights)
- allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
+ it 'returns a 404 if page param is negative' do
+ get whats_new_path(page: -1), xhr: true
- get whats_new_path(version: 42), xhr: true
-
- expect(response.body).to eq(highlights.items.to_json)
- expect(response.headers['X-Next-Page']).to be_nil
- end
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
- context 'with whats_new_drawer feature disabled' do
- before do
- stub_feature_flags(whats_new_drawer: false)
- end
+ context 'with version param' do
+ it 'returns items without pagination headers' do
+ allow(ReleaseHighlight).to receive(:for_version).with(version: '42').and_return(highlights)
+ allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
- it 'returns a 404' do
- get whats_new_path, xhr: true
+ get whats_new_path(version: 42), xhr: true
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to eq(highlights.items.to_json)
+ expect(response.headers['X-Next-Page']).to be_nil
end
end
end