summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-28 12:14:07 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-28 12:14:07 +0000
commit22ecb1e3fc02bb923c3e9941b1baa849348a036f (patch)
treec01d9e91564f50e790a63c71675dd0f6e7735153
parent5eab6dcdd923ca375b86d6993f20a3e37dbd7a51 (diff)
downloadgitlab-ce-22ecb1e3fc02bb923c3e9941b1baa849348a036f.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml9
-rw-r--r--.gitlab/ci/rails/rspec-foss-impact.gitlab-ci.yml.erb48
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml1
-rw-r--r--.rubocop_todo/layout/line_length.yml1
-rw-r--r--.rubocop_todo/lint/unused_block_argument.yml1
-rw-r--r--.rubocop_todo/style/symbol_proc.yml1
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/issues/create_merge_request_dropdown.js1
-rw-r--r--app/controllers/concerns/notes_actions.rb3
-rw-r--r--app/helpers/sidebars_helper.rb2
-rw-r--r--app/models/namespaces/traversal/linear.rb14
-rw-r--r--app/models/note.rb3
-rw-r--r--app/models/uploads/fog.rb13
-rw-r--r--app/services/ci/create_pipeline_service.rb1
-rw-r--r--app/services/notes/create_service.rb1
-rw-r--r--app/views/admin/dev_ops_report/show.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml7
-rw-r--r--config/feature_flags/development/ci_hooks_pre_get_sources_script.yml8
-rw-r--r--config/feature_flags/development/use_traversal_ids_for_ancestors.yml2
-rw-r--r--db/docs/elastic_reindexing_slices.yml2
-rw-r--r--db/docs/elasticsearch_indexed_projects.yml2
-rw-r--r--doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md2
-rw-r--r--doc/ci/yaml/index.md13
-rw-r--r--doc/drawers/advanced_search_syntax.md1
-rw-r--r--doc/integration/advanced_search/elasticsearch.md2
-rw-r--r--doc/user/search/advanced_search.md13
-rw-r--r--lib/api/entities/ci/job_request/response.rb4
-rw-r--r--lib/gitlab/ci/config/entry/job.rb6
-rw-r--r--lib/gitlab/database/migrations/test_batched_background_runner.rb2
-rw-r--r--lib/sidebars/concerns/super_sidebar_panel.rb53
-rw-r--r--lib/sidebars/menu.rb11
-rw-r--r--lib/sidebars/menu_item.rb6
-rw-r--r--lib/sidebars/projects/menus/issues_menu.rb15
-rw-r--r--lib/sidebars/projects/menus/merge_requests_menu.rb10
-rw-r--r--lib/sidebars/projects/menus/project_information_menu.rb10
-rw-r--r--lib/sidebars/projects/menus/scope_menu.rb9
-rw-r--r--lib/sidebars/projects/menus/wiki_menu.rb7
-rw-r--r--lib/sidebars/projects/panel.rb9
-rw-r--r--lib/sidebars/projects/super_sidebar_menus/plan_menu.rb19
-rw-r--r--lib/sidebars/projects/super_sidebar_panel.rb41
-rw-r--r--lib/sidebars/static_menu.rb13
-rw-r--r--lib/sidebars/uncategorized_menu.rb19
-rw-r--r--locale/gitlab.pot27
-rwxr-xr-xscripts/generate-rspec-foss-impact-pipeline66
-rwxr-xr-xscripts/generate_rspec_pipeline.rb176
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb14
-rw-r--r--spec/features/groups/new_group_page_spec.rb42
-rw-r--r--spec/features/projects/new_project_spec.rb40
-rw-r--r--spec/frontend/issues/create_merge_request_dropdown_spec.js8
-rw-r--r--spec/helpers/sidebars_helper_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb21
-rw-r--r--spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb21
-rw-r--r--spec/lib/sidebars/concerns/super_sidebar_panel_spec.rb141
-rw-r--r--spec/lib/sidebars/menu_spec.rb16
-rw-r--r--spec/lib/sidebars/projects/panel_spec.rb6
-rw-r--r--spec/lib/sidebars/projects/super_sidebar_menus/plan_menu_spec.rb12
-rw-r--r--spec/lib/sidebars/projects/super_sidebar_panel_spec.rb47
-rw-r--r--spec/lib/sidebars/static_menu_spec.rb36
-rw-r--r--spec/lib/sidebars/uncategorized_menu_spec.rb12
-rw-r--r--spec/models/namespace_spec.rb31
-rw-r--r--spec/models/uploads/fog_spec.rb87
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb13
-rw-r--r--spec/scripts/generate_rspec_pipeline_spec.rb198
-rw-r--r--spec/services/ci/create_pipeline_service/scripts_spec.rb25
-rw-r--r--spec/support/helpers/stub_object_storage.rb4
-rw-r--r--spec/support/rspec_order_todo.yml1
69 files changed, 1203 insertions, 242 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9fa296be455..3b22e2fc21e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -154,7 +154,7 @@ variables:
JUNIT_RETRY_FILE: rspec/junit_rspec-retry.xml
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/report-master.json
RSPEC_CHANGED_FILES_PATH: rspec/changed_files.txt
- RSPEC_FOSS_IMPACT_PIPELINE_YML: rspec-foss-impact-pipeline.yml
+ RSPEC_FOSS_IMPACT_PIPELINE_TEMPLATE_YML: .gitlab/ci/rails/rspec-foss-impact.gitlab-ci.yml.erb
RSPEC_LAST_RUN_RESULTS_FILE: rspec/rspec_last_run_results.txt
RSPEC_MATCHING_JS_FILES_PATH: rspec/js_matching_files.txt
RSPEC_MATCHING_TESTS_PATH: rspec/matching_tests.txt
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 188e3519772..ee55239fb4c 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -781,13 +781,14 @@ rspec-foss-impact:pipeline-generate:
extends:
- .rails:rules:rspec-foss-impact
stage: prepare
- needs: ["detect-tests"]
+ needs: ["detect-tests", "retrieve-tests-metadata"]
script:
- - scripts/generate-rspec-foss-impact-pipeline "${RSPEC_MATCHING_TESTS_FOSS_PATH}" "${RSPEC_FOSS_IMPACT_PIPELINE_YML}"
+ - scripts/generate_rspec_pipeline.rb -f "${RSPEC_MATCHING_TESTS_FOSS_PATH}" -t "${RSPEC_FOSS_IMPACT_PIPELINE_TEMPLATE_YML}" -k "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
+ - cat "${RSPEC_FOSS_IMPACT_PIPELINE_TEMPLATE_YML}.yml"
artifacts:
expire_in: 1 day
paths:
- - $RSPEC_FOSS_IMPACT_PIPELINE_YML
+ - "${RSPEC_FOSS_IMPACT_PIPELINE_TEMPLATE_YML}.yml"
rspec-foss-impact:trigger:
extends:
@@ -810,7 +811,7 @@ rspec-foss-impact:trigger:
yaml_variables: true
pipeline_variables: true
include:
- - artifact: $RSPEC_FOSS_IMPACT_PIPELINE_YML
+ - artifact: "${RSPEC_FOSS_IMPACT_PIPELINE_TEMPLATE_YML}.yml"
job: rspec-foss-impact:pipeline-generate
fail-pipeline-early:
diff --git a/.gitlab/ci/rails/rspec-foss-impact.gitlab-ci.yml.erb b/.gitlab/ci/rails/rspec-foss-impact.gitlab-ci.yml.erb
index eb54fa25875..02b7d61a4fa 100644
--- a/.gitlab/ci/rails/rspec-foss-impact.gitlab-ci.yml.erb
+++ b/.gitlab/ci/rails/rspec-foss-impact.gitlab-ci.yml.erb
@@ -21,7 +21,7 @@ dont-interrupt-me:
script:
- echo "This jobs makes sure this pipeline won't be interrupted! See https://docs.gitlab.com/ee/ci/yaml/#interruptible."
-rspec foss-impact:
+.base-rspec-foss-impact:
extends: .rspec-base-pg12-as-if-foss
needs:
- pipeline: $PARENT_PIPELINE_ID
@@ -37,9 +37,6 @@ rspec foss-impact:
variables:
RSPEC_TESTS_FILTER_FILE: "${RSPEC_MATCHING_TESTS_FOSS_PATH}"
RSPEC_TESTS_MAPPING_ENABLED: "true"
-<% if Integer(parallel_value) > 1 %>
- parallel: <%= parallel_value %>
-<% end %>
script:
- !reference [.base-script, script]
- rspec_paralellized_job "--tag ~quarantine --tag ~level:migration --tag ~zoekt"
@@ -48,3 +45,46 @@ rspec foss-impact:
paths:
- "${RSPEC_MATCHING_TESTS_FOSS_PATH}"
- tmp/capybara/
+
+<% if rspec_files_per_test_level[:migration][:files].size > 0 %>
+rspec migration foss-impact:
+ extends: .base-rspec-foss-impact
+<% if rspec_files_per_test_level[:migration][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:migration][:parallelization] %>
+<% end %>
+ script:
+ - !reference [.base-script, script]
+ - rspec_paralellized_job "--tag ~quarantine --tag ~zoekt"
+<% end %>
+
+<% if rspec_files_per_test_level[:background_migration][:files].size > 0 %>
+rspec background_migration foss-impact:
+ extends: .base-rspec-foss-impact
+<% if rspec_files_per_test_level[:background_migration][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:background_migration][:parallelization] %>
+<% end %>
+<% end %>
+
+<% if rspec_files_per_test_level[:unit][:files].size > 0 %>
+rspec unit foss-impact:
+ extends: .base-rspec-foss-impact
+<% if rspec_files_per_test_level[:unit][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:unit][:parallelization] %>
+<% end %>
+<% end %>
+
+<% if rspec_files_per_test_level[:integration][:files].size > 0 %>
+rspec integration foss-impact:
+ extends: .base-rspec-foss-impact
+<% if rspec_files_per_test_level[:integration][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:integration][:parallelization] %>
+<% end %>
+<% end %>
+
+<% if rspec_files_per_test_level[:system][:files].size > 0 %>
+rspec system foss-impact:
+ extends: .base-rspec-foss-impact
+<% if rspec_files_per_test_level[:system][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:system][:parallelization] %>
+<% end %>
+<% end %>
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index be2b77ef81e..63c0b722055 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -1403,7 +1403,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/features/admin/admin_emails_spec.rb'
- 'ee/spec/features/admin/admin_settings_spec.rb'
- 'ee/spec/features/billings/billing_plans_spec.rb'
- - 'ee/spec/features/billings/extend_reactivate_trial_spec.rb'
- 'ee/spec/features/boards/boards_spec.rb'
- 'ee/spec/features/boards/swimlanes/epics_swimlanes_drag_drop_spec.rb'
- 'ee/spec/features/burndown_charts_spec.rb'
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 12683ec3831..30c47ed2ed0 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -1582,7 +1582,6 @@ Layout/LineLength:
- 'ee/spec/features/admin/groups/admin_subscription_alerts_spec.rb'
- 'ee/spec/features/admin/subscriptions/admin_views_subscription_spec.rb'
- 'ee/spec/features/billings/billing_plans_spec.rb'
- - 'ee/spec/features/billings/extend_reactivate_trial_spec.rb'
- 'ee/spec/features/boards/scoped_issue_board_spec.rb'
- 'ee/spec/features/boards/sidebar_spec.rb'
- 'ee/spec/features/boards/swimlanes/epics_swimlanes_drag_drop_spec.rb'
diff --git a/.rubocop_todo/lint/unused_block_argument.yml b/.rubocop_todo/lint/unused_block_argument.yml
index 188b57db7a7..b9013f03bfa 100644
--- a/.rubocop_todo/lint/unused_block_argument.yml
+++ b/.rubocop_todo/lint/unused_block_argument.yml
@@ -138,7 +138,6 @@ Lint/UnusedBlockArgument:
- 'ee/spec/factories/protected_environments.rb'
- 'ee/spec/factories/slack_integrations.rb'
- 'ee/spec/factories/users.rb'
- - 'ee/spec/features/billings/extend_reactivate_trial_spec.rb'
- 'ee/spec/features/groups/group_settings_spec.rb'
- 'ee/spec/graphql/mutations/dast/profiles/update_spec.rb'
- 'ee/spec/graphql/resolvers/analytics/contribution_analytics/contributions_resolver_spec.rb'
diff --git a/.rubocop_todo/style/symbol_proc.yml b/.rubocop_todo/style/symbol_proc.yml
index e3bde84c7b8..bc4ecaa5400 100644
--- a/.rubocop_todo/style/symbol_proc.yml
+++ b/.rubocop_todo/style/symbol_proc.yml
@@ -105,7 +105,6 @@ Style/SymbolProc:
- 'ee/lib/gitlab/geo/oauth/logout_state.rb'
- 'ee/spec/elastic/migrate/20220118150500_delete_orphaned_commits_spec.rb'
- 'ee/spec/factories/issues.rb'
- - 'ee/spec/features/billings/extend_reactivate_trial_spec.rb'
- 'ee/spec/features/billings/qrtly_reconciliation_alert_spec.rb'
- 'ee/spec/helpers/ee/geo_helper_spec.rb'
- 'ee/spec/helpers/ee/registrations_helper_spec.rb'
diff --git a/Gemfile b/Gemfile
index 13c119420ea..3bf40a6aba0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -158,7 +158,7 @@ gem 'fog-rackspace', '~> 0.1.1'
# We may want to update this dependency if this is ever addressed upstream, e.g. via
# https://github.com/aliyun/aliyun-oss-ruby-sdk/pull/93
gem 'fog-aliyun', '~> 0.4'
-gem 'gitlab-fog-azure-rm', '~> 1.4.0', require: 'fog/azurerm'
+gem 'gitlab-fog-azure-rm', '~> 1.7.0', require: 'fog/azurerm'
# for Google storage
gem 'google-cloud-storage', '~> 1.44.0'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 7c4dfd8bd84..c3dfd696b68 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -202,7 +202,7 @@
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
{"name":"gitlab-dangerfiles","version":"3.7.0","platform":"ruby","checksum":"35c5bc42e60c575ab5701192ca2384ab414b14c2963602b39e143b1aaeb7e54d"},
{"name":"gitlab-experiment","version":"0.7.1","platform":"ruby","checksum":"166dddb3aa83428bcaa93c35684ed01dc4d61f321fd2ae40b020806dc54a7824"},
-{"name":"gitlab-fog-azure-rm","version":"1.4.0","platform":"ruby","checksum":"af4163c32b028aa5208814a3f4765a5817d50527e6c61931f766bf18a2e0eb7e"},
+{"name":"gitlab-fog-azure-rm","version":"1.7.0","platform":"ruby","checksum":"969c67943c54ad4c259a6acd040493f13922fbdf2211bb4eca00e71505263dc2"},
{"name":"gitlab-labkit","version":"0.30.1","platform":"ruby","checksum":"bdedbd86014c83dfd6a50d20dbc1709697bba2bb9e3666383e5f28cbd312b113"},
{"name":"gitlab-license","version":"2.2.1","platform":"ruby","checksum":"39fcf6be8b2887df8afe01b5dcbae8d08b7c5d937ff56b0fb40484a8c4f02d30"},
{"name":"gitlab-mail_room","version":"0.0.9","platform":"ruby","checksum":"6700374b5c0aa9d9ad4e711aeb677f0b7d415a6d01d3baa699efab25349d851c"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 6067d9009bc..e6e4c005945 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -577,7 +577,7 @@ GEM
gitlab-experiment (0.7.1)
activesupport (>= 3.0)
request_store (>= 1.0)
- gitlab-fog-azure-rm (1.4.0)
+ gitlab-fog-azure-rm (1.7.0)
azure-storage-blob (~> 2.0)
azure-storage-common (~> 2.0)
fog-core (= 2.1.0)
@@ -1678,7 +1678,7 @@ DEPENDENCIES
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 3.7.0)
gitlab-experiment (~> 0.7.1)
- gitlab-fog-azure-rm (~> 1.4.0)
+ gitlab-fog-azure-rm (~> 1.7.0)
gitlab-labkit (~> 0.30.1)
gitlab-license (~> 2.2.1)
gitlab-mail_room (~> 0.0.9)
diff --git a/app/assets/javascripts/issues/create_merge_request_dropdown.js b/app/assets/javascripts/issues/create_merge_request_dropdown.js
index 977a505437d..27ba94c8381 100644
--- a/app/assets/javascripts/issues/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/issues/create_merge_request_dropdown.js
@@ -569,6 +569,7 @@ export default class CreateMergeRequestDropdown {
pathReplacement,
);
+ this.wrapperEl.dataset.createBranchPath = this.createBranchPath;
this.wrapperEl.dataset.createMrPath = this.createMrPath;
}
}
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 512dbf0de5d..06b9c901e4a 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -45,7 +45,8 @@ module NotesActions
respond_to do |format|
format.json do
json = {
- commands_changes: @note.commands_changes&.slice(:emoji_award, :time_estimate, :spend_time)
+ commands_changes: @note.commands_changes&.slice(:emoji_award, :time_estimate, :spend_time),
+ command_names: @note.command_names
}
if @note.persisted? && return_discussion?
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 577352bb377..b6901d011ab 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -86,7 +86,7 @@ module SidebarsHelper
when 'project'
context = project_sidebar_context(project, user, current_ref, ref_type: ref_type,
route_is_active: method(:active_nav_link?))
- Sidebars::Projects::Panel.new(context)
+ Sidebars::Projects::SuperSidebarPanel.new(context)
when 'group'
context = group_sidebar_context(group, user, route_is_active: method(:active_nav_link?))
Sidebars::Groups::Panel.new(context)
diff --git a/app/models/namespaces/traversal/linear.rb b/app/models/namespaces/traversal/linear.rb
index 1aa59c4f1fe..9ce52e52eeb 100644
--- a/app/models/namespaces/traversal/linear.rb
+++ b/app/models/namespaces/traversal/linear.rb
@@ -215,6 +215,16 @@ module Namespaces
hierarchy_order == :desc ? traversal_ids : traversal_ids.reverse
end
+ def parent=(obj)
+ super(obj)
+ set_traversal_ids
+ end
+
+ def parent_id=(id)
+ super(id)
+ set_traversal_ids
+ end
+
private
attr_accessor :transient_traversal_ids
@@ -232,6 +242,8 @@ module Namespaces
end
def set_traversal_ids
+ return if id.blank?
+
# This is a temporary guard and will be removed.
return if is_a?(Namespaces::ProjectNamespace)
@@ -242,7 +254,7 @@ module Namespaces
end
# Clear root_ancestor memo if changed.
- if read_attribute(traversal_ids)&.first != transient_traversal_ids.first
+ if read_attribute(:traversal_ids)&.first != transient_traversal_ids.first
clear_memoization(:root_ancestor)
end
diff --git a/app/models/note.rb b/app/models/note.rb
index a64f7311725..6f6b1c5f52b 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -60,6 +60,9 @@ class Note < ApplicationRecord
# Attribute used to store the attributes that have been changed by quick actions.
attr_writer :commands_changes
+ # Attribute used to store the quick action command names.
+ attr_accessor :command_names
+
# Attribute used to determine whether keep_around_commits will be skipped for diff notes.
attr_accessor :skip_keep_around_commits
diff --git a/app/models/uploads/fog.rb b/app/models/uploads/fog.rb
index 5d57b644dbe..d2b8eab9f0d 100644
--- a/app/models/uploads/fog.rb
+++ b/app/models/uploads/fog.rb
@@ -21,7 +21,9 @@ module Uploads
private
def delete_object(key)
- connection.delete_object(bucket_name, key)
+ return unless available?
+
+ connection.delete_object(bucket_name, object_key(key))
# So far, only GoogleCloudStorage raises an exception when the file is not found.
# Other providers support idempotent requests and does not raise an error
@@ -35,11 +37,16 @@ module Uploads
end
def bucket_name
- return unless available?
-
object_store.remote_directory
end
+ def object_key(key)
+ # We allow administrators to create "sub buckets" by setting a prefix.
+ # This makes it possible to deploy GitLab with only one object storage
+ # bucket. This mirrors the implementation in app/uploaders/object_storage.rb.
+ File.join([object_store.bucket_prefix, key].compact)
+ end
+
def connection
return unless available?
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 8a0148d9faf..7cad7e8301c 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -7,7 +7,6 @@ module Ci
LOG_MAX_DURATION_THRESHOLD = 3.seconds
LOG_MAX_PIPELINE_SIZE = 2_000
LOG_MAX_CREATION_THRESHOLD = 20.seconds
-
SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build,
Gitlab::Ci::Pipeline::Chain::Build::Associations,
Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index f5efc480fef..c9f414f3605 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -54,6 +54,7 @@ module Notes
content, update_params, message, command_names = quick_actions_service.execute(note, quick_action_options)
only_commands = content.empty?
note.note = content
+ note.command_names = command_names
yield(only_commands)
diff --git a/app/views/admin/dev_ops_report/show.html.haml b/app/views/admin/dev_ops_report/show.html.haml
index a2425b93ad3..d92d13260fe 100644
--- a/app/views/admin/dev_ops_report/show.html.haml
+++ b/app/views/admin/dev_ops_report/show.html.haml
@@ -1,5 +1,5 @@
- page_title _('DevOps Reports')
-- add_page_specific_style 'page_bundles/dev_ops_report'
+- add_page_specific_style 'page_bundles/dev_ops_reports'
.container
.gl-mt-3
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 0fd0a97a658..d03e24d2457 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -2,8 +2,11 @@
- @left_sidebar = true
.layout-page.hide-when-top-nav-responsive-open{ class: page_with_sidebar_class }
- if show_super_sidebar?
- - sidebar_panel = super_sidebar_nav_panel(nav: nav, user: current_user, group: @group, project: @project, current_ref: current_ref, ref_type: @ref_type)
- - sidebar_data = super_sidebar_context(current_user, group: @group, project: @project, panel: sidebar_panel).to_json
+ -# Render the parent group sidebar while creating a new subgroup/project, see GroupsController#new.
+ - group = @parent_group || @group
+
+ - sidebar_panel = super_sidebar_nav_panel(nav: nav, user: current_user, group: group, project: @project, current_ref: current_ref, ref_type: @ref_type)
+ - sidebar_data = super_sidebar_context(current_user, group: group, project: @project, panel: sidebar_panel).to_json
%aside.js-super-sidebar.super-sidebar.super-sidebar-loading{ data: { root_path: root_path, sidebar: sidebar_data, toggle_new_nav_endpoint: profile_preferences_url } }
- if display_whats_new?
diff --git a/config/feature_flags/development/ci_hooks_pre_get_sources_script.yml b/config/feature_flags/development/ci_hooks_pre_get_sources_script.yml
deleted file mode 100644
index 42afd4235cc..00000000000
--- a/config/feature_flags/development/ci_hooks_pre_get_sources_script.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ci_hooks_pre_get_sources_script
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102332
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381840
-milestone: '15.6'
-type: development
-group: group::pipeline authoring
-default_enabled: false
diff --git a/config/feature_flags/development/use_traversal_ids_for_ancestors.yml b/config/feature_flags/development/use_traversal_ids_for_ancestors.yml
index 4a89aac140d..f0d24b8eff0 100644
--- a/config/feature_flags/development/use_traversal_ids_for_ancestors.yml
+++ b/config/feature_flags/development/use_traversal_ids_for_ancestors.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334952
milestone: '13.12'
type: development
group: group::workspace
-default_enabled: false
+default_enabled: true
diff --git a/db/docs/elastic_reindexing_slices.yml b/db/docs/elastic_reindexing_slices.yml
index 84e42b16d57..9b8cc69e73b 100644
--- a/db/docs/elastic_reindexing_slices.yml
+++ b/db/docs/elastic_reindexing_slices.yml
@@ -3,7 +3,7 @@ table_name: elastic_reindexing_slices
classes:
- Elastic::ReindexingSlice
feature_categories:
-- application_performance
+- global_search
description: TODO
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55681
milestone: '13.12'
diff --git a/db/docs/elasticsearch_indexed_projects.yml b/db/docs/elasticsearch_indexed_projects.yml
index 17e2e116fdd..17fc5d0f779 100644
--- a/db/docs/elasticsearch_indexed_projects.yml
+++ b/db/docs/elasticsearch_indexed_projects.yml
@@ -3,7 +3,7 @@ table_name: elasticsearch_indexed_projects
classes:
- ElasticsearchIndexedProject
feature_categories:
-- application_performance
+- global_search
description: TODO
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9861
milestone: '11.10'
diff --git a/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md b/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md
index 61396108d41..3e86d30df1d 100644
--- a/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md
+++ b/doc/architecture/blueprints/ci_data_decay/pipeline_partitioning.md
@@ -811,5 +811,7 @@ DRIs:
| Product Leadership | Jackie Porter, Director of Product Management |
| Engineering Leadership | Caroline Simpson, Engineering Manager / Cheryl Li, Senior Engineering Manager |
| Lead Engineer | Marius Bobin, Senior Backend Engineer |
+| Senior Engineer | Maxime Orefice, Senior Backend Engineer |
+| Senior Engineer | Tianwen Chen, Senior Backend Engineer |
<!-- vale gitlab.Spelling = YES -->
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index 0d6cad4f86d..9a4c1711516 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -1941,12 +1941,9 @@ rspec:
### `hooks`
-> Introduced in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`.
-The feature is not ready for production use.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381840) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default.
+> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/381840) in GitLab 15.9.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/381840-link) in GitLab 15.10. Feature flag `ci_hooks_pre_get_sources_script` removed.
Use `hooks` to specify lists of commands to execute on the runner
at certain stages of job execution, like before retrieving the Git repository.
@@ -1960,7 +1957,9 @@ at certain stages of job execution, like before retrieving the Git repository.
#### `hooks:pre_get_sources_script`
-> Introduced in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381840) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default.
+> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/381840) in GitLab 15.9.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/381840-link) in GitLab 15.10. Feature flag `ci_hooks_pre_get_sources_script` removed.
Use `hooks:pre_get_sources_script` to specify a list of commands to execute on the runner
before retrieving the Git repository and any submodules. You can use it
diff --git a/doc/drawers/advanced_search_syntax.md b/doc/drawers/advanced_search_syntax.md
index 7556c8bdfaf..6e732bd3175 100644
--- a/doc/drawers/advanced_search_syntax.md
+++ b/doc/drawers/advanced_search_syntax.md
@@ -13,6 +13,7 @@ source: /doc/user/search/advanced_search.md
| Syntax | Description | Example |
|--------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| `"` | Exact search | [`"gem sidekiq"`](https://gitlab.com/search?group_id=9970&project_id=278964&scope=blobs&search=%22gem+sidekiq%22) |
+| `~` | Fuzzy search | [`J~ Doe`](https://gitlab.com/search?scope=users&search=j%7E+doe) |
| <code>&#124;</code> | Or | [<code>display &#124; banner</code>](https://gitlab.com/search?group_id=9970&project_id=278964&scope=blobs&search=display+%7C+banner) |
| `+` | And | [`display +banner`](https://gitlab.com/search?group_id=9970&project_id=278964&repository_ref=&scope=blobs&search=display+%2Bbanner&snippets=) |
| `-` | Exclude | [`display -banner`](https://gitlab.com/search?group_id=9970&project_id=278964&scope=blobs&search=display+-banner) |
diff --git a/doc/integration/advanced_search/elasticsearch.md b/doc/integration/advanced_search/elasticsearch.md
index b2ca9346982..31684de026a 100644
--- a/doc/integration/advanced_search/elasticsearch.md
+++ b/doc/integration/advanced_search/elasticsearch.md
@@ -31,7 +31,7 @@ before we remove them.
### OpenSearch version requirements
-| GitLab version | Elasticsearch version |
+| GitLab version | OpenSearch version |
|-----------------------|--------------------------|
| GitLab 15.0 or later | OpenSearch 1.x or later |
diff --git a/doc/user/search/advanced_search.md b/doc/user/search/advanced_search.md
index ae9722bca9b..4f595af7ba9 100644
--- a/doc/user/search/advanced_search.md
+++ b/doc/user/search/advanced_search.md
@@ -44,6 +44,7 @@ Advanced Search uses [Elasticsearch syntax](https://www.elastic.co/guide/en/elas
| Syntax | Description | Example |
|--------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| `"` | Exact search | [`"gem sidekiq"`](https://gitlab.com/search?group_id=9970&project_id=278964&scope=blobs&search=%22gem+sidekiq%22) |
+| `~` | Fuzzy search | [`J~ Doe`](https://gitlab.com/search?scope=users&search=j%7E+doe) |
| <code>&#124;</code> | Or | [<code>display &#124; banner</code>](https://gitlab.com/search?group_id=9970&project_id=278964&scope=blobs&search=display+%7C+banner) |
| `+` | And | [`display +banner`](https://gitlab.com/search?group_id=9970&project_id=278964&repository_ref=&scope=blobs&search=display+%2Bbanner&snippets=) |
| `-` | Exclude | [`display -banner`](https://gitlab.com/search?group_id=9970&project_id=278964&scope=blobs&search=display+-banner) |
@@ -52,6 +53,16 @@ Advanced Search uses [Elasticsearch syntax](https://www.elastic.co/guide/en/elas
| `#` | Issue ID | [`#23456`](https://gitlab.com/search?snippets=&scope=issues&repository_ref=&search=%2323456&group_id=9970&project_id=278964) |
| `!` | Merge request ID | [`!23456`](https://gitlab.com/search?snippets=&scope=merge_requests&repository_ref=&search=%2123456&group_id=9970&project_id=278964) |
+### Refining user search
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/388409) in GitLab 15.10 [with a flag](../../administration/feature_flags.md) named `user_search_simple_query_string`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `user_search_simple_query_string`.
+On GitLab.com, this feature is not available.
+
+In user search, a [fuzzy query](https://www.elastic.co/guide/en/elasticsearch/reference/7.2/query-dsl-fuzzy-query.html) is used by default. You can refine your search with [Elasticsearch syntax](#syntax).
+
### Code search
| Syntax | Description | Example |
@@ -83,7 +94,7 @@ Advanced Search uses [Elasticsearch syntax](https://www.elastic.co/guide/en/elas
- The search query must not contain any of the following characters:
```plaintext
- . , : ; / ` ' = ? $ & ^ | ~ < > ( ) { } [ ] @
+ . , : ; / ` ' = ? $ & ^ | < > ( ) { } [ ] @
```
- Search results show only the first match in a file.
diff --git a/lib/api/entities/ci/job_request/response.rb b/lib/api/entities/ci/job_request/response.rb
index cfdbeed79b6..e07bba1e850 100644
--- a/lib/api/entities/ci/job_request/response.rb
+++ b/lib/api/entities/ci/job_request/response.rb
@@ -23,9 +23,7 @@ module API
expose :runner_variables, as: :variables
expose :steps, using: Entities::Ci::JobRequest::Step
- expose :runtime_hooks, as: :hooks,
- using: Entities::Ci::JobRequest::Hook,
- if: ->(job) { ::Feature.enabled?(:ci_hooks_pre_get_sources_script, job.project) }
+ expose :runtime_hooks, as: :hooks, using: Entities::Ci::JobRequest::Hook
expose :image, using: Entities::Ci::JobRequest::Image
expose :services, using: Entities::Ci::JobRequest::Service
expose :artifacts, using: Entities::Ci::JobRequest::Artifacts
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 7c49b59a7f0..2390ba05916 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -164,7 +164,7 @@ module Gitlab
artifacts: artifacts_value,
release: release_value,
after_script: after_script_value,
- hooks: hooks_pre_get_sources_script_enabled? ? hooks_value : nil,
+ hooks: hooks_value,
ignore: ignored?,
allow_failure_criteria: allow_failure_criteria,
needs: needs_defined? ? needs_value : nil,
@@ -194,10 +194,6 @@ module Gitlab
allow_failure_value
end
-
- def hooks_pre_get_sources_script_enabled?
- YamlProcessor::FeatureFlags.enabled?(:ci_hooks_pre_get_sources_script)
- end
end
end
end
diff --git a/lib/gitlab/database/migrations/test_batched_background_runner.rb b/lib/gitlab/database/migrations/test_batched_background_runner.rb
index 01fdba22c19..af853c933ba 100644
--- a/lib/gitlab/database/migrations/test_batched_background_runner.rb
+++ b/lib/gitlab/database/migrations/test_batched_background_runner.rb
@@ -27,7 +27,7 @@ module Gitlab
table_max_value = define_batchable_model(migration.table_name, connection: connection)
.maximum(migration.column_name)
- largest_batch_start = table_max_value - migration.batch_size
+ largest_batch_start = [table_max_value - migration.batch_size, smallest_batch_start].max
# variance is the portion of the batch range that we shrink between variance * 0 and variance * 1
# to pick actual batches to sample.
diff --git a/lib/sidebars/concerns/super_sidebar_panel.rb b/lib/sidebars/concerns/super_sidebar_panel.rb
new file mode 100644
index 00000000000..9303d91c0e7
--- /dev/null
+++ b/lib/sidebars/concerns/super_sidebar_panel.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module Concerns
+ # Contains helper methods aid conversion of a "normal" panel
+ # into a Super Sidebar Panel
+ module SuperSidebarPanel
+ # Picks an element from the given list and adds it to the current menus
+ # Used for menus which behave the same in the old nav and Supersidebar
+ def pick_from_old_menus(old_menus, element)
+ add_menu(remove_element(old_menus, element))
+ end
+
+ def transform_old_menus(current_menus, *old_menus)
+ old_menus.each do |menu|
+ next unless menu.render?
+
+ menu.renderable_items.each { |item| add_menu_item_to_super_sidebar_parent(current_menus, item) }
+
+ menu_item_args = menu.serialize_as_menu_item_args
+
+ next if menu_item_args.nil?
+
+ add_menu_item_to_super_sidebar_parent(
+ current_menus, ::Sidebars::MenuItem.new(**menu_item_args)
+ )
+ end
+ end
+
+ private
+
+ # Finds a menu_items super sidebar parent and adds the item to that menu
+ # Handles:
+ # - menu_item.super_sidebar_before, adding before a certain item
+ # - parent == nil, or parent not being part of the panel:
+ # we assume that the menu item hasn't been categorized yet
+ # - parent == ::Sidebars::NilMenuItem, the item explicitly is supposed to be removed
+ def add_menu_item_to_super_sidebar_parent(menus, menu_item)
+ parent = menu_item.super_sidebar_parent || ::Sidebars::UncategorizedMenu
+ return if parent == ::Sidebars::NilMenuItem
+
+ idx = index_of(menus, parent) || index_of(menus, ::Sidebars::UncategorizedMenu)
+ return unless idx
+
+ if menu_item.super_sidebar_before
+ menus[idx].insert_item_before(menu_item.super_sidebar_before, menu_item)
+ else
+ menus[idx].add_item(menu_item)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/menu.rb b/lib/sidebars/menu.rb
index d0fd8212671..defc9848bf9 100644
--- a/lib/sidebars/menu.rb
+++ b/lib/sidebars/menu.rb
@@ -122,6 +122,17 @@ module Sidebars
end
end
+ # Sometimes we want to convert a top-level Menu (e.g. Wiki/Snippets)
+ # to a MenuItem. This serializer is used in order to enable that conversion
+ def serialize_as_menu_item_args
+ {
+ title: title,
+ link: link,
+ active_routes: active_routes,
+ container_html_options: container_html_options
+ }
+ end
+
private
override :index_of
diff --git a/lib/sidebars/menu_item.rb b/lib/sidebars/menu_item.rb
index 9cdde9acc0d..0e50c704695 100644
--- a/lib/sidebars/menu_item.rb
+++ b/lib/sidebars/menu_item.rb
@@ -4,11 +4,11 @@ module Sidebars
class MenuItem
include ::Sidebars::Concerns::LinkWithHtmlOptions
- attr_reader :title, :link, :active_routes, :item_id, :container_html_options, :sprite_icon, :sprite_icon_html_options, :hint_html_options, :has_pill, :pill_count
+ attr_reader :title, :link, :active_routes, :item_id, :container_html_options, :sprite_icon, :sprite_icon_html_options, :hint_html_options, :has_pill, :pill_count, :super_sidebar_parent, :super_sidebar_before
alias_method :has_pill?, :has_pill
# rubocop: disable Metrics/ParameterLists
- def initialize(title:, link:, active_routes:, item_id: nil, container_html_options: {}, sprite_icon: nil, sprite_icon_html_options: {}, hint_html_options: {}, has_pill: false, pill_count: nil)
+ def initialize(title:, link:, active_routes:, item_id: nil, container_html_options: {}, sprite_icon: nil, sprite_icon_html_options: {}, hint_html_options: {}, has_pill: false, pill_count: nil, super_sidebar_parent: nil, super_sidebar_before: nil)
@title = title
@link = link
@active_routes = active_routes
@@ -19,6 +19,8 @@ module Sidebars
@hint_html_options = hint_html_options
@has_pill = has_pill
@pill_count = pill_count
+ @super_sidebar_before = super_sidebar_before
+ @super_sidebar_parent = super_sidebar_parent
end
# rubocop: enable Metrics/ParameterLists
diff --git a/lib/sidebars/projects/menus/issues_menu.rb b/lib/sidebars/projects/menus/issues_menu.rb
index 51eea3d850d..21d42fa3879 100644
--- a/lib/sidebars/projects/menus/issues_menu.rb
+++ b/lib/sidebars/projects/menus/issues_menu.rb
@@ -68,6 +68,16 @@ module Sidebars
}
end
+ override :serialize_as_menu_item_args
+ def serialize_as_menu_item_args
+ super.merge({
+ sprite_icon: sprite_icon,
+ pill_count: pill_count,
+ has_pill: has_pill?,
+ super_sidebar_parent: ::Sidebars::StaticMenu
+ })
+ end
+
private
def show_issues_menu_items?
@@ -78,6 +88,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('List'),
link: project_issues_path(context.project),
+ super_sidebar_parent: ::Sidebars::NilMenuItem,
active_routes: { path: 'projects/issues#index' },
container_html_options: { aria: { label: _('Issues') } },
item_id: :issue_list
@@ -90,6 +101,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: title,
link: project_boards_path(context.project),
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::PlanMenu,
active_routes: { controller: :boards },
item_id: :boards
)
@@ -99,6 +111,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Service Desk'),
link: service_desk_project_issues_path(context.project),
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::PlanMenu,
active_routes: { path: 'issues#service_desk' },
item_id: :service_desk
)
@@ -108,6 +121,8 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Milestones'),
link: project_milestones_path(context.project),
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::PlanMenu,
+ super_sidebar_before: :service_desk,
active_routes: { controller: :milestones },
item_id: :milestones
)
diff --git a/lib/sidebars/projects/menus/merge_requests_menu.rb b/lib/sidebars/projects/menus/merge_requests_menu.rb
index 3e543872d36..1695d759ed2 100644
--- a/lib/sidebars/projects/menus/merge_requests_menu.rb
+++ b/lib/sidebars/projects/menus/merge_requests_menu.rb
@@ -64,6 +64,16 @@ module Sidebars
{ controller: ['projects/merge_requests', :milestones] }
end
end
+
+ override :serialize_as_menu_item_args
+ def serialize_as_menu_item_args
+ super.merge({
+ sprite_icon: sprite_icon,
+ pill_count: pill_count,
+ has_pill: has_pill?,
+ super_sidebar_parent: ::Sidebars::StaticMenu
+ })
+ end
end
end
end
diff --git a/lib/sidebars/projects/menus/project_information_menu.rb b/lib/sidebars/projects/menus/project_information_menu.rb
index 44b94ee3522..020de2ff65f 100644
--- a/lib/sidebars/projects/menus/project_information_menu.rb
+++ b/lib/sidebars/projects/menus/project_information_menu.rb
@@ -33,12 +33,18 @@ module Sidebars
'project'
end
+ override :serialize_as_menu_item_args
+ def serialize_as_menu_item_args
+ nil
+ end
+
private
def activity_menu_item
::Sidebars::MenuItem.new(
title: _('Activity'),
link: activity_project_path(context.project),
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::PlanMenu,
active_routes: { path: 'projects#activity' },
item_id: :activity,
container_html_options: { class: 'shortcuts-project-activity' }
@@ -53,6 +59,8 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Labels'),
link: project_labels_path(context.project),
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::PlanMenu,
+ super_sidebar_before: :activity,
active_routes: { controller: :labels },
item_id: :labels
)
@@ -66,6 +74,8 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Members'),
link: project_project_members_path(context.project),
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::PlanMenu,
+ super_sidebar_before: :labels,
active_routes: { controller: :project_members },
item_id: :members,
container_html_options: {
diff --git a/lib/sidebars/projects/menus/scope_menu.rb b/lib/sidebars/projects/menus/scope_menu.rb
index 35502c7ea09..77da695d49c 100644
--- a/lib/sidebars/projects/menus/scope_menu.rb
+++ b/lib/sidebars/projects/menus/scope_menu.rb
@@ -39,6 +39,15 @@ module Sidebars
def render?
true
end
+
+ override :serialize_as_menu_item_args
+ def serialize_as_menu_item_args
+ super.merge({
+ title: _('Project overview'),
+ sprite_icon: 'project',
+ super_sidebar_parent: ::Sidebars::StaticMenu
+ })
+ end
end
end
end
diff --git a/lib/sidebars/projects/menus/wiki_menu.rb b/lib/sidebars/projects/menus/wiki_menu.rb
index 3980b193fd1..8bf223da4da 100644
--- a/lib/sidebars/projects/menus/wiki_menu.rb
+++ b/lib/sidebars/projects/menus/wiki_menu.rb
@@ -35,6 +35,13 @@ module Sidebars
def active_routes
{ controller: :wikis }
end
+
+ override :serialize_as_menu_item_args
+ def serialize_as_menu_item_args
+ super.merge({
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::PlanMenu
+ })
+ end
end
end
end
diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb
index a47095bd665..9d0f5eb87bd 100644
--- a/lib/sidebars/projects/panel.rb
+++ b/lib/sidebars/projects/panel.rb
@@ -15,15 +15,6 @@ module Sidebars
_('Project navigation')
end
- override :super_sidebar_context_header
- def super_sidebar_context_header
- @super_sidebar_context_header ||= {
- title: context.project.name,
- avatar: context.project.avatar_url,
- id: context.project.id
- }
- end
-
private
def add_menus
diff --git a/lib/sidebars/projects/super_sidebar_menus/plan_menu.rb b/lib/sidebars/projects/super_sidebar_menus/plan_menu.rb
new file mode 100644
index 00000000000..ae9b2d826b7
--- /dev/null
+++ b/lib/sidebars/projects/super_sidebar_menus/plan_menu.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module Projects
+ module SuperSidebarMenus
+ class PlanMenu < ::Sidebars::Menu
+ override :title
+ def title
+ _('Plan')
+ end
+
+ override :sprite_icon
+ def sprite_icon
+ 'planning'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/projects/super_sidebar_panel.rb b/lib/sidebars/projects/super_sidebar_panel.rb
new file mode 100644
index 00000000000..f0ebea92525
--- /dev/null
+++ b/lib/sidebars/projects/super_sidebar_panel.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module Projects
+ class SuperSidebarPanel < ::Sidebars::Projects::Panel
+ include ::Sidebars::Concerns::SuperSidebarPanel
+ extend ::Gitlab::Utils::Override
+
+ override :configure_menus
+ def configure_menus
+ super
+ old_menus = @menus
+ @menus = []
+
+ add_menu(Sidebars::StaticMenu.new(context))
+ add_menu(Sidebars::Projects::SuperSidebarMenus::PlanMenu.new(context))
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::RepositoryMenu)
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::CiCdMenu)
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::SecurityComplianceMenu)
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::DeploymentsMenu)
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::PackagesRegistriesMenu)
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::InfrastructureMenu)
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::MonitorMenu)
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::AnalyticsMenu)
+ add_menu(Sidebars::UncategorizedMenu.new(context))
+ pick_from_old_menus(old_menus, Sidebars::Projects::Menus::SettingsMenu)
+
+ transform_old_menus(@menus, @scope_menu, *old_menus)
+ end
+
+ override :super_sidebar_context_header
+ def super_sidebar_context_header
+ {
+ title: context.project.name,
+ avatar: context.project.avatar_url,
+ id: context.project.id
+ }
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/static_menu.rb b/lib/sidebars/static_menu.rb
new file mode 100644
index 00000000000..b7ba69b1717
--- /dev/null
+++ b/lib/sidebars/static_menu.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Sidebars
+ # This is a special menu which does not serialize as
+ # a section and instead hoists all of menu items
+ # to be top-level items
+ class StaticMenu < ::Sidebars::Menu
+ override :serialize_for_super_sidebar
+ def serialize_for_super_sidebar
+ serialize_items_for_super_sidebar
+ end
+ end
+end
diff --git a/lib/sidebars/uncategorized_menu.rb b/lib/sidebars/uncategorized_menu.rb
new file mode 100644
index 00000000000..dc9ed8308fa
--- /dev/null
+++ b/lib/sidebars/uncategorized_menu.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Sidebars
+ # This Menu is a temporary help while we implement the new menu
+ # categories for everything. Once every Menu Item is categorized,
+ # we can remove this. This should be done before the Super Sidebar
+ # moves out of Alpha.
+ class UncategorizedMenu < ::Sidebars::Menu
+ override :title
+ def title
+ _('Uncategorized')
+ end
+
+ override :sprite_icon
+ def sprite_icon
+ 'question'
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c35193d0dc9..67b374607f4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6628,27 +6628,9 @@ msgstr ""
msgid "BillingPlan|Upgrade for free"
msgstr ""
-msgid "Billings|%{planName} plan"
-msgstr ""
-
-msgid "Billings|An error occurred while extending your trial."
-msgstr ""
-
-msgid "Billings|An error occurred while reactivating your trial."
-msgstr ""
-
-msgid "Billings|By extending your trial, you will receive an additional 30 days of %{planName}. Your trial can be only extended once."
-msgstr ""
-
-msgid "Billings|By reactivating your trial, you will receive an additional 30 days of %{planName}. Your trial can be only reactivated once."
-msgstr ""
-
msgid "Billings|Error validating card details"
msgstr ""
-msgid "Billings|Extend trial"
-msgstr ""
-
msgid "Billings|Free groups are limited to %{number} seats."
msgstr ""
@@ -6658,9 +6640,6 @@ msgstr ""
msgid "Billings|In a seat"
msgstr ""
-msgid "Billings|Reactivate trial"
-msgstr ""
-
msgid "Billings|Seats in use / Seats available"
msgstr ""
@@ -33465,6 +33444,9 @@ msgstr ""
msgid "Project order will not be saved as local storage is not available."
msgstr ""
+msgid "Project overview"
+msgstr ""
+
msgid "Project path"
msgstr ""
@@ -45861,6 +45843,9 @@ msgstr ""
msgid "Unban"
msgstr ""
+msgid "Uncategorized"
+msgstr ""
+
msgid "Uncommitted changes will be lost if you change branches. Do you want to continue?"
msgstr ""
diff --git a/scripts/generate-rspec-foss-impact-pipeline b/scripts/generate-rspec-foss-impact-pipeline
deleted file mode 100755
index 3277f38ebe1..00000000000
--- a/scripts/generate-rspec-foss-impact-pipeline
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-# Script to generate `rspec foss-impact` test child pipeline with dynamically parallelized jobs.
-
-source scripts/utils.sh
-
-rspec_matching_tests_foss_path="${1}"
-pipeline_yml="${2}"
-
-test_file_count=$(wc -w "${rspec_matching_tests_foss_path}" | awk '{ print $1 }')
-echoinfo "test_file_count: ${test_file_count}"
-
-if [[ "${test_file_count}" -eq 0 ]]; then
- skip_pipeline=".gitlab/ci/_skip.yml"
-
- echo "Using ${skip_pipeline} due to no impacted FOSS rspec tests to run"
- cp $skip_pipeline "$pipeline_yml"
- exit
-fi
-
-# As of 2022-09-01:
-# $ find spec -type f | wc -l
-# 12825
-# and
-# $ find ee/spec -type f | wc -l
-# 5610
-# which gives a total of 18435 test files (`number_of_tests_in_total_in_the_test_suite`).
-#
-# Total time to run all tests (based on https://gitlab-org.gitlab.io/rspec_profiling_stats/) is 170183 seconds (`duration_of_the_test_suite_in_seconds`).
-#
-# This gives an approximate 170183 / 18435 = 9.2 seconds per test file (`average_test_file_duration_in_seconds`).
-#
-# If we want each test job to finish in 10 minutes, given we have 3 minutes of setup (`setup_duration_in_seconds`), then we need to give 7 minutes of testing to each test node (`optimal_test_runtime_duration_in_seconds`).
-# (7 * 60) / 9.2 = 45.6
-#
-# So if we'd want to run the full test suites in 10 minutes (`optimal_test_job_duration_in_seconds`), we'd need to run at max 45 test file per nodes (`optimal_test_file_count_per_node`).
-number_of_tests_in_total_in_the_test_suite=18435
-duration_of_the_test_suite_in_seconds=170183
-optimal_test_job_duration_in_seconds=600 # 10 minutes
-setup_duration_in_seconds=180 # 3 minutes
-
-optimal_test_runtime_duration_in_seconds=$(( optimal_test_job_duration_in_seconds - setup_duration_in_seconds ))
-echoinfo "optimal_test_runtime_duration_in_seconds: ${optimal_test_runtime_duration_in_seconds}"
-
-average_test_file_duration_in_seconds=$(( duration_of_the_test_suite_in_seconds / number_of_tests_in_total_in_the_test_suite ))
-echoinfo "average_test_file_duration_in_seconds: ${average_test_file_duration_in_seconds}"
-
-optimal_test_file_count_per_node=$(( optimal_test_runtime_duration_in_seconds / average_test_file_duration_in_seconds ))
-echoinfo "optimal_test_file_count_per_node: ${optimal_test_file_count_per_node}"
-
-node_count=$(( test_file_count / optimal_test_file_count_per_node ))
-echoinfo "node_count: ${node_count}"
-
-echoinfo "Optimal node count for 'rspec foss-impact' jobs is ${node_count}."
-
-MAX_NODES_COUNT=50 # Maximum parallelization allowed by GitLab
-if [[ "${node_count}" -gt "${MAX_NODES_COUNT}" ]]; then
- echoinfo "We don't want to parallelize 'rspec foss-impact' to more than ${MAX_NODES_COUNT} jobs for now! Decreasing the parallelization to ${MAX_NODES_COUNT}."
- node_count=${MAX_NODES_COUNT}
-fi
-
-ruby -rerb -e "puts ERB.new(File.read('.gitlab/ci/rails/rspec-foss-impact.gitlab-ci.yml.erb')).result_with_hash(parallel_value: ${node_count})" > "${pipeline_yml}"
-
-echosuccess "Generated ${pipeline_yml} pipeline with following content:"
-cat "${pipeline_yml}"
diff --git a/scripts/generate_rspec_pipeline.rb b/scripts/generate_rspec_pipeline.rb
new file mode 100755
index 00000000000..e226acc0430
--- /dev/null
+++ b/scripts/generate_rspec_pipeline.rb
@@ -0,0 +1,176 @@
+#!/usr/bin/env ruby
+
+# frozen_string_literal: true
+
+require 'optparse'
+require 'json'
+require 'fileutils'
+require 'erb'
+require_relative '../tooling/quality/test_level'
+
+# Class to generate RSpec test child pipeline with dynamically parallelized jobs.
+class GenerateRspecPipeline
+ SKIP_PIPELINE_YML_FILE = ".gitlab/ci/_skip.yml"
+ TEST_LEVELS = %i[migration background_migration unit integration system].freeze
+ MAX_NODES_COUNT = 50 # Maximum parallelization allowed by GitLab
+
+ OPTIMAL_TEST_JOB_DURATION_IN_SECONDS = 600 # 10 MINUTES
+ SETUP_DURATION_IN_SECONDS = 180.0 # 3 MINUTES
+ OPTIMAL_TEST_RUNTIME_DURATION_IN_SECONDS = OPTIMAL_TEST_JOB_DURATION_IN_SECONDS - SETUP_DURATION_IN_SECONDS
+
+ # As of 2022-09-01:
+ # $ find spec -type f | wc -l
+ # 12825
+ # and
+ # $ find ee/spec -type f | wc -l
+ # 5610
+ # which gives a total of 18435 test files (`NUMBER_OF_TESTS_IN_TOTAL_IN_THE_TEST_SUITE`).
+ #
+ # Total time to run all tests (based on https://gitlab-org.gitlab.io/rspec_profiling_stats/)
+ # is 170183 seconds (`DURATION_OF_THE_TEST_SUITE_IN_SECONDS`).
+ #
+ # This gives an approximate 170183 / 18435 = 9.2 seconds per test file
+ # (`DEFAULT_AVERAGE_TEST_FILE_DURATION_IN_SECONDS`).
+ #
+ # If we want each test job to finish in 10 minutes, given we have 3 minutes of setup (`SETUP_DURATION_IN_SECONDS`),
+ # then we need to give 7 minutes of testing to each test node (`OPTIMAL_TEST_RUNTIME_DURATION_IN_SECONDS`).
+ # (7 * 60) / 9.2 = 45.6
+ #
+ # So if we'd want to run the full test suites in 10 minutes (`OPTIMAL_TEST_JOB_DURATION_IN_SECONDS`),
+ # we'd need to run at max 45 test file per nodes (`#optimal_test_file_count_per_node_per_test_level`).
+ NUMBER_OF_TESTS_IN_TOTAL_IN_THE_TEST_SUITE = 18_435
+ DURATION_OF_THE_TEST_SUITE_IN_SECONDS = 170_183
+ DEFAULT_AVERAGE_TEST_FILE_DURATION_IN_SECONDS =
+ DURATION_OF_THE_TEST_SUITE_IN_SECONDS / NUMBER_OF_TESTS_IN_TOTAL_IN_THE_TEST_SUITE
+
+ # rspec_files_path: A file containing RSpec files to run, separated by a space
+ # pipeline_template_path: A YAML pipeline configuration template to generate the final pipeline config from
+ def initialize(pipeline_template_path:, rspec_files_path: nil, knapsack_report_path: nil)
+ @pipeline_template_path = pipeline_template_path.to_s
+ @rspec_files_path = rspec_files_path.to_s
+ @knapsack_report_path = knapsack_report_path.to_s
+
+ raise ArgumentError unless File.exist?(@pipeline_template_path)
+ end
+
+ def generate!
+ if all_rspec_files.empty?
+ info "Using #{SKIP_PIPELINE_YML_FILE} due to no RSpec files to run"
+ FileUtils.cp(SKIP_PIPELINE_YML_FILE, pipeline_filename)
+ return
+ end
+
+ File.open(pipeline_filename, 'w') do |handle|
+ pipeline_yaml = ERB.new(File.read(pipeline_template_path)).result_with_hash(**erb_binding)
+ handle.write(pipeline_yaml.squeeze("\n").strip)
+ end
+ end
+
+ private
+
+ attr_reader :pipeline_template_path, :rspec_files_path, :knapsack_report_path
+
+ def info(text)
+ $stdout.puts "[#{self.class.name}] #{text}"
+ end
+
+ def all_rspec_files
+ @all_rspec_files ||= File.exist?(rspec_files_path) ? File.read(rspec_files_path).split(' ') : []
+ end
+
+ def pipeline_filename
+ @pipeline_filename ||= "#{pipeline_template_path}.yml"
+ end
+
+ def erb_binding
+ { rspec_files_per_test_level: rspec_files_per_test_level }
+ end
+
+ def rspec_files_per_test_level
+ @rspec_files_per_test_level ||= begin
+ all_remaining_rspec_files = all_rspec_files.dup
+ TEST_LEVELS.each_with_object(Hash.new { |h, k| h[k] = {} }) do |test_level, memo| # rubocop:disable Rails/IndexWith
+ memo[test_level][:files] = all_remaining_rspec_files
+ .grep(Quality::TestLevel.new.regexp(test_level))
+ .tap { |files| files.each { |file| all_remaining_rspec_files.delete(file) } }
+ memo[test_level][:parallelization] = optimal_nodes_count(test_level, memo[test_level][:files])
+ end
+ end
+ end
+
+ def optimal_nodes_count(test_level, rspec_files)
+ nodes_count = (rspec_files.size / optimal_test_file_count_per_node_per_test_level(test_level)).ceil
+ info "Optimal node count for #{rspec_files.size} #{test_level} RSpec files is #{nodes_count}."
+
+ if nodes_count > MAX_NODES_COUNT
+ info "We don't want to parallelize to more than #{MAX_NODES_COUNT} jobs for now! " \
+ "Decreasing the parallelization to #{MAX_NODES_COUNT}."
+
+ MAX_NODES_COUNT
+ else
+ nodes_count
+ end
+ end
+
+ def optimal_test_file_count_per_node_per_test_level(test_level)
+ [
+ (OPTIMAL_TEST_RUNTIME_DURATION_IN_SECONDS / average_test_file_duration_in_seconds_per_test_level[test_level]),
+ 1
+ ].max
+ end
+
+ def average_test_file_duration_in_seconds_per_test_level
+ @optimal_test_file_count_per_node_per_test_level ||=
+ if knapsack_report.any?
+ remaining_knapsack_report = knapsack_report.dup
+ TEST_LEVELS.each_with_object({}) do |test_level, memo|
+ matching_data_per_test_level = remaining_knapsack_report
+ .select { |test_file, _| test_file.match?(Quality::TestLevel.new.regexp(test_level)) }
+ .tap { |test_data| test_data.each { |file, _| remaining_knapsack_report.delete(file) } }
+ memo[test_level] =
+ matching_data_per_test_level.values.sum / matching_data_per_test_level.keys.size
+ end
+ else
+ TEST_LEVELS.each_with_object({}) do |test_level, memo| # rubocop:disable Rails/IndexWith
+ memo[test_level] = DEFAULT_AVERAGE_TEST_FILE_DURATION_IN_SECONDS
+ end
+ end
+ end
+
+ def knapsack_report
+ @knapsack_report ||=
+ begin
+ File.exist?(knapsack_report_path) ? JSON.parse(File.read(knapsack_report_path)) : {}
+ rescue JSON::ParserError => e
+ info "[ERROR] Knapsack report at #{knapsack_report_path} couldn't be parsed! Error:\n#{e}"
+ {}
+ end
+ end
+end
+
+if $PROGRAM_NAME == __FILE__
+ options = {}
+
+ OptionParser.new do |opts|
+ opts.on("-f", "--rspec-files-path path", String, "Path to a file containing RSpec files to run, " \
+ "separated by a space") do |value|
+ options[:rspec_files_path] = value
+ end
+
+ opts.on("-t", "--pipeline-template-path PATH", String, "Path to a YAML pipeline configuration template to " \
+ "generate the final pipeline config from") do |value|
+ options[:pipeline_template_path] = value
+ end
+
+ opts.on("-k", "--knapsack-report-path path", String, "Path to a Knapsack report") do |value|
+ options[:knapsack_report_path] = value
+ end
+
+ opts.on("-h", "--help", "Prints this help") do
+ puts opts
+ exit
+ end
+ end.parse!
+
+ GenerateRspecPipeline.new(**options).generate!
+end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 96006974c13..5e4e47be2c5 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -436,6 +436,13 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
expect(json_response['commands_changes']).not_to include('target_project', 'title')
end
+
+ it 'includes command_names' do
+ create!
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['command_names']).to include('award', 'estimate', 'spend')
+ end
end
context 'with commands that do not return changes' do
@@ -454,6 +461,13 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['commands_changes']).not_to include('target_project', 'title')
end
+
+ it 'includes command_names' do
+ create!
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['command_names']).to include('move', 'title')
+ end
end
end
end
diff --git a/spec/features/groups/new_group_page_spec.rb b/spec/features/groups/new_group_page_spec.rb
index 4670df3fb5e..6d9513ce84f 100644
--- a/spec/features/groups/new_group_page_spec.rb
+++ b/spec/features/groups/new_group_page_spec.rb
@@ -30,16 +30,42 @@ RSpec.describe 'New group page', :js, feature_category: :subgroups do
end
describe 'sidebar' do
- context 'for a new top-level group' do
- it_behaves_like 'a dashboard page with sidebar', :new_group_path, :groups
+ context 'in the current navigation' do
+ before do
+ user.update!(use_new_navigation: false)
+ end
+
+ context 'for a new top-level group' do
+ it_behaves_like 'a dashboard page with sidebar', :new_group_path, :groups
+ end
+
+ context 'for a new subgroup' do
+ it 'shows the group sidebar of the parent group' do
+ visit new_group_path(parent_id: parent_group.id, anchor: 'create-group-pane')
+ expect(page).to have_selector(
+ ".nav-sidebar[aria-label=\"Group navigation\"] .context-header[title=\"#{parent_group.name}\"]"
+ )
+ end
+ end
end
- context 'for a new subgroup' do
- it 'shows the group sidebar of the parent group' do
- visit new_group_path(parent_id: parent_group.id, anchor: 'create-group-pane')
- expect(page).to have_selector(
- ".nav-sidebar[aria-label=\"Group navigation\"] .context-header[title=\"#{parent_group.name}\"]"
- )
+ context 'in the new navigation' do
+ before do
+ user.update!(use_new_navigation: true)
+ end
+
+ context 'for a new top-level group' do
+ it 'shows the "Your work" navigation' do
+ visit new_group_path
+ expect(page).to have_selector(".super-sidebar .context-switcher-toggle", text: "Your work")
+ end
+ end
+
+ context 'for a new subgroup' do
+ it 'shows the group navigation of the parent group' do
+ visit new_group_path(parent_id: parent_group.id, anchor: 'create-group-pane')
+ expect(page).to have_selector(".super-sidebar .context-switcher-toggle", text: parent_group.name)
+ end
end
end
end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 3cbfa14208f..439ae4275ae 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -588,14 +588,42 @@ RSpec.describe 'New project', :js, feature_category: :projects do
sign_in(user)
end
- context 'for a new top-level project' do
- it_behaves_like 'a dashboard page with sidebar', :new_project_path, :projects
+ context 'in the current navigation' do
+ before do
+ user.update!(use_new_navigation: false)
+ end
+
+ context 'for a new top-level project' do
+ it_behaves_like 'a dashboard page with sidebar', :new_project_path, :projects
+ end
+
+ context 'for a new group project' do
+ it 'shows the group sidebar of the parent group' do
+ visit new_project_path(namespace_id: parent_group.id)
+ expect(page).to have_selector(".nav-sidebar[aria-label=\"Group navigation\"] .context-header[title=\"#{parent_group.name}\"]")
+ end
+ end
end
- context 'for a new group project' do
- it 'shows the group sidebar of the parent group' do
- visit new_project_path(namespace_id: parent_group.id)
- expect(page).to have_selector(".nav-sidebar[aria-label=\"Group navigation\"] .context-header[title=\"#{parent_group.name}\"]")
+ context 'in the new navigation' do
+ before do
+ parent_group.add_owner(user)
+ user.update!(use_new_navigation: true)
+ sign_in(user)
+ end
+
+ context 'for a new top-level project' do
+ it 'shows the "Your work" navigation' do
+ visit new_project_path
+ expect(page).to have_selector(".super-sidebar .context-switcher-toggle", text: "Your work")
+ end
+ end
+
+ context 'for a new group project' do
+ it 'shows the group sidebar of the parent group' do
+ visit new_project_path(namespace_id: parent_group.id)
+ expect(page).to have_selector(".super-sidebar .context-switcher-toggle", text: parent_group.name)
+ end
end
end
end
diff --git a/spec/frontend/issues/create_merge_request_dropdown_spec.js b/spec/frontend/issues/create_merge_request_dropdown_spec.js
index cc2ee84348a..21ae844e2dd 100644
--- a/spec/frontend/issues/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/issues/create_merge_request_dropdown_spec.js
@@ -65,6 +65,14 @@ describe('CreateMergeRequestDropdown', () => {
expect(dropdown.createMrPath).toBe(
`${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=contains%23hash&merge_request%5Btarget_branch%5D=master&merge_request%5Bissue_iid%5D=42`,
);
+
+ expect(dropdown.wrapperEl.dataset.createBranchPath).toBe(
+ `${TEST_HOST}/branches?branch_name=contains%23hash&issue=42`,
+ );
+
+ expect(dropdown.wrapperEl.dataset.createMrPath).toBe(
+ `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=contains%23hash&merge_request%5Btarget_branch%5D=master&merge_request%5Bissue_iid%5D=42`,
+ );
});
});
diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb
index aa6b1dba6e8..1afe4efaf22 100644
--- a/spec/helpers/sidebars_helper_spec.rb
+++ b/spec/helpers/sidebars_helper_spec.rb
@@ -192,7 +192,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
before do
allow(helper).to receive(:project_sidebar_context_data).and_return(
- { current_user: nil, container: project, can_view_pipeline_editor: false })
+ { current_user: nil, container: project, can_view_pipeline_editor: false, learn_gitlab_enabled: false })
allow(helper).to receive(:group_sidebar_context_data).and_return({ current_user: nil, container: group })
allow(group).to receive(:to_global_id).and_return(5)
@@ -204,7 +204,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
end
it 'returns Project Panel for project nav' do
- expect(helper.super_sidebar_nav_panel(nav: 'project')).to be_a(Sidebars::Projects::Panel)
+ expect(helper.super_sidebar_nav_panel(nav: 'project')).to be_a(Sidebars::Projects::SuperSidebarPanel)
end
it 'returns Group Panel for group nav' do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 4773c0b5e1e..c8b4a8b8a0e 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -728,27 +728,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo
scheduling_type: :stage,
id_tokens: { TEST_ID_TOKEN: { aud: 'https://gitlab.com' } })
end
-
- context 'when the FF ci_hooks_pre_get_sources_script is disabled' do
- before do
- stub_feature_flags(ci_hooks_pre_get_sources_script: false)
- end
-
- it 'returns correct value' do
- expect(entry.value)
- .to eq(name: :rspec,
- before_script: %w[ls pwd],
- script: %w[rspec],
- stage: 'test',
- ignore: false,
- after_script: %w[cleanup],
- only: { refs: %w[branches tags] },
- job_variables: {},
- root_variables_inheritance: true,
- scheduling_type: :stage,
- id_tokens: { TEST_ID_TOKEN: { aud: 'https://gitlab.com' } })
- end
- end
end
end
diff --git a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
index 57c5011590c..6bcefa455cf 100644
--- a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
+++ b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
@@ -48,6 +48,7 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
let(:result_dir) { Pathname.new(Dir.mktmpdir) }
let(:connection) { base_model.connection }
let(:table_name) { "_test_column_copying" }
+ let(:num_rows_in_table) { 1000 }
let(:from_id) { 0 }
after do
@@ -61,7 +62,7 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
data bigint default 0
);
- insert into #{table_name} (id) select i from generate_series(1, 1000) g(i);
+ insert into #{table_name} (id) select i from generate_series(1, #{num_rows_in_table}) g(i);
SQL
end
@@ -134,6 +135,24 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
expect(calls).not_to be_empty
end
+ it 'samples 1 job with a batch size higher than the table size' do
+ calls = []
+ define_background_migration(migration_name) do |*args|
+ travel 1.minute
+ calls << args
+ end
+
+ queue_migration(migration_name, table_name, :id,
+ job_interval: 5.minutes,
+ batch_size: num_rows_in_table * 2,
+ sub_batch_size: num_rows_in_table * 2)
+
+ described_class.new(result_dir: result_dir, connection: connection,
+ from_id: from_id).run_jobs(for_duration: 3.minutes)
+
+ expect(calls.size).to eq(1)
+ end
+
context 'with multiple jobs to run' do
let(:last_id) do
Gitlab::Database::SharedModel.using_connection(connection) do
diff --git a/spec/lib/sidebars/concerns/super_sidebar_panel_spec.rb b/spec/lib/sidebars/concerns/super_sidebar_panel_spec.rb
new file mode 100644
index 00000000000..55598948271
--- /dev/null
+++ b/spec/lib/sidebars/concerns/super_sidebar_panel_spec.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Sidebars::Concerns::SuperSidebarPanel, feature_category: :navigation do
+ let(:menu_class_foo) { Class.new(Sidebars::Menu) }
+ let(:menu_foo) { menu_class_foo.new({}) }
+
+ let(:menu_class_bar) do
+ Class.new(Sidebars::Menu) do
+ def title
+ "Bar"
+ end
+ end
+ end
+
+ let(:menu_bar) { menu_class_bar.new({}) }
+
+ subject do
+ Class.new(Sidebars::Panel) do
+ include Sidebars::Concerns::SuperSidebarPanel
+ end.new({})
+ end
+
+ before do
+ allow(menu_foo).to receive(:render?).and_return(true)
+ allow(menu_bar).to receive(:render?).and_return(true)
+ end
+
+ describe '#pick_from_old_menus' do
+ it 'removes element of a given class from a list and adds it to menus' do
+ old_menus = [menu_foo, menu_bar]
+
+ subject.pick_from_old_menus(old_menus, menu_class_foo)
+
+ expect(old_menus).not_to include(menu_foo)
+ expect(subject.renderable_menus).to include(menu_foo)
+ end
+
+ it 'is a noop, if the list does not contain an element of the wanted class' do
+ old_menus = [menu_foo]
+
+ subject.pick_from_old_menus(old_menus, menu_class_bar)
+
+ expect(old_menus).to eq([menu_foo])
+ expect(subject.renderable_menus).to eq([])
+ end
+ end
+
+ describe '#transform_old_menus' do
+ let(:uncategorized_menu) { ::Sidebars::UncategorizedMenu.new({}) }
+
+ let(:menu_item) do
+ Sidebars::MenuItem.new(title: 'foo3', link: 'foo3', active_routes: { controller: 'barc' },
+ super_sidebar_parent: menu_class_foo)
+ end
+
+ let(:nil_menu_item) { Sidebars::NilMenuItem.new(item_id: :nil_item) }
+ let(:existing_item) do
+ Sidebars::MenuItem.new(
+ item_id: :exists,
+ title: 'Existing item',
+ link: 'foo2',
+ active_routes: { controller: 'foo2' }
+ )
+ end
+
+ let(:current_menus) { [menu_foo, uncategorized_menu] }
+
+ before do
+ allow(menu_bar).to receive(:serialize_as_menu_item_args).and_return(nil)
+ menu_foo.add_item(existing_item)
+ end
+
+ context 'for Menus with Menu Items' do
+ before do
+ menu_bar.add_item(menu_item)
+ menu_bar.add_item(nil_menu_item)
+ end
+
+ it 'adds Menu Items to defined super_sidebar_parent' do
+ subject.transform_old_menus(current_menus, menu_bar)
+
+ expect(menu_foo.renderable_items).to eq([existing_item, menu_item])
+ expect(uncategorized_menu.renderable_items).to eq([])
+ end
+
+ it 'adds Menu Items to defined super_sidebar_parent, before super_sidebar_before' do
+ allow(menu_item).to receive(:super_sidebar_before).and_return(:exists)
+ subject.transform_old_menus(current_menus, menu_bar)
+
+ expect(menu_foo.renderable_items).to eq([menu_item, existing_item])
+ expect(uncategorized_menu.renderable_items).to eq([])
+ end
+
+ it 'considers Menu Items uncategorized if super_sidebar_parent is nil' do
+ allow(menu_item).to receive(:super_sidebar_parent).and_return(nil)
+ subject.transform_old_menus(current_menus, menu_bar)
+
+ expect(menu_foo.renderable_items).to eq([existing_item])
+ expect(uncategorized_menu.renderable_items).to eq([menu_item])
+ end
+
+ it 'considers Menu Items uncategorized if super_sidebar_parent cannot be found' do
+ allow(menu_item).to receive(:super_sidebar_parent).and_return(menu_class_bar)
+ subject.transform_old_menus(current_menus, menu_bar)
+
+ expect(menu_foo.renderable_items).to eq([existing_item])
+ expect(uncategorized_menu.renderable_items).to eq([menu_item])
+ end
+
+ it 'considers Menu Items deleted if super_sidebar_parent is Sidebars::NilMenuItem' do
+ allow(menu_item).to receive(:super_sidebar_parent).and_return(::Sidebars::NilMenuItem)
+ subject.transform_old_menus(current_menus, menu_bar)
+
+ expect(menu_foo.renderable_items).to eq([existing_item])
+ expect(uncategorized_menu.renderable_items).to eq([])
+ end
+ end
+
+ it 'converts "solo" top-level Menu entry to Menu Item' do
+ allow(Sidebars::MenuItem).to receive(:new).and_return(menu_item)
+ allow(menu_bar).to receive(:serialize_as_menu_item_args).and_return({})
+
+ subject.transform_old_menus(current_menus, menu_bar)
+
+ expect(menu_foo.renderable_items).to eq([existing_item, menu_item])
+ expect(uncategorized_menu.renderable_items).to eq([])
+ end
+
+ it 'drops "solo" top-level Menu entries, if they serialize to nil' do
+ allow(Sidebars::MenuItem).to receive(:new).and_return(menu_item)
+ allow(menu_bar).to receive(:serialize_as_menu_item_args).and_return(nil)
+
+ subject.transform_old_menus(current_menus, menu_bar)
+
+ expect(menu_foo.renderable_items).to eq([existing_item])
+ expect(uncategorized_menu.renderable_items).to eq([])
+ end
+ end
+end
diff --git a/spec/lib/sidebars/menu_spec.rb b/spec/lib/sidebars/menu_spec.rb
index c84e04a738f..7c4d74aecc8 100644
--- a/spec/lib/sidebars/menu_spec.rb
+++ b/spec/lib/sidebars/menu_spec.rb
@@ -56,6 +56,22 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do
end
end
+ describe '#serialize_as_menu_item_args' do
+ it 'returns hash of title, link, active_routes, container_html_options' do
+ allow(menu).to receive(:title).and_return('Title')
+ allow(menu).to receive(:active_routes).and_return({ path: 'foo' })
+ allow(menu).to receive(:container_html_options).and_return({ class: 'foo' })
+ allow(menu).to receive(:link).and_return('/link')
+
+ expect(menu.serialize_as_menu_item_args).to eq({
+ title: 'Title',
+ link: '/link',
+ active_routes: { path: 'foo' },
+ container_html_options: { class: 'foo' }
+ })
+ end
+ end
+
describe '#render?' do
context 'when the menus has no items' do
it 'returns false' do
diff --git a/spec/lib/sidebars/projects/panel_spec.rb b/spec/lib/sidebars/projects/panel_spec.rb
index a581b982f9f..ec1df438cf1 100644
--- a/spec/lib/sidebars/projects/panel_spec.rb
+++ b/spec/lib/sidebars/projects/panel_spec.rb
@@ -13,12 +13,6 @@ RSpec.describe Sidebars::Projects::Panel, feature_category: :navigation do
expect(subject.scope_menu).to be_a(Sidebars::Projects::Menus::ScopeMenu)
end
- it 'implements #super_sidebar_context_header' do
- expect(subject.super_sidebar_context_header).to eq({
- title: project.name, avatar: project.avatar_url, id: project.id
- })
- end
-
context 'Confluence menu item' do
subject { described_class.new(context).instance_variable_get(:@menus) }
diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/plan_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/plan_menu_spec.rb
new file mode 100644
index 00000000000..3917d26f6f2
--- /dev/null
+++ b/spec/lib/sidebars/projects/super_sidebar_menus/plan_menu_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::Projects::SuperSidebarMenus::PlanMenu, feature_category: :navigation do
+ subject { described_class.new({}) }
+
+ it 'has title and sprite_icon' do
+ expect(subject.title).to eq(_("Plan"))
+ expect(subject.sprite_icon).to eq("planning")
+ end
+end
diff --git a/spec/lib/sidebars/projects/super_sidebar_panel_spec.rb b/spec/lib/sidebars/projects/super_sidebar_panel_spec.rb
new file mode 100644
index 00000000000..a4df46ca493
--- /dev/null
+++ b/spec/lib/sidebars/projects/super_sidebar_panel_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::Projects::SuperSidebarPanel, feature_category: :navigation do
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:user) { project.first_owner }
+
+ let(:context) do
+ double("Stubbed context", current_user: user, container: project, project: project, current_ref: 'master').as_null_object # rubocop:disable RSpec/VerifiedDoubles
+ end
+
+ subject { described_class.new(context) }
+
+ it 'implements #super_sidebar_context_header' do
+ expect(subject.super_sidebar_context_header).to eq(
+ {
+ title: project.name,
+ avatar: project.avatar_url,
+ id: project.id
+ })
+ end
+
+ describe '#renderable_menus' do
+ let(:category_menu) do
+ [
+ Sidebars::StaticMenu,
+ Sidebars::Projects::SuperSidebarMenus::PlanMenu,
+ Sidebars::Projects::Menus::RepositoryMenu,
+ Sidebars::Projects::Menus::CiCdMenu,
+ Sidebars::Projects::Menus::SecurityComplianceMenu,
+ Sidebars::Projects::Menus::DeploymentsMenu,
+ Sidebars::Projects::Menus::PackagesRegistriesMenu,
+ Sidebars::Projects::Menus::InfrastructureMenu,
+ Sidebars::Projects::Menus::MonitorMenu,
+ Sidebars::Projects::Menus::AnalyticsMenu,
+ Sidebars::UncategorizedMenu,
+ Sidebars::Projects::Menus::SettingsMenu
+ ]
+ end
+
+ it "is exposed as a renderable menu" do
+ expect(subject.renderable_menus.map(&:class)).to eq(category_menu)
+ end
+ end
+end
diff --git a/spec/lib/sidebars/static_menu_spec.rb b/spec/lib/sidebars/static_menu_spec.rb
new file mode 100644
index 00000000000..a42fed4b170
--- /dev/null
+++ b/spec/lib/sidebars/static_menu_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::StaticMenu, feature_category: :navigation do
+ let(:context) { {} }
+
+ subject { described_class.new(context) }
+
+ describe '#serialize_for_super_sidebar' do
+ it 'returns flat list of all menu items' do
+ subject.add_item(Sidebars::MenuItem.new(title: 'Is active', link: 'foo2', active_routes: { controller: 'fooc' }))
+ subject.add_item(Sidebars::MenuItem.new(title: 'Not active', link: 'foo3', active_routes: { controller: 'barc' }))
+ subject.add_item(Sidebars::NilMenuItem.new(item_id: 'nil_item'))
+
+ allow(context).to receive(:route_is_active).and_return(->(x) { x[:controller] == 'fooc' })
+
+ expect(subject.serialize_for_super_sidebar).to eq(
+ [
+ {
+ title: "Is active",
+ icon: nil,
+ link: "foo2",
+ is_active: true
+ },
+ {
+ title: "Not active",
+ icon: nil,
+ link: "foo3",
+ is_active: false
+ }
+ ]
+ )
+ end
+ end
+end
diff --git a/spec/lib/sidebars/uncategorized_menu_spec.rb b/spec/lib/sidebars/uncategorized_menu_spec.rb
new file mode 100644
index 00000000000..45e7c0c87e2
--- /dev/null
+++ b/spec/lib/sidebars/uncategorized_menu_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UncategorizedMenu, feature_category: :navigation do
+ subject { described_class.new({}) }
+
+ it 'has title and sprite_icon' do
+ expect(subject.title).to eq(_("Uncategorized"))
+ expect(subject.sprite_icon).to eq("question")
+ end
+end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 4e3d8f633b7..2d6ddd74dfd 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -465,6 +465,14 @@ RSpec.describe Namespace, feature_category: :subgroups do
end
end
+ context 'when parent is nil' do
+ let(:namespace) { build(:group, parent: nil) }
+
+ it 'returns []' do
+ expect(namespace.traversal_ids).to eq []
+ end
+ end
+
context 'when made a child group' do
let!(:namespace) { create(:group) }
let!(:parent_namespace) { create(:group, children: [namespace]) }
@@ -1961,6 +1969,29 @@ RSpec.describe Namespace, feature_category: :subgroups do
expect(very_deep_nested_group.root_ancestor).to eq(root_group)
end
end
+
+ context 'when parent is changed' do
+ let(:group) { create(:group) }
+ let(:new_parent) { create(:group) }
+
+ shared_examples 'updates root_ancestor' do
+ it do
+ expect { subject }.to change { group.root_ancestor }.from(group).to(new_parent)
+ end
+ end
+
+ context 'by object' do
+ subject { group.parent = new_parent }
+
+ include_examples 'updates root_ancestor'
+ end
+
+ context 'by id' do
+ subject { group.parent_id = new_parent.id }
+
+ include_examples 'updates root_ancestor'
+ end
+ end
end
describe '#full_path_before_last_save' do
diff --git a/spec/models/uploads/fog_spec.rb b/spec/models/uploads/fog_spec.rb
index 1ffe7c6c43b..a1b0bcf95e0 100644
--- a/spec/models/uploads/fog_spec.rb
+++ b/spec/models/uploads/fog_spec.rb
@@ -3,10 +3,21 @@
require 'spec_helper'
RSpec.describe Uploads::Fog do
+ let(:credentials) do
+ {
+ provider: "AWS",
+ aws_access_key_id: "AWS_ACCESS_KEY_ID",
+ aws_secret_access_key: "AWS_SECRET_ACCESS_KEY",
+ region: "eu-central-1"
+ }
+ end
+
+ let(:bucket_prefix) { nil }
let(:data_store) { described_class.new }
+ let(:config) { { connection: credentials, bucket_prefix: bucket_prefix, remote_directory: 'uploads' } }
before do
- stub_uploads_object_storage(FileUploader)
+ stub_uploads_object_storage(FileUploader, config: config)
end
describe '#available?' do
@@ -18,7 +29,7 @@ RSpec.describe Uploads::Fog do
context 'when object storage is disabled' do
before do
- stub_uploads_object_storage(FileUploader, enabled: false)
+ stub_uploads_object_storage(FileUploader, config: config, enabled: false)
end
it { is_expected.to be_falsy }
@@ -28,6 +39,60 @@ RSpec.describe Uploads::Fog do
context 'model with uploads' do
let(:project) { create(:project) }
let(:relation) { project.uploads }
+ let(:connection) { ::Fog::Storage.new(credentials) }
+ let(:paths) { relation.pluck(:path) }
+
+ # Only fog-aws simulates mocking of deleting an object properly.
+ # We'll just test that the various providers implement the require methods.
+ describe 'Fog provider acceptance tests' do
+ let!(:uploads) { create_list(:upload, 2, :with_file, :issuable_upload, model: project) }
+
+ shared_examples 'Fog provider' do
+ describe '#get_object' do
+ it 'returns a Hash with a body' do
+ expect(connection.get_object('uploads', paths.first)[:body]).not_to be_nil
+ end
+ end
+
+ describe '#delete_object' do
+ it 'returns true' do
+ expect(connection.delete_object('uploads', paths.first)).to be_truthy
+ end
+ end
+ end
+
+ before do
+ uploads.each { |upload| upload.retrieve_uploader.migrate!(2) }
+ end
+
+ context 'with AWS provider' do
+ it_behaves_like 'Fog provider'
+ end
+
+ context 'with Google provider' do
+ let(:credentials) do
+ {
+ provider: "Google",
+ google_storage_access_key_id: 'ACCESS_KEY_ID',
+ google_storage_secret_access_key: 'SECRET_ACCESS_KEY'
+ }
+ end
+
+ it_behaves_like 'Fog provider'
+ end
+
+ context 'with AzureRM provider' do
+ let(:credentials) do
+ {
+ provider: 'AzureRM',
+ azure_storage_account_name: 'test-access-id',
+ azure_storage_access_key: 'secret'
+ }
+ end
+
+ it_behaves_like 'Fog provider'
+ end
+ end
describe '#keys' do
let!(:uploads) { create_list(:upload, 2, :object_storage, uploader: FileUploader, model: project) }
@@ -40,7 +105,7 @@ RSpec.describe Uploads::Fog do
end
describe '#delete_keys' do
- let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) }
+ let(:connection) { ::Fog::Storage.new(credentials) }
let(:keys) { data_store.keys(relation) }
let(:paths) { relation.pluck(:path) }
let!(:uploads) { create_list(:upload, 2, :with_file, :issuable_upload, model: project) }
@@ -63,6 +128,22 @@ RSpec.describe Uploads::Fog do
end
end
+ context 'with bucket prefix' do
+ let(:bucket_prefix) { 'test-prefix' }
+
+ it 'deletes multiple data' do
+ paths.each do |path|
+ expect(connection.get_object('uploads', File.join(bucket_prefix, path))[:body]).not_to be_nil
+ end
+
+ subject
+
+ paths.each do |path|
+ expect { connection.get_object('uploads', File.join(bucket_prefix, path))[:body] }.to raise_error(Excon::Error::NotFound)
+ end
+ end
+ end
+
context 'when one of keys is missing' do
let(:keys) { ['unknown'] + super() }
diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
index 6e721d40560..28dbc4fd168 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -831,19 +831,6 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
end
end
end
-
- context 'when the FF ci_hooks_pre_get_sources_script is disabled' do
- before do
- stub_feature_flags(ci_hooks_pre_get_sources_script: false)
- end
-
- it 'does not return the pre_get_sources_script' do
- request_job
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).not_to have_key('hooks')
- end
- end
end
describe 'port support' do
diff --git a/spec/scripts/generate_rspec_pipeline_spec.rb b/spec/scripts/generate_rspec_pipeline_spec.rb
new file mode 100644
index 00000000000..b3eaf9e9127
--- /dev/null
+++ b/spec/scripts/generate_rspec_pipeline_spec.rb
@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'tempfile'
+
+require_relative '../../scripts/generate_rspec_pipeline'
+
+RSpec.describe GenerateRspecPipeline, :silence_stdout, feature_category: :tooling do
+ describe '#generate!' do
+ let!(:rspec_files) { Tempfile.new(['rspec_files_path', '.txt']) }
+ let(:rspec_files_content) do
+ "spec/migrations/a_spec.rb spec/migrations/b_spec.rb " \
+ "spec/lib/gitlab/background_migration/a_spec.rb spec/lib/gitlab/background_migration/b_spec.rb " \
+ "spec/models/a_spec.rb spec/models/b_spec.rb " \
+ "spec/controllers/a_spec.rb spec/controllers/b_spec.rb " \
+ "spec/features/a_spec.rb spec/features/b_spec.rb"
+ end
+
+ let(:pipeline_template) { Tempfile.new(['pipeline_template', '.yml.erb']) }
+ let(:pipeline_template_content) do
+ <<~YAML
+ <% if rspec_files_per_test_level[:migration][:files].size > 0 %>
+ rspec migration:
+ <% if rspec_files_per_test_level[:migration][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:migration][:parallelization] %>
+ <% end %>
+ <% end %>
+ <% if rspec_files_per_test_level[:background_migration][:files].size > 0 %>
+ rspec background_migration:
+ <% if rspec_files_per_test_level[:background_migration][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:background_migration][:parallelization] %>
+ <% end %>
+ <% end %>
+ <% if rspec_files_per_test_level[:unit][:files].size > 0 %>
+ rspec unit:
+ <% if rspec_files_per_test_level[:unit][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:unit][:parallelization] %>
+ <% end %>
+ <% end %>
+ <% if rspec_files_per_test_level[:integration][:files].size > 0 %>
+ rspec integration:
+ <% if rspec_files_per_test_level[:integration][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:integration][:parallelization] %>
+ <% end %>
+ <% end %>
+ <% if rspec_files_per_test_level[:system][:files].size > 0 %>
+ rspec system:
+ <% if rspec_files_per_test_level[:system][:parallelization] > 1 %>
+ parallel: <%= rspec_files_per_test_level[:system][:parallelization] %>
+ <% end %>
+ <% end %>
+ YAML
+ end
+
+ let(:knapsack_report) { Tempfile.new(['knapsack_report', '.json']) }
+ let(:knapsack_report_content) do
+ <<~JSON
+ {
+ "spec/migrations/a_spec.rb": 360.3,
+ "spec/migrations/b_spec.rb": 180.1,
+ "spec/lib/gitlab/background_migration/a_spec.rb": 60.5,
+ "spec/lib/gitlab/background_migration/b_spec.rb": 180.3,
+ "spec/models/a_spec.rb": 360.2,
+ "spec/models/b_spec.rb": 180.6,
+ "spec/controllers/a_spec.rb": 60.2,
+ "spec/controllers/ab_spec.rb": 180.4,
+ "spec/features/a_spec.rb": 360.1,
+ "spec/features/b_spec.rb": 180.5
+ }
+ JSON
+ end
+
+ around do |example|
+ rspec_files.write(rspec_files_content)
+ rspec_files.rewind
+ pipeline_template.write(pipeline_template_content)
+ pipeline_template.rewind
+ knapsack_report.write(knapsack_report_content)
+ knapsack_report.rewind
+ example.run
+ ensure
+ rspec_files.close
+ rspec_files.unlink
+ pipeline_template.close
+ pipeline_template.unlink
+ knapsack_report.close
+ knapsack_report.unlink
+ end
+
+ context 'when rspec_files and pipeline_template_path exists' do
+ subject do
+ described_class.new(
+ rspec_files_path: rspec_files.path,
+ pipeline_template_path: pipeline_template.path
+ )
+ end
+
+ it 'generates the pipeline config with default parallelization' do
+ subject.generate!
+
+ expect(File.read("#{pipeline_template.path}.yml"))
+ .to eq(
+ "rspec migration:\nrspec background_migration:\nrspec unit:\n" \
+ "rspec integration:\nrspec system:"
+ )
+ end
+
+ context 'when parallelization > 0' do
+ before do
+ stub_const("#{described_class}::DEFAULT_AVERAGE_TEST_FILE_DURATION_IN_SECONDS", 360)
+ end
+
+ it 'generates the pipeline config' do
+ subject.generate!
+
+ expect(File.read("#{pipeline_template.path}.yml"))
+ .to eq(
+ "rspec migration:\n parallel: 2\nrspec background_migration:\n parallel: 2\n" \
+ "rspec unit:\n parallel: 2\nrspec integration:\n parallel: 2\n" \
+ "rspec system:\n parallel: 2"
+ )
+ end
+ end
+
+ context 'when parallelization > MAX_NODES_COUNT' do
+ let(:rspec_files_content) do
+ Array.new(51) { |i| "spec/migrations/#{i}_spec.rb" }.join(' ')
+ end
+
+ before do
+ stub_const(
+ "#{described_class}::DEFAULT_AVERAGE_TEST_FILE_DURATION_IN_SECONDS",
+ described_class::OPTIMAL_TEST_JOB_DURATION_IN_SECONDS
+ )
+ end
+
+ it 'generates the pipeline config with max parallelization of 50' do
+ subject.generate!
+
+ expect(File.read("#{pipeline_template.path}.yml")).to eq("rspec migration:\n parallel: 50")
+ end
+ end
+ end
+
+ context 'when knapsack_report_path is given' do
+ subject do
+ described_class.new(
+ rspec_files_path: rspec_files.path,
+ pipeline_template_path: pipeline_template.path,
+ knapsack_report_path: knapsack_report.path
+ )
+ end
+
+ it 'generates the pipeline config with parallelization based on Knapsack' do
+ subject.generate!
+
+ expect(File.read("#{pipeline_template.path}.yml"))
+ .to eq(
+ "rspec migration:\n parallel: 2\nrspec background_migration:\n" \
+ "rspec unit:\n parallel: 2\nrspec integration:\n" \
+ "rspec system:\n parallel: 2"
+ )
+ end
+
+ context 'and Knapsack report does not contain valid JSON' do
+ let(:knapsack_report_content) { "#{super()}," }
+
+ it 'generates the pipeline config with default parallelization' do
+ subject.generate!
+
+ expect(File.read("#{pipeline_template.path}.yml"))
+ .to eq(
+ "rspec migration:\nrspec background_migration:\nrspec unit:\n" \
+ "rspec integration:\nrspec system:"
+ )
+ end
+ end
+ end
+
+ context 'when rspec_files does not exist' do
+ subject { described_class.new(rspec_files_path: nil, pipeline_template_path: pipeline_template.path) }
+
+ it 'generates the pipeline config using the no-op template' do
+ subject.generate!
+
+ expect(File.read("#{pipeline_template.path}.yml")).to include("no-op:")
+ end
+ end
+
+ context 'when pipeline_template_path does not exist' do
+ subject { described_class.new(rspec_files_path: rspec_files.path, pipeline_template_path: nil) }
+
+ it 'generates the pipeline config using the no-op template' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service/scripts_spec.rb b/spec/services/ci/create_pipeline_service/scripts_spec.rb
index 50b558e505a..770db9331cd 100644
--- a/spec/services/ci/create_pipeline_service/scripts_spec.rb
+++ b/spec/services/ci/create_pipeline_service/scripts_spec.rb
@@ -83,30 +83,5 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
options: { script: ["echo 'hello job3 script'"] }
)
end
-
- context 'when the FF ci_hooks_pre_get_sources_script is disabled' do
- before do
- stub_feature_flags(ci_hooks_pre_get_sources_script: false)
- end
-
- it 'creates jobs without hook data' do
- expect(pipeline).to be_created_successfully
- expect(pipeline.builds.find_by(name: 'job1')).to have_attributes(
- name: 'job1',
- stage: 'test',
- options: { script: ["echo 'hello job1 script'"] }
- )
- expect(pipeline.builds.find_by(name: 'job2')).to have_attributes(
- name: 'job2',
- stage: 'test',
- options: { script: ["echo 'hello job2 script'"] }
- )
- expect(pipeline.builds.find_by(name: 'job3')).to have_attributes(
- name: 'job3',
- stage: 'test',
- options: { script: ["echo 'hello job3 script'"] }
- )
- end
- end
end
end
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index c163ce1d880..6b633856228 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -15,7 +15,7 @@ module StubObjectStorage
direct_upload: false,
cdn: {}
)
-
+ old_config = Settingslogic.new(config.deep_stringify_keys)
new_config = config.to_h.deep_symbolize_keys.merge({
enabled: enabled,
proxy_download: proxy_download,
@@ -37,7 +37,7 @@ module StubObjectStorage
return unless enabled
stub_object_storage(connection_params: uploader.object_store_credentials,
- remote_directory: config.remote_directory)
+ remote_directory: old_config.remote_directory)
end
def stub_object_storage(connection_params:, remote_directory:)
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 8d1e5e907c5..7b7b7da13e5 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -226,7 +226,6 @@
- './ee/spec/features/analytics/code_analytics_spec.rb'
- './ee/spec/features/analytics/group_analytics_spec.rb'
- './ee/spec/features/billings/billing_plans_spec.rb'
-- './ee/spec/features/billings/extend_reactivate_trial_spec.rb'
- './ee/spec/features/billings/qrtly_reconciliation_alert_spec.rb'
- './ee/spec/features/boards/board_filters_spec.rb'
- './ee/spec/features/boards/boards_licensed_features_spec.rb'