Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-02-28 12:14:07 +00:00
parent 5eab6dcdd9
commit 22ecb1e3fc
69 changed files with 1203 additions and 242 deletions

View File

@ -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

View File

@ -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:

View File

@ -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 %>

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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"},

View File

@ -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)

View File

@ -569,6 +569,7 @@ export default class CreateMergeRequestDropdown {
pathReplacement,
);
this.wrapperEl.dataset.createBranchPath = this.createBranchPath;
this.wrapperEl.dataset.createMrPath = this.createMrPath;
}
}

View File

@ -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?

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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 -->

View File

@ -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

View File

@ -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) |

View File

@ -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 |

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -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

View File

@ -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: {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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}"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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`,
);
});
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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() }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:)

View File

@ -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'