diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-28 15:14:07 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-28 15:14:07 +0300 |
commit | 22ecb1e3fc02bb923c3e9941b1baa849348a036f (patch) | |
tree | c01d9e91564f50e790a63c71675dd0f6e7735153 | |
parent | 5eab6dcdd923ca375b86d6993f20a3e37dbd7a51 (diff) |
Add latest changes from gitlab-org/gitlab@master
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' @@ -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>|</code> | Or | [<code>display | 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>|</code> | Or | [<code>display | 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' |