Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/concerns/membership_actions.rb3
-rw-r--r--app/graphql/resolvers/concerns/caching_array_resolver.rb2
-rw-r--r--app/graphql/resolvers/merge_request_pipelines_resolver.rb4
-rw-r--r--app/graphql/resolvers/package_pipelines_resolver.rb1
-rw-r--r--app/models/analytics/cycle_analytics/issue_stage_event.rb4
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage_event_model.rb1
-rw-r--r--app/models/concerns/ci/deployable.rb8
-rw-r--r--app/models/environment.rb4
-rw-r--r--app/models/users/phone_number_validation.rb4
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--app/workers/environments/stop_job_failed_worker.rb23
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/docs/routes.yml1
-rw-r--r--db/docs/users.yml1
-rw-r--r--doc/development/i18n/proofreader.md2
-rw-r--r--doc/development/migration_style_guide.md23
-rw-r--r--doc/integration/jira/troubleshooting.md40
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb20
-rw-r--r--lib/gitlab/analytics/cycle_analytics/request_params.rb27
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1.rb42
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb99
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb196
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb106
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb78
-rw-r--r--lib/gitlab/graphql/pagination/active_record_array_connection.rb1
-rw-r--r--lib/gitlab/usage_data.rb19
-rw-r--r--qa/qa/page/project/pipeline/index.rb2
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb40
-rw-r--r--spec/factories/environments.rb4
-rw-r--r--spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb4
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb292
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb313
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb190
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb78
-rw-r--r--spec/lib/gitlab/graphql/pagination/array_connection_spec.rb3
-rw-r--r--spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb3
-rw-r--r--spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb6
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb22
-rw-r--r--spec/support/helpers/fake_migration_classes.rb13
-rw-r--r--spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb4
-rw-r--r--spec/support/shared_examples/ci/deployable_shared_examples.rb22
-rw-r--r--spec/workers/environments/stop_job_failed_worker_spec.rb99
42 files changed, 303 insertions, 1512 deletions
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 0c15c4d0d3f..f660419375e 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -156,7 +156,8 @@ module MembershipActions
[:inherited]
else
if Feature.enabled?(:webui_members_inherited_users, current_user)
- [:inherited, :direct, :shared_from_groups, (:invited_groups if params[:project_id])].compact
+ project_relations = [:invited_groups, :shared_into_ancestors]
+ [:inherited, :direct, :shared_from_groups, *(project_relations if params[:project_id])]
else
[:inherited, :direct]
end
diff --git a/app/graphql/resolvers/concerns/caching_array_resolver.rb b/app/graphql/resolvers/concerns/caching_array_resolver.rb
index 62649518142..15bf9a90e46 100644
--- a/app/graphql/resolvers/concerns/caching_array_resolver.rb
+++ b/app/graphql/resolvers/concerns/caching_array_resolver.rb
@@ -22,7 +22,7 @@
#
# **important**: If the cardinality of your collection is likely to be greater than 100,
# then you will want to pass `max_page_size:` as part of the field definition
-# or (ideally) as part of the resolver `field_options`.
+# or (ideally) set `max_page_size` in the resolver.
#
# How to implement:
# --------------------
diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
index deb698c63e1..45159e0edd5 100644
--- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb
+++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
@@ -11,9 +11,7 @@ module Resolvers
# Return at most 500 pipelines for each MR.
# Merge requests generally have many fewer pipelines than this.
- def self.field_options
- super.merge(max_page_size: 500)
- end
+ max_page_size 500
def resolve(**args)
return unless project
diff --git a/app/graphql/resolvers/package_pipelines_resolver.rb b/app/graphql/resolvers/package_pipelines_resolver.rb
index 1e57a5bee15..40e5456164a 100644
--- a/app/graphql/resolvers/package_pipelines_resolver.rb
+++ b/app/graphql/resolvers/package_pipelines_resolver.rb
@@ -51,6 +51,7 @@ module Resolvers
def default_value_for(first:, last:, after:, before:)
Gitlab::Graphql::Pagination::ActiveRecordArrayConnection.new(
[],
+ context: context,
first: first,
last: last,
after: after,
diff --git a/app/models/analytics/cycle_analytics/issue_stage_event.rb b/app/models/analytics/cycle_analytics/issue_stage_event.rb
index cdb89dde423..1a8f1b7c84a 100644
--- a/app/models/analytics/cycle_analytics/issue_stage_event.rb
+++ b/app/models/analytics/cycle_analytics/issue_stage_event.rb
@@ -4,7 +4,6 @@ module Analytics
module CycleAnalytics
class IssueStageEvent < ApplicationRecord
include StageEventModel
- include Awardable
extend SuppressCompositePrimaryKeyWarning
validates(*%i[stage_event_hash_id issue_id group_id project_id start_event_timestamp], presence: true)
@@ -12,8 +11,6 @@ module Analytics
alias_attribute :state, :state_id
enum state: Issue.available_states, _suffix: true
- has_one :epic_issue, primary_key: 'issue_id', foreign_key: 'issue_id' # rubocop: disable Rails/InverseOf
-
scope :assigned_to, ->(user) do
assignees_class = IssueAssignee
condition = assignees_class.where(user_id: user).where(arel_table[:issue_id].eq(assignees_class.arel_table[:issue_id]))
@@ -60,3 +57,4 @@ module Analytics
end
end
end
+Analytics::CycleAnalytics::IssueStageEvent.prepend_mod_with('Analytics::CycleAnalytics::IssueStageEvent')
diff --git a/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
index 8f20e3880b3..1d9cf5729cd 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
@@ -16,6 +16,7 @@ module Analytics
scope :start_event_timestamp_before, -> (date) { where(arel_table[:start_event_timestamp].lteq(date)) }
scope :authored, ->(user) { where(author_id: user) }
scope :with_milestone_id, ->(milestone_id) { where(milestone_id: milestone_id) }
+ scope :without_milestone_id, -> (milestone_id) { where('milestone_id <> ? or milestone_id IS NULL', milestone_id) }
scope :end_event_is_not_happened_yet, -> { where(end_event_timestamp: nil) }
scope :order_by_end_event, -> (direction) do
# ORDER BY end_event_timestamp, merge_request_id/issue_id, start_event_timestamp
diff --git a/app/models/concerns/ci/deployable.rb b/app/models/concerns/ci/deployable.rb
index d25151f9a34..bc2c67a8633 100644
--- a/app/models/concerns/ci/deployable.rb
+++ b/app/models/concerns/ci/deployable.rb
@@ -17,6 +17,14 @@ module Ci
end
end
+ after_transition any => [:failed] do |job|
+ next unless job.stops_environment?
+
+ job.run_after_commit do
+ Environments::StopJobFailedWorker.perform_async(id)
+ end
+ end
+
# Synchronize Deployment Status
# Please note that the data integirty is not assured because we can't use
# a database transaction due to DB decomposition.
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 29394c37e2c..efdcf7174aa 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -195,6 +195,10 @@ class Environment < ApplicationRecord
transition %i[available stopping] => :stopped
end
+ event :recover_stuck_stopping do
+ transition stopping: :available
+ end
+
state :available
state :stopping
state :stopped
diff --git a/app/models/users/phone_number_validation.rb b/app/models/users/phone_number_validation.rb
index 52f16a7861f..e033445d76b 100644
--- a/app/models/users/phone_number_validation.rb
+++ b/app/models/users/phone_number_validation.rb
@@ -2,9 +2,13 @@
module Users
class PhoneNumberValidation < ApplicationRecord
+ include IgnorableColumns
+
self.primary_key = :user_id
self.table_name = 'user_phone_number_validations'
+ ignore_column :verification_attempts, remove_with: '16.7', remove_after: '2023-11-17'
+
belongs_to :user, foreign_key: :user_id
belongs_to :banned_user, class_name: '::Users::BannedUser', foreign_key: :user_id
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index da05824be4f..f39c5f5c232 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -2892,6 +2892,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: environments_stop_job_failed
+ :worker_name: Environments::StopJobFailedWorker
+ :feature_category: :continuous_delivery
+ :has_external_dependencies: false
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: environments_stop_job_success
:worker_name: Environments::StopJobSuccessWorker
:feature_category: :continuous_delivery
diff --git a/app/workers/environments/stop_job_failed_worker.rb b/app/workers/environments/stop_job_failed_worker.rb
new file mode 100644
index 00000000000..44aa3a4e91f
--- /dev/null
+++ b/app/workers/environments/stop_job_failed_worker.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Environments
+ class StopJobFailedWorker
+ include ApplicationWorker
+
+ data_consistency :delayed
+ idempotent!
+ feature_category :continuous_delivery
+
+ def perform(job_id, _params = {})
+ Ci::Processable.find_by_id(job_id).try do |job|
+ revert_environment(job) if job.stops_environment? && job.failed?
+ end
+ end
+
+ private
+
+ def revert_environment(job)
+ job.persisted_environment.fire_state_event(:recover_stuck_stopping)
+ end
+ end
+end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 704c79c0a74..b8ebb3f8afb 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -271,6 +271,8 @@
- 1
- - environments_canary_ingress_update
- 1
+- - environments_stop_job_failed
+ - 1
- - environments_stop_job_success
- 1
- - epics
diff --git a/db/docs/routes.yml b/db/docs/routes.yml
index f070f917e31..51a390673aa 100644
--- a/db/docs/routes.yml
+++ b/db/docs/routes.yml
@@ -1,7 +1,6 @@
---
table_name: routes
classes:
-- Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Route
- Route
feature_categories:
- groups_and_projects
diff --git a/db/docs/users.yml b/db/docs/users.yml
index 1fd16ae9af7..119c89d48cc 100644
--- a/db/docs/users.yml
+++ b/db/docs/users.yml
@@ -1,7 +1,6 @@
---
table_name: users
classes:
-- Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::User
- TmpUser
- User
feature_categories:
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 65cde363e98..cea59bae41b 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -19,6 +19,8 @@ are very appreciative of the work done by translators and proofreaders!
- Tsegaselassie Tadesse - [GitLab](https://gitlab.com/tsega), [Crowdin](https://crowdin.com/profile/tsegaselassi)
- Arabic
- Proofreaders needed.
+- Basque
+ - Unai Tolosa - [GitLab](https://gitlab.com/utolosa002), [Crowdin](https://crowdin.com/profile/utolosa002)
- Belarusian
- Anton Katsuba - [GitLab](https://gitlab.com/coinvariant), [Crowdin](https://crowdin.com/profile/aerialfiddle)
- Bosnian
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index f4eff426d6a..29181dd1b9d 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -1454,29 +1454,6 @@ end
Here is an [example MR](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62195) illustrating how to use our new helper.
-### Renaming reserved paths
-
-When a new route for projects is introduced, it could conflict with any
-existing records. The path for these records should be renamed, and the
-related data should be moved on disk.
-
-Since we had to do this a few times already, there are now some helpers to help
-with this.
-
-To use this you can include `Gitlab::Database::RenameReservedPathsMigration::V1`
-in your migration. This provides 3 methods which you can pass one or more
-paths that need to be rejected.
-
-- **`rename_root_paths`**: Renames the path of all _namespaces_ with the
-given name that don't have a `parent_id`.
-- **`rename_child_paths`**: Renames the path of all _namespaces_ with the
-given name that have a `parent_id`.
-- **`rename_wildcard_paths`**: Renames the path of all _projects_, and all
-_namespaces_ that have a `project_id`.
-
-The `path` column for these rows are renamed to their previous value followed
-by an integer. For example: `users` would turn into `users0`
-
## Using application code in migrations (discouraged)
The use of application code (including models) in migrations is generally
diff --git a/doc/integration/jira/troubleshooting.md b/doc/integration/jira/troubleshooting.md
index 9483c7e809e..31af5ca0ebe 100644
--- a/doc/integration/jira/troubleshooting.md
+++ b/doc/integration/jira/troubleshooting.md
@@ -65,11 +65,13 @@ There is a [known bug](https://gitlab.com/gitlab-org/gitlab/-/issues/341571)
where the Jira integration sometimes does not work for a project that has been imported.
As a workaround, disable the integration and then re-enable it.
-## Bulk change all Jira integrations to Jira group-level or instance-level values
+## Bulk change all Jira integrations to Jira instance-level or group-level values
WARNING:
Commands that change data can cause damage if not run correctly or under the right conditions. Always run commands in a test environment first and have a backup instance ready to restore.
+### Change all projects instance wide
+
To change all Jira projects to use instance-level integration settings:
1. In a [Rails console](../../administration/operations/rails_console.md#starting-a-rails-console-session), run the following:
@@ -99,6 +101,42 @@ To change all Jira projects to use instance-level integration settings:
1. Modify and save the instance-level integration from the UI to propagate the changes to all group-level and project-level integrations.
+### Change all projects in a group
+
+To change all Jira projects in a group (and its subgroups) to use group-level integration settings:
+
+- In a [Rails console](../../administration/operations/rails_console.md#starting-a-rails-console-session), run the following:
+
+ ```ruby
+ def reset_integration(target)
+ integration = target.integrations.find_by(type: Integrations::Jira)
+
+ return if integration.nil? # Skip if the project has no Jira integration
+ return unless integration.inherit_from_id.nil? # Skip integrations that are already inheriting
+
+ default_integration = Integration.default_integration(integration.type, target)
+
+ integration.inherit_from_id = default_integration.id
+
+ if integration.save(context: :manual_change)
+ BulkUpdateIntegrationService.new(default_integration, [integration]).execute
+ end
+ end
+
+ parent_group = Group.find_by_full_path('top-level-group') # Add the full path of your top-level group
+ current_user = User.find_by_username('admin-user') # Add the username of a user with administrator access
+
+ groups = GroupsFinder.new(current_user, { parent: parent_group, include_parent_descendants: true }).execute
+
+ groups.find_each do |group|
+ reset_integration(group)
+
+ group.projects.find_each do |project|
+ reset_integration(project)
+ end
+ end
+ ```
+
## Bulk update the service integration password for all projects
WARNING:
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb
index 41f94e79f91..fc0e4ab5a0d 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb
@@ -50,8 +50,7 @@ module Gitlab
def filter_author(query)
return query if params[:author_username].blank?
- user = User.by_username(params[:author_username]).first
-
+ user = find_user(params[:author_username])
return query.none if user.blank?
query.authored(user)
@@ -60,11 +59,7 @@ module Gitlab
def filter_milestone_ids(query)
return query if params[:milestone_title].blank?
- milestone = MilestonesFinder
- .new(group_ids: root_ancestor.self_and_descendant_ids, project_ids: root_ancestor.all_projects.select(:id), title: params[:milestone_title])
- .execute
- .first
-
+ milestone = find_milestone(params[:milestone_title])
return query.none if milestone.blank?
query.with_milestone_id(milestone.id)
@@ -115,6 +110,17 @@ module Gitlab
private
attr_reader :stage, :params, :root_ancestor, :stage_event_model
+
+ def find_milestone(title)
+ MilestonesFinder
+ .new(group_ids: root_ancestor.self_and_descendant_ids, project_ids: root_ancestor.all_projects.select(:id), title: title)
+ .execute
+ .first
+ end
+
+ def find_user(username)
+ User.by_username(username).first
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/lib/gitlab/analytics/cycle_analytics/request_params.rb b/lib/gitlab/analytics/cycle_analytics/request_params.rb
index 005758b3db0..cea25ba2db4 100644
--- a/lib/gitlab/analytics/cycle_analytics/request_params.rb
+++ b/lib/gitlab/analytics/cycle_analytics/request_params.rb
@@ -12,6 +12,24 @@ module Gitlab
MAX_RANGE_DAYS = 180.days.freeze
DEFAULT_DATE_RANGE = 29.days # 30 including Date.today
+ NEGATABLE_PARAMS = [
+ :assignee_username,
+ :author_username,
+ :epic_id,
+ :iteration_id,
+ :label_name,
+ :milestone_title,
+ :my_reaction_emoji,
+ :weight
+ ].freeze
+
+ LICENSED_PARAMS = [
+ :weight,
+ :epic_id,
+ :my_reaction_emoji,
+ :iteration_id
+ ].freeze
+
STRONG_PARAMS_DEFINITION = [
:created_before,
:created_after,
@@ -22,13 +40,11 @@ module Gitlab
:page,
:stage_id,
:end_event_filter,
- :weight,
- :epic_id,
- :my_reaction_emoji,
- :iteration_id,
+ *LICENSED_PARAMS,
label_name: [].freeze,
assignee_username: [].freeze,
- project_ids: [].freeze
+ project_ids: [].freeze,
+ not: NEGATABLE_PARAMS
].freeze
FINDER_PARAM_NAMES = [
@@ -54,6 +70,7 @@ module Gitlab
attribute :epic_id
attribute :my_reaction_emoji
attribute :iteration_id
+ attribute :not, default: -> { {} }
FINDER_PARAM_NAMES.each do |param_name|
attribute param_name
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
deleted file mode 100644
index 2314246da55..00000000000
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-# This module can be included in migrations to make it easier to rename paths
-# of `Namespace` & `Project` models certain paths would become `reserved`.
-#
-# If the way things are stored on the filesystem related to namespaces and
-# projects ever changes. Don't update this module, or anything nested in `V1`,
-# since it needs to keep functioning for all migrations using it using the state
-# that the data is in at the time. Instead, create a `V2` module that implements
-# the new way of reserving paths.
-module Gitlab
- module Database
- module RenameReservedPathsMigration
- module V1
- def self.included(kls)
- kls.include(MigrationHelpers)
- end
-
- def rename_wildcard_paths(one_or_more_paths)
- rename_child_paths(one_or_more_paths)
- paths = Array(one_or_more_paths)
- RenameProjects.new(paths, self).rename_projects
- end
-
- def rename_child_paths(one_or_more_paths)
- paths = Array(one_or_more_paths)
- RenameNamespaces.new(paths, self).rename_namespaces(type: :child)
- end
-
- def rename_root_paths(paths)
- paths = Array(paths)
- RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level)
- end
-
- def revert_renames
- RenameProjects.new([], self).revert_renames
- RenameNamespaces.new([], self).revert_renames
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
deleted file mode 100644
index f1dc3ed74fe..00000000000
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- module RenameReservedPathsMigration
- module V1
- module MigrationClasses
- module Routable
- def full_path
- if route && route.path.present?
- @full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
- else
- update_route if persisted?
-
- build_full_path
- end
- end
-
- def build_full_path
- if parent && path
- parent.full_path + '/' + path
- else
- path
- end
- end
-
- def update_route
- prepare_route
- route.save
- end
-
- def prepare_route
- route || build_route(source: self)
- route.path = build_full_path
- @full_path = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
- end
- end
-
- class Namespace < ActiveRecord::Base
- include MigrationClasses::Routable
- self.table_name = 'namespaces'
- self.inheritance_column = :_type_disabled
- belongs_to :parent,
- class_name: "#{MigrationClasses.name}::Namespace"
- has_one :route, as: :source
- has_many :children,
- class_name: "#{MigrationClasses.name}::Namespace",
- foreign_key: :parent_id
-
- # Overridden to have the correct `source_type` for the `route` relation
- def self.name
- 'Namespace'
- end
-
- def kind
- type == 'Group' ? 'group' : 'user'
- end
- end
-
- class User < ActiveRecord::Base
- self.table_name = 'users'
- end
-
- class Route < ActiveRecord::Base
- self.table_name = 'routes'
- belongs_to :source, polymorphic: true
- end
-
- class Project < ActiveRecord::Base
- include MigrationClasses::Routable
- has_one :route, as: :source
- self.table_name = 'projects'
-
- HASHED_STORAGE_FEATURES = {
- repository: 1,
- attachments: 2
- }.freeze
-
- def repository_storage_path
- Gitlab.config.repositories.storages[repository_storage].legacy_disk_path
- end
-
- # Overridden to have the correct `source_type` for the `route` relation
- def self.name
- 'Project'
- end
-
- def hashed_storage?(feature)
- raise ArgumentError, "Invalid feature" unless HASHED_STORAGE_FEATURES.include?(feature)
- return false unless respond_to?(:storage_version)
-
- self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature]
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
deleted file mode 100644
index 2c9d0d6c0d1..00000000000
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- module RenameReservedPathsMigration
- module V1
- class RenameBase
- attr_reader :paths, :migration
-
- delegate :update_column_in_batches,
- :execute,
- :replace_sql,
- :quote_string,
- :say,
- to: :migration
-
- def initialize(paths, migration)
- @paths = paths
- @migration = migration
- end
-
- def path_patterns
- @path_patterns ||= paths.flat_map { |path| ["%/#{path}", path] }
- end
-
- def rename_path_for_routable(routable)
- old_path = routable.path
- old_full_path = routable.full_path
- # Only remove the last occurrence of the path name to get the parent namespace path
- namespace_path = remove_last_occurrence(old_full_path, old_path)
- new_path = rename_path(namespace_path, old_path)
- new_full_path = join_routable_path(namespace_path, new_path)
-
- perform_rename(routable, old_full_path, new_full_path)
-
- [old_full_path, new_full_path]
- end
-
- def perform_rename(routable, old_full_path, new_full_path)
- # skips callbacks & validations
- new_path = new_full_path.split('/').last
- routable.class.where(id: routable)
- .update_all(path: new_path)
-
- rename_routes(old_full_path, new_full_path)
- end
-
- def rename_routes(old_full_path, new_full_path)
- routes = Route.arel_table
-
- quoted_old_full_path = quote_string(old_full_path)
- quoted_old_wildcard_path = quote_string("#{old_full_path}/%")
-
- filter =
- "routes.id IN "\
- "( SELECT routes.id FROM routes WHERE lower(routes.path) = lower('#{quoted_old_full_path}') "\
- "UNION SELECT routes.id FROM routes WHERE routes.path ILIKE '#{quoted_old_wildcard_path}' )"
-
- replace_statement = replace_sql(Route.arel_table[:path],
- old_full_path,
- new_full_path)
-
- update = Arel::UpdateManager.new
- .table(routes)
- .set([[routes[:path], replace_statement]])
- .where(Arel::Nodes::SqlLiteral.new(filter))
-
- execute(update.to_sql)
- end
-
- def rename_path(namespace_path, path_was)
- counter = 0
- path = "#{path_was}#{counter}"
-
- while route_exists?(join_routable_path(namespace_path, path))
- counter += 1
- path = "#{path_was}#{counter}"
- end
-
- path
- end
-
- def remove_last_occurrence(string, pattern)
- string.reverse.sub(pattern.reverse, "").reverse
- end
-
- def join_routable_path(namespace_path, top_level)
- if namespace_path.present?
- File.join(namespace_path, top_level)
- else
- top_level
- end
- end
-
- def route_exists?(full_path)
- MigrationClasses::Route.where(Route.arel_table[:path].matches(full_path)).any?
- end
-
- def move_pages(old_path, new_path)
- move_folders(pages_dir, old_path, new_path)
- end
-
- def move_uploads(old_path, new_path)
- return unless file_storage?
-
- move_folders(uploads_dir, old_path, new_path)
- end
-
- def move_folders(directory, old_relative_path, new_relative_path)
- old_path = File.join(directory, old_relative_path)
- unless File.directory?(old_path)
- say "#{old_path} doesn't exist, skipping"
- return
- end
-
- new_path = File.join(directory, new_relative_path)
- FileUtils.mv(old_path, new_path)
- end
-
- def remove_cached_html_for_projects(project_ids)
- project_ids.each do |project_id|
- update_column_in_batches(:projects, :description_html, nil) do |table, query|
- query.where(table[:id].eq(project_id))
- end
-
- update_column_in_batches(:issues, :description_html, nil) do |table, query|
- query.where(table[:project_id].eq(project_id))
- end
-
- update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
- query.where(table[:target_project_id].eq(project_id))
- end
-
- update_column_in_batches(:notes, :note_html, nil) do |table, query|
- query.where(table[:project_id].eq(project_id))
- end
-
- update_column_in_batches(:milestones, :description_html, nil) do |table, query|
- query.where(table[:project_id].eq(project_id))
- end
- end
- end
-
- def track_rename(type, old_path, new_path)
- key = redis_key_for_type(type)
- Gitlab::Redis::SharedState.with do |redis|
- redis.lpush(key, [old_path, new_path].to_json)
- redis.expire(key, 2.weeks.to_i)
- end
- say "tracked rename: #{key}: #{old_path} -> #{new_path}"
- end
-
- def reverts_for_type(type)
- key = redis_key_for_type(type)
-
- Gitlab::Redis::SharedState.with do |redis|
- failed_reverts = []
-
- while rename_info = redis.lpop(key)
- path_before_rename, path_after_rename = Gitlab::Json.parse(rename_info)
- say "renaming #{type} from #{path_after_rename} back to #{path_before_rename}"
- begin
- yield(path_before_rename, path_after_rename)
- rescue StandardError => e
- failed_reverts << rename_info
- say "Renaming #{type} from #{path_after_rename} back to "\
- "#{path_before_rename} failed. Review the error and try "\
- "again by running the `down` action. \n"\
- "#{e.message}: \n #{e.backtrace.join("\n")}"
- end
- end
-
- failed_reverts.each { |rename_info| redis.lpush(key, rename_info) }
- end
- end
-
- def redis_key_for_type(type)
- "rename:#{migration.name}:#{type}"
- end
-
- def file_storage?
- CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
- end
-
- def uploads_dir
- File.join(CarrierWave.root, "uploads")
- end
-
- def pages_dir
- Settings.pages.path
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
deleted file mode 100644
index 72ae2849911..00000000000
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- module RenameReservedPathsMigration
- module V1
- class RenameNamespaces < RenameBase
- include Gitlab::ShellAdapter
-
- def rename_namespaces(type:)
- namespaces_for_paths(type: type).each do |namespace|
- rename_namespace(namespace)
- end
- end
-
- def namespaces_for_paths(type:)
- namespaces = case type
- when :child
- MigrationClasses::Namespace.where.not(parent_id: nil)
- when :top_level
- MigrationClasses::Namespace.where(parent_id: nil)
- end
- with_paths = MigrationClasses::Route.arel_table[:path]
- .matches_any(path_patterns)
- namespaces.joins(:route).where(with_paths)
- .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
- end
-
- def rename_namespace(namespace)
- old_full_path, new_full_path = rename_path_for_routable(namespace)
-
- track_rename('namespace', old_full_path, new_full_path)
-
- rename_namespace_dependencies(namespace, old_full_path, new_full_path)
- end
-
- def rename_namespace_dependencies(namespace, old_full_path, new_full_path)
- move_repositories(namespace, old_full_path, new_full_path)
- move_uploads(old_full_path, new_full_path)
- move_pages(old_full_path, new_full_path)
- rename_user(old_full_path, new_full_path) if namespace.kind == 'user'
- remove_cached_html_for_projects(projects_for_namespace(namespace).map(&:id))
- end
-
- def revert_renames
- reverts_for_type('namespace') do |path_before_rename, current_path|
- matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path)
- namespace = MigrationClasses::Namespace.joins(:route)
- .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
- .find_by(matches_path)&.becomes(MigrationClasses::Namespace) # rubocop: disable Cop/AvoidBecomes
-
- if namespace
- perform_rename(namespace, current_path, path_before_rename)
-
- rename_namespace_dependencies(namespace, current_path, path_before_rename)
- else
- say "Couldn't rename namespace from #{current_path} back to #{path_before_rename}, "\
- "namespace was renamed, or no longer exists at the expected path"
- end
- end
- end
-
- def rename_user(old_username, new_username)
- MigrationClasses::User.where(username: old_username)
- .update_all(username: new_username)
- end
-
- def move_repositories(namespace, old_full_path, new_full_path)
- repo_shards_for_namespace(namespace).each do |repository_storage|
- # Ensure old directory exists before moving it
- Gitlab::GitalyClient::NamespaceService.allow do
- gitlab_shell.add_namespace(repository_storage, old_full_path)
-
- unless gitlab_shell.mv_namespace(repository_storage, old_full_path, new_full_path)
- message = "Exception moving on shard #{repository_storage} from #{old_full_path} to #{new_full_path}"
- Gitlab::AppLogger.error message
- end
- end
- end
- end
-
- def repo_shards_for_namespace(namespace)
- projects_for_namespace(namespace).distinct.select(:repository_storage)
- .map(&:repository_storage)
- end
-
- def projects_for_namespace(namespace)
- namespace_ids = child_ids_for_parent(namespace, ids: [namespace.id])
- namespace_or_children = MigrationClasses::Project
- .arel_table[:namespace_id]
- .in(namespace_ids)
- MigrationClasses::Project.where(namespace_or_children)
- end
-
- def child_ids_for_parent(namespace, ids: [])
- namespace.children.each do |child|
- ids << child.id
- child_ids_for_parent(child, ids: ids) if child.children.any?
- end
- ids
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
deleted file mode 100644
index 155e35b64f4..00000000000
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- module RenameReservedPathsMigration
- module V1
- class RenameProjects < RenameBase
- include Gitlab::ShellAdapter
-
- def rename_projects
- projects_for_paths.each do |project|
- rename_project(project)
- end
-
- remove_cached_html_for_projects(projects_for_paths.map(&:id))
- end
-
- def rename_project(project)
- old_full_path, new_full_path = rename_path_for_routable(project)
-
- track_rename('project', old_full_path, new_full_path)
-
- move_project_folders(project, old_full_path, new_full_path)
- end
-
- def move_project_folders(project, old_full_path, new_full_path)
- unless project.hashed_storage?(:repository)
- move_repository(project, old_full_path, new_full_path)
- move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
- end
-
- move_uploads(old_full_path, new_full_path) unless project.hashed_storage?(:attachments)
- move_pages(old_full_path, new_full_path)
- end
-
- def revert_renames
- reverts_for_type('project') do |path_before_rename, current_path|
- matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path)
- project = MigrationClasses::Project.joins(:route)
- .allow_cross_joins_across_databases(url:
- 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843')
- .find_by(matches_path)
-
- if project
- perform_rename(project, current_path, path_before_rename)
-
- move_project_folders(project, current_path, path_before_rename)
- else
- say "Couldn't rename project from #{current_path} back to "\
- "#{path_before_rename}, project was renamed or no longer "\
- "exists at the expected path."
-
- end
- end
- end
-
- def move_repository(project, old_path, new_path)
- unless gitlab_shell.mv_repository(project.repository_storage,
- old_path,
- new_path)
- Gitlab::AppLogger.error "Error moving #{old_path} to #{new_path}"
- end
- end
-
- def projects_for_paths
- return @projects_for_paths if @projects_for_paths
-
- with_paths = MigrationClasses::Route.arel_table[:path]
- .matches_any(path_patterns)
-
- @projects_for_paths = MigrationClasses::Project.joins(:route).where(with_paths)
- .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843')
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/graphql/pagination/active_record_array_connection.rb b/lib/gitlab/graphql/pagination/active_record_array_connection.rb
index 9e40f79b2fd..ce16693cf89 100644
--- a/lib/gitlab/graphql/pagination/active_record_array_connection.rb
+++ b/lib/gitlab/graphql/pagination/active_record_array_connection.rb
@@ -59,6 +59,7 @@ module Gitlab
def dup
self.class.new(
items.dup,
+ context: context,
first: first,
after: after,
max_page_size: max_page_size,
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index c3378856633..4109e952c8f 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -379,7 +379,6 @@ module Gitlab
bulk_imports: {
gitlab_v1: count(::BulkImport.where(**time_period, source_type: :gitlab))
},
- project_imports: project_imports(time_period),
issue_imports: issue_imports(time_period),
group_imports: group_imports(time_period)
}
@@ -569,24 +568,6 @@ module Gitlab
omniauth_provider_names.reject { |name| name.starts_with?('ldap') }
end
- def project_imports(time_period)
- time_frame = metric_time_period(time_period)
- counters = {
- gitlab_project: add_metric('CountImportedProjectsMetric', time_frame: time_frame, options: { import_type: 'gitlab_project' }),
- github: add_metric('CountImportedProjectsMetric', time_frame: time_frame, options: { import_type: 'github' }),
- bitbucket: add_metric('CountImportedProjectsMetric', time_frame: time_frame, options: { import_type: 'bitbucket' }),
- bitbucket_server: add_metric('CountImportedProjectsMetric', time_frame: time_frame, options: { import_type: 'bitbucket_server' }),
- gitea: add_metric('CountImportedProjectsMetric', time_frame: time_frame, options: { import_type: 'gitea' }),
- git: add_metric('CountImportedProjectsMetric', time_frame: time_frame, options: { import_type: 'git' }),
- manifest: add_metric('CountImportedProjectsMetric', time_frame: time_frame, options: { import_type: 'manifest' }),
- gitlab_migration: add_metric('CountBulkImportsEntitiesMetric', time_frame: time_frame, options: { source_type: :project_entity })
- }
-
- counters[:total] = add_metric('CountImportedProjectsTotalMetric', time_frame: time_frame)
-
- counters
- end
-
def issue_imports(time_period)
time_frame = metric_time_period(time_period)
{
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index 664f2f22917..657c2c00642 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -24,7 +24,7 @@ module QA
end
def latest_pipeline_status
- latest_pipeline.find(element_selector_css('ci-badge-link')).text
+ latest_pipeline.find(element_selector_css('ci-badge-text')).text
end
# If no status provided, wait for pipeline to complete
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 9657cf33afd..c20f92cd2f0 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -98,18 +98,9 @@ RSpec.describe Projects::ProjectMembersController do
end
end
- context 'when invited group members are present' do
+ shared_examples 'users are invited through groups' do
let_it_be(:invited_group_member) { create(:user) }
- before do
- group.add_owner(invited_group_member)
-
- project.invited_groups << group
- project.add_maintainer(user)
-
- sign_in(user)
- end
-
context 'when webui_members_inherited_users is disabled' do
before do
stub_feature_flags(webui_members_inherited_users: false)
@@ -128,6 +119,35 @@ RSpec.describe Projects::ProjectMembersController do
expect(assigns(:project_members).map(&:user_id)).to include(invited_group_member.id)
end
end
+
+ context 'when invited group members are present' do
+ before do
+ group.add_owner(invited_group_member)
+
+ project.invited_groups << group
+ project.add_maintainer(user)
+
+ sign_in(user)
+ end
+
+ include_examples 'users are invited through groups'
+ end
+
+ context 'when group is invited to project parent' do
+ let_it_be(:parent_group) { create(:group, :public) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: parent_group) }
+
+ before do
+ group.add_owner(invited_group_member)
+
+ parent_group.shared_with_groups << group
+ project.add_maintainer(user)
+
+ sign_in(user)
+ end
+
+ include_examples 'users are invited through groups'
+ end
end
context 'invited members' do
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 2df9f482bb9..6f2cd4bf596 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -15,6 +15,10 @@ FactoryBot.define do
state { :stopped }
end
+ trait :stopping do
+ state { :stopping }
+ end
+
trait :production do
name { 'production' }
end
diff --git a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
index 892ab53a53e..d3cda7d9c8f 100644
--- a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
+++ b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
@@ -206,8 +206,8 @@ RSpec.describe ::CachingArrayResolver do
def resolve_users(admin:, resolver: caching_resolver)
args = { is_admin: admin }
- opts = resolver.field_options
- allow(resolver).to receive(:field_options).and_return(opts.merge(max_page_size: max_page_size))
+ allow(resolver).to receive(:has_max_page_size?).and_return(true)
+ allow(resolver).to receive(:max_page_size).and_return(max_page_size)
resolve(resolver, args: args, ctx: query_context, schema: schema, arg_style: :internal)
end
end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
deleted file mode 100644
index 370d03b495c..00000000000
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ /dev/null
@@ -1,292 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete, feature_category: :groups_and_projects do
- let(:migration) { FakeRenameReservedPathMigrationV1.new }
- let(:subject) { described_class.new(['the-path'], migration) }
-
- before do
- allow(migration).to receive(:say)
- TestEnv.clean_test_path
- end
-
- def migration_namespace(namespace)
- Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
- Namespace.find(namespace.id)
- end
-
- def migration_project(project)
- Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
- Project.find(project.id)
- end
-
- describe "#remove_last_occurrence" do
- it "removes only the last occurrence of a string" do
- input = "this/is/a-word-to-replace/namespace/with/a-word-to-replace"
-
- expect(subject.remove_last_occurrence(input, "a-word-to-replace"))
- .to eq("this/is/a-word-to-replace/namespace/with/")
- end
- end
-
- describe '#remove_cached_html_for_projects' do
- let(:project) { create(:project, description_html: 'Project description') }
-
- it 'removes description_html from projects' do
- subject.remove_cached_html_for_projects([project.id])
-
- expect(project.reload.description_html).to be_nil
- end
-
- it 'removes issue descriptions' do
- issue = create(:issue, project: project, description_html: 'Issue description')
-
- subject.remove_cached_html_for_projects([project.id])
-
- expect(issue.reload.description_html).to be_nil
- end
-
- it 'removes merge request descriptions' do
- merge_request = create(:merge_request,
- source_project: project,
- target_project: project,
- description_html: 'MergeRequest description')
-
- subject.remove_cached_html_for_projects([project.id])
-
- expect(merge_request.reload.description_html).to be_nil
- end
-
- it 'removes note html' do
- note = create(:note,
- project: project,
- noteable: create(:issue, project: project),
- note_html: 'note description')
-
- subject.remove_cached_html_for_projects([project.id])
-
- expect(note.reload.note_html).to be_nil
- end
-
- it 'removes milestone description' do
- milestone = create(:milestone,
- project: project,
- description_html: 'milestone description')
-
- subject.remove_cached_html_for_projects([project.id])
-
- expect(milestone.reload.description_html).to be_nil
- end
- end
-
- describe '#rename_path_for_routable' do
- context 'for personal namespaces' do
- let(:namespace) { create(:namespace, path: 'the-path') }
-
- it "renames namespaces called the-path" do
- subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(namespace.reload.path).to eq("the-path0")
- end
-
- it "renames the route to the namespace" do
- subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(Namespace.find(namespace.id).full_path).to eq("the-path0")
- end
-
- it "renames the route for projects of the namespace" do
- project = create(:project, :repository, path: "project-path", namespace: namespace)
-
- subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(project.route.reload.path).to eq("the-path0/project-path")
- end
-
- it 'returns the old & the new path' do
- old_path, new_path = subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(old_path).to eq('the-path')
- expect(new_path).to eq('the-path0')
- end
-
- it "doesn't rename routes that start with a similar name" do
- other_namespace = create(:namespace, path: 'the-path-but-not-really')
- project = create(:project, path: 'the-project', namespace: other_namespace)
-
- subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(project.route.reload.path).to eq('the-path-but-not-really/the-project')
- end
- end
-
- context 'for groups' do
- context "the-path group -> subgroup -> the-path0 project" do
- it "updates the route of the project correctly" do
- group = create(:group, path: 'the-path')
- subgroup = create(:group, path: "subgroup", parent: group)
- project = create(:project, :repository, path: "the-path0", namespace: subgroup)
-
- subject.rename_path_for_routable(migration_namespace(group))
-
- expect(project.route.reload.path).to eq("the-path0/subgroup/the-path0")
- end
- end
- end
-
- context 'for projects' do
- let(:parent) { create(:namespace, path: 'the-parent') }
- let(:project) { create(:project, path: 'the-path', namespace: parent) }
-
- it 'renames the project called `the-path`' do
- subject.rename_path_for_routable(migration_project(project))
-
- expect(project.reload.path).to eq('the-path0')
- end
-
- it 'renames the route for the project' do
- subject.rename_path_for_routable(project)
-
- expect(project.reload.route.path).to eq('the-parent/the-path0')
- end
-
- it 'returns the old & new path' do
- old_path, new_path = subject.rename_path_for_routable(migration_project(project))
-
- expect(old_path).to eq('the-parent/the-path')
- expect(new_path).to eq('the-parent/the-path0')
- end
- end
- end
-
- describe '#perform_rename' do
- context 'for personal namespaces' do
- it 'renames the path' do
- namespace = create(:namespace, path: 'the-path')
-
- subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed')
-
- expect(namespace.reload.path).to eq('renamed')
- expect(namespace.reload.route.path).to eq('renamed')
- end
- end
-
- context 'for groups' do
- it 'renames all the routes for the group' do
- group = create(:group, path: 'the-path')
- child = create(:group, path: 'child', parent: group)
- project = create(:project, :repository, namespace: child, path: 'the-project')
- other_one = create(:group, path: 'the-path-is-similar')
-
- subject.perform_rename(migration_namespace(group), 'the-path', 'renamed')
-
- expect(group.reload.route.path).to eq('renamed')
- expect(child.reload.route.path).to eq('renamed/child')
- expect(project.reload.route.path).to eq('renamed/child/the-project')
- expect(other_one.reload.route.path).to eq('the-path-is-similar')
- end
- end
- end
-
- describe '#move_pages' do
- it 'moves the pages directory' do
- expect(subject).to receive(:move_folders)
- .with(TestEnv.pages_path, 'old-path', 'new-path')
-
- subject.move_pages('old-path', 'new-path')
- end
- end
-
- describe "#move_uploads" do
- let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') }
- let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') }
-
- it 'moves subdirectories in the uploads folder' do
- expect(subject).to receive(:uploads_dir).and_return(uploads_dir)
- expect(subject).to receive(:move_folders).with(uploads_dir, 'old_path', 'new_path')
-
- subject.move_uploads('old_path', 'new_path')
- end
-
- it "doesn't move uploads when they are stored in object storage" do
- expect(subject).to receive(:file_storage?).and_return(false)
- expect(subject).not_to receive(:move_folders)
-
- subject.move_uploads('old_path', 'new_path')
- end
- end
-
- describe '#move_folders' do
- let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') }
- let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') }
-
- before do
- FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
- FileUtils.mkdir_p(uploads_dir)
- allow(subject).to receive(:uploads_dir).and_return(uploads_dir)
- end
-
- it 'moves a folder with files' do
- source = File.join(uploads_dir, 'parent-group', 'sub-group')
- FileUtils.mkdir_p(source)
- destination = File.join(uploads_dir, 'parent-group', 'moved-group')
- FileUtils.touch(File.join(source, 'test.txt'))
- expected_file = File.join(destination, 'test.txt')
-
- subject.move_folders(uploads_dir, File.join('parent-group', 'sub-group'), File.join('parent-group', 'moved-group'))
-
- expect(File.exist?(expected_file)).to be(true)
- end
- end
-
- describe '#track_rename', :redis do
- it 'tracks a rename in redis' do
- key = 'rename:FakeRenameReservedPathMigrationV1:namespace'
-
- subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed')
-
- old_path = nil
- new_path = nil
- Gitlab::Redis::SharedState.with do |redis|
- rename_info = redis.lpop(key)
- old_path, new_path = Gitlab::Json.parse(rename_info)
- end
-
- expect(old_path).to eq('path/to/namespace')
- expect(new_path).to eq('path/to/renamed')
- end
- end
-
- describe '#reverts_for_type', :redis do
- it 'yields for each tracked rename' do
- subject.track_rename('project', 'old_path', 'new_path')
- subject.track_rename('project', 'old_path2', 'new_path2')
- subject.track_rename('namespace', 'namespace_path', 'new_namespace_path')
-
- expect { |b| subject.reverts_for_type('project', &b) }
- .to yield_successive_args(%w(old_path2 new_path2), %w(old_path new_path))
- expect { |b| subject.reverts_for_type('namespace', &b) }
- .to yield_with_args('namespace_path', 'new_namespace_path')
- end
-
- it 'keeps the revert in redis if it failed' do
- subject.track_rename('project', 'old_path', 'new_path')
-
- subject.reverts_for_type('project') do
- raise 'whatever happens, keep going!'
- end
-
- key = 'rename:FakeRenameReservedPathMigrationV1:project'
- stored_renames = nil
- rename_count = 0
- Gitlab::Redis::SharedState.with do |redis|
- stored_renames = redis.lrange(key, 0, 1)
- rename_count = redis.llen(key)
- end
-
- expect(rename_count).to eq(1)
- expect(Gitlab::Json.parse(stored_renames.first)).to eq(%w(old_path new_path))
- end
- end
-end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
deleted file mode 100644
index b00a1d4a9e1..00000000000
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ /dev/null
@@ -1,313 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete,
-feature_category: :groups_and_projects do
- let(:migration) { FakeRenameReservedPathMigrationV1.new }
- let(:subject) { described_class.new(['the-path'], migration) }
- let(:namespace) { create(:group, name: 'the-path') }
-
- before do
- allow(migration).to receive(:say)
- TestEnv.clean_test_path
- end
-
- def migration_namespace(namespace)
- Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
- Namespace.find(namespace.id)
- end
-
- describe '#namespaces_for_paths' do
- context 'nested namespaces' do
- let(:subject) { described_class.new(['parent/the-Path'], migration) }
-
- it 'includes the namespace' do
- parent = create(:group, path: 'parent')
- child = create(:group, path: 'the-path', parent: parent)
-
- found_ids = subject.namespaces_for_paths(type: :child)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(child.id)
- end
- end
-
- context 'for child namespaces' do
- it 'only returns child namespaces with the correct path' do
- _root_namespace = create(:group, path: 'THE-path')
- _other_path = create(:group,
- path: 'other',
- parent: create(:group))
- namespace = create(:group,
- path: 'the-path',
- parent: create(:group))
-
- found_ids = subject.namespaces_for_paths(type: :child)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(namespace.id)
- end
-
- it 'has no namespaces that look the same' do
- _root_namespace = create(:group, path: 'THE-path')
- _similar_path = create(:group,
- path: 'not-really-the-path',
- parent: create(:group))
- namespace = create(:group,
- path: 'the-path',
- parent: create(:group))
-
- found_ids = subject.namespaces_for_paths(type: :child)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(namespace.id)
- end
- end
-
- context 'for top levelnamespaces' do
- it 'only returns child namespaces with the correct path' do
- root_namespace = create(:group, path: 'the-path')
- _other_path = create(:group, path: 'other')
- _child_namespace = create(:group,
- path: 'the-path',
- parent: create(:group))
-
- found_ids = subject.namespaces_for_paths(type: :top_level)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(root_namespace.id)
- end
-
- it 'has no namespaces that just look the same' do
- root_namespace = create(:group, path: 'the-path')
- _similar_path = create(:group, path: 'not-really-the-path')
- _child_namespace = create(:group,
- path: 'the-path',
- parent: create(:group))
-
- found_ids = subject.namespaces_for_paths(type: :top_level)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(root_namespace.id)
- end
- end
- end
-
- describe '#move_repositories' do
- let(:namespace) { create(:group, name: 'hello-group') }
-
- it 'moves a project for a namespace' do
- project = create(:project, :repository, :legacy_storage, namespace: namespace, path: 'hello-project')
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'bye-group/hello-project.git',
- nil,
- nil
- )
-
- subject.move_repositories(namespace, 'hello-group', 'bye-group')
-
- expect(expected_repository).to exist
- end
-
- it 'moves a namespace in a subdirectory correctly' do
- child_namespace = create(:group, name: 'sub-group', parent: namespace)
- project = create(:project, :repository, :legacy_storage, namespace: child_namespace, path: 'hello-project')
-
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'hello-group/renamed-sub-group/hello-project.git',
- nil,
- nil
- )
-
- subject.move_repositories(child_namespace, 'hello-group/sub-group', 'hello-group/renamed-sub-group')
-
- expect(expected_repository).to exist
- end
-
- it 'moves a parent namespace with subdirectories' do
- child_namespace = create(:group, name: 'sub-group', parent: namespace)
- project = create(:project, :repository, :legacy_storage, namespace: child_namespace, path: 'hello-project')
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'renamed-group/sub-group/hello-project.git',
- nil,
- nil
- )
-
- subject.move_repositories(child_namespace, 'hello-group', 'renamed-group')
-
- expect(expected_repository).to exist
- end
- end
-
- describe "#child_ids_for_parent" do
- it "collects child ids for all levels" do
- parent = create(:group)
- first_child = create(:group, parent: parent)
- second_child = create(:group, parent: parent)
- third_child = create(:group, parent: second_child)
- all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
-
- collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id])
-
- expect(collected_ids).to contain_exactly(*all_ids)
- end
- end
-
- describe "#rename_namespace" do
- it 'renames paths & routes for the namespace' do
- expect(subject).to receive(:rename_path_for_routable)
- .with(namespace)
- .and_call_original
-
- subject.rename_namespace(namespace)
-
- expect(namespace.reload.path).to eq('the-path0')
- end
-
- it 'tracks the rename' do
- expect(subject).to receive(:track_rename)
- .with('namespace', 'the-path', 'the-path0')
-
- subject.rename_namespace(namespace)
- end
-
- it 'renames things related to the namespace' do
- expect(subject).to receive(:rename_namespace_dependencies)
- .with(namespace, 'the-path', 'the-path0')
-
- subject.rename_namespace(namespace)
- end
- end
-
- describe '#rename_namespace_dependencies' do
- it "moves the repository for a project in the namespace" do
- project = create(:project, :repository, :legacy_storage, namespace: namespace, path: "the-path-project")
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- "the-path0/the-path-project.git",
- nil,
- nil
- )
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
-
- expect(expected_repository).to exist
- end
-
- it "moves the uploads for the namespace" do
- expect(subject).to receive(:move_uploads).with("the-path", "the-path0")
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
- end
-
- it "moves the pages for the namespace" do
- expect(subject).to receive(:move_pages).with("the-path", "the-path0")
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
- end
-
- it 'invalidates the markdown cache of related projects' do
- project = create(:project, :legacy_storage, namespace: namespace, path: "the-path-project")
-
- expect(subject).to receive(:remove_cached_html_for_projects).with([project.id])
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
- end
-
- it "doesn't rename users for other namespaces" do
- expect(subject).not_to receive(:rename_user)
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
- end
-
- it 'renames the username of a namespace for a user' do
- user = create(:user, username: 'the-path')
-
- expect(subject).to receive(:rename_user).with('the-path', 'the-path0')
-
- subject.rename_namespace_dependencies(user.namespace, 'the-path', 'the-path0')
- end
- end
-
- describe '#rename_user' do
- it 'renames a username' do
- subject = described_class.new([], migration)
- user = create(:user, username: 'broken')
-
- subject.rename_user('broken', 'broken0')
-
- expect(user.reload.username).to eq('broken0')
- end
- end
-
- describe '#rename_namespaces' do
- let!(:top_level_namespace) { create(:group, path: 'the-path') }
- let!(:child_namespace) do
- create(:group, path: 'the-path', parent: create(:group))
- end
-
- it 'renames top level namespaces the namespace' do
- expect(subject).to receive(:rename_namespace)
- .with(migration_namespace(top_level_namespace))
-
- subject.rename_namespaces(type: :top_level)
- end
-
- it 'renames child namespaces' do
- expect(subject).to receive(:rename_namespace)
- .with(migration_namespace(child_namespace))
-
- subject.rename_namespaces(type: :child)
- end
- end
-
- describe '#revert_renames', :redis do
- it 'renames the routes back to the previous values' do
- project = create(:project, :legacy_storage, :repository, path: 'a-project', namespace: namespace)
- subject.rename_namespace(namespace)
-
- expect(subject).to receive(:perform_rename)
- .with(
- kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace),
- 'the-path0',
- 'the-path'
- ).and_call_original
-
- subject.revert_renames
-
- expect(namespace.reload.path).to eq('the-path')
- expect(namespace.reload.route.path).to eq('the-path')
- expect(project.reload.route.path).to eq('the-path/a-project')
- end
-
- it 'moves the repositories back to their original place' do
- project = create(:project, :repository, :legacy_storage, path: 'a-project', namespace: namespace)
- project.create_repository
- subject.rename_namespace(namespace)
-
- expected_repository = Gitlab::Git::Repository.new(project.repository_storage, 'the-path/a-project.git', nil, nil)
-
- expect(subject).to receive(:rename_namespace_dependencies)
- .with(
- kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace),
- 'the-path0',
- 'the-path'
- ).and_call_original
-
- subject.revert_renames
-
- expect(expected_repository).to exist
- end
-
- it "doesn't break when the namespace was renamed" do
- subject.rename_namespace(namespace)
- namespace.update!(path: 'renamed-afterwards')
-
- expect { subject.revert_renames }.not_to raise_error
- end
- end
-end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
deleted file mode 100644
index d2665664fb0..00000000000
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete,
-feature_category: :groups_and_projects do
- let(:migration) { FakeRenameReservedPathMigrationV1.new }
- let(:subject) { described_class.new(['the-path'], migration) }
- let(:project) do
- create(:project,
- :legacy_storage,
- path: 'the-path',
- namespace: create(:namespace, path: 'known-parent' ))
- end
-
- before do
- allow(migration).to receive(:say)
- TestEnv.clean_test_path
- end
-
- describe '#projects_for_paths' do
- it 'searches using nested paths' do
- namespace = create(:namespace, path: 'hello')
- project = create(:project, :legacy_storage, path: 'THE-path', namespace: namespace)
-
- result_ids = described_class.new(['Hello/the-path'], migration)
- .projects_for_paths.map(&:id)
-
- expect(result_ids).to contain_exactly(project.id)
- end
-
- it 'includes the correct projects' do
- project = create(:project, :legacy_storage, path: 'THE-path')
- _other_project = create(:project, :legacy_storage)
-
- result_ids = subject.projects_for_paths.map(&:id)
-
- expect(result_ids).to contain_exactly(project.id)
- end
- end
-
- describe '#rename_projects' do
- let!(:projects) { create_list(:project, 2, :legacy_storage, path: 'the-path') }
-
- it 'renames each project' do
- expect(subject).to receive(:rename_project).twice
-
- subject.rename_projects
- end
-
- it 'invalidates the markdown cache of related projects' do
- expect(subject).to receive(:remove_cached_html_for_projects)
- .with(a_collection_containing_exactly(*projects.map(&:id)))
-
- subject.rename_projects
- end
- end
-
- describe '#rename_project' do
- it 'renames path & route for the project' do
- expect(subject).to receive(:rename_path_for_routable)
- .with(project)
- .and_call_original
-
- subject.rename_project(project)
-
- expect(project.reload.path).to eq('the-path0')
- end
-
- it 'tracks the rename' do
- expect(subject).to receive(:track_rename)
- .with('project', 'known-parent/the-path', 'known-parent/the-path0')
-
- subject.rename_project(project)
- end
-
- it 'renames the folders for the project' do
- expect(subject).to receive(:move_project_folders).with(project, 'known-parent/the-path', 'known-parent/the-path0')
-
- subject.rename_project(project)
- end
- end
-
- describe '#move_project_folders' do
- it 'moves the wiki & the repo' do
- expect(subject).to receive(:move_repository)
- .with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki')
- expect(subject).to receive(:move_repository)
- .with(project, 'known-parent/the-path', 'known-parent/the-path0')
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
-
- it 'does not move the repositories when hashed storage is enabled' do
- project.update!(storage_version: Project::HASHED_STORAGE_FEATURES[:repository])
-
- expect(subject).not_to receive(:move_repository)
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
-
- it 'moves uploads' do
- expect(subject).to receive(:move_uploads)
- .with('known-parent/the-path', 'known-parent/the-path0')
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
-
- it 'does not move uploads when hashed storage is enabled for attachments' do
- project.update!(storage_version: Project::HASHED_STORAGE_FEATURES[:attachments])
-
- expect(subject).not_to receive(:move_uploads)
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
-
- it 'moves pages' do
- expect(subject).to receive(:move_pages)
- .with('known-parent/the-path', 'known-parent/the-path0')
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
- end
-
- describe '#move_repository' do
- let(:known_parent) { create(:namespace, path: 'known-parent') }
- let(:project) { create(:project, :repository, :legacy_storage, path: 'the-path', namespace: known_parent) }
-
- it 'moves the repository for a project' do
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'known-parent/new-repo.git',
- nil,
- nil
- )
-
- subject.move_repository(project, 'known-parent/the-path', 'known-parent/new-repo')
-
- expect(expected_repository).to exist
- end
- end
-
- describe '#revert_renames', :redis do
- it 'renames the routes back to the previous values' do
- subject.rename_project(project)
-
- expect(subject).to receive(:perform_rename)
- .with(
- kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project),
- 'known-parent/the-path0',
- 'known-parent/the-path'
- ).and_call_original
-
- subject.revert_renames
-
- expect(project.reload.path).to eq('the-path')
- expect(project.route.path).to eq('known-parent/the-path')
- end
-
- it 'moves the repositories back to their original place' do
- project.create_repository
- subject.rename_project(project)
-
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'known-parent/the-path.git',
- nil,
- nil
- )
-
- expect(subject).to receive(:move_project_folders)
- .with(
- kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project),
- 'known-parent/the-path0',
- 'known-parent/the-path'
- ).and_call_original
-
- subject.revert_renames
-
- expect(expected_repository).to exist
- end
-
- it "doesn't break when the project was renamed" do
- subject.rename_project(project)
- project.update!(path: 'renamed-afterwards')
-
- expect { subject.revert_renames }.not_to raise_error
- end
- end
-end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
deleted file mode 100644
index 3b2d3ab1354..00000000000
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.shared_examples 'renames child namespaces' do |type|
- it 'renames namespaces' do
- rename_namespaces = double
- expect(described_class::RenameNamespaces)
- .to receive(:new).with(%w[first-path second-path], subject)
- .and_return(rename_namespaces)
- expect(rename_namespaces).to receive(:rename_namespaces)
- .with(type: :child)
-
- subject.rename_wildcard_paths(%w[first-path second-path])
- end
-end
-
-RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
- let(:subject) { FakeRenameReservedPathMigrationV1.new }
-
- before do
- allow(subject).to receive(:say)
- end
-
- describe '#rename_child_paths' do
- it_behaves_like 'renames child namespaces'
- end
-
- describe '#rename_wildcard_paths' do
- it_behaves_like 'renames child namespaces'
-
- it 'renames projects' do
- rename_projects = double
- expect(described_class::RenameProjects)
- .to receive(:new).with(['the-path'], subject)
- .and_return(rename_projects)
-
- expect(rename_projects).to receive(:rename_projects)
-
- subject.rename_wildcard_paths(['the-path'])
- end
- end
-
- describe '#rename_root_paths' do
- it 'renames namespaces' do
- rename_namespaces = double
- expect(described_class::RenameNamespaces)
- .to receive(:new).with(['the-path'], subject)
- .and_return(rename_namespaces)
- expect(rename_namespaces).to receive(:rename_namespaces)
- .with(type: :top_level)
-
- subject.rename_root_paths('the-path')
- end
- end
-
- describe '#revert_renames' do
- it 'renames namespaces' do
- rename_namespaces = double
- expect(described_class::RenameNamespaces)
- .to receive(:new).with([], subject)
- .and_return(rename_namespaces)
- expect(rename_namespaces).to receive(:revert_renames)
-
- subject.revert_renames
- end
-
- it 'renames projects' do
- rename_projects = double
- expect(described_class::RenameProjects)
- .to receive(:new).with([], subject)
- .and_return(rename_projects)
- expect(rename_projects).to receive(:revert_renames)
-
- subject.revert_renames
- end
- end
-end
diff --git a/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb
index 03cf53bb990..28885d0379b 100644
--- a/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb
@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe ::Gitlab::Graphql::Pagination::ArrayConnection do
+ let(:context) { instance_double(GraphQL::Query::Context, schema: GitlabSchema) }
let(:nodes) { (1..10) }
- subject(:connection) { described_class.new(nodes, max_page_size: 100) }
+ subject(:connection) { described_class.new(nodes, context: context, max_page_size: 100) }
it_behaves_like 'a connection with collection methods'
diff --git a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
index d2475d1edb9..e3ae6732ebb 100644
--- a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
+ let(:context) { instance_double(GraphQL::Query::Context, schema: GitlabSchema) }
let(:prev_cursor) { 1 }
let(:next_cursor) { 6 }
let(:values) { [2, 3, 4, 5] }
@@ -10,7 +11,7 @@ RSpec.describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection d
let(:arguments) { {} }
subject(:connection) do
- described_class.new(all_nodes, **{ max_page_size: values.size }.merge(arguments))
+ described_class.new(all_nodes, **{ context: context, max_page_size: values.size }.merge(arguments))
end
it_behaves_like 'a connection with collection methods'
diff --git a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
index 1ca7c1c3c69..a8babaf8d3b 100644
--- a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
@@ -3,18 +3,20 @@
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection do
+ let(:context) { instance_double(GraphQL::Query::Context, schema: GitlabSchema) }
+
it 'subclasses from GraphQL::Relay::RelationConnection' do
expect(described_class.superclass).to eq GraphQL::Pagination::ActiveRecordRelationConnection
end
it_behaves_like 'a connection with collection methods' do
- let(:connection) { described_class.new(Project.all) }
+ let(:connection) { described_class.new(Project.all, context: context) }
end
it_behaves_like 'a redactable connection' do
let_it_be(:users) { create_list(:user, 2) }
- let(:connection) { described_class.new(User.all, max_page_size: 10) }
+ let(:connection) { described_class.new(User.all, context: context, max_page_size: 10) }
let(:unwanted) { users.second }
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 143d0484392..aae6a5cdd89 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -293,17 +293,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
bulk_imports: {
gitlab_v1: 2
},
- project_imports: {
- bitbucket: 2,
- bitbucket_server: 2,
- git: 2,
- gitea: 2,
- github: 2,
- gitlab_migration: 2,
- gitlab_project: 2,
- manifest: 2,
- total: 16
- },
issue_imports: {
jira: 2,
fogbugz: 2,
@@ -320,17 +309,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
bulk_imports: {
gitlab_v1: 1
},
- project_imports: {
- bitbucket: 1,
- bitbucket_server: 1,
- git: 1,
- gitea: 1,
- github: 1,
- gitlab_migration: 1,
- gitlab_project: 1,
- manifest: 1,
- total: 8
- },
issue_imports: {
jira: 1,
fogbugz: 1,
diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb
deleted file mode 100644
index 6c066b3b199..00000000000
--- a/spec/support/helpers/fake_migration_classes.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2]
- include Gitlab::Database::RenameReservedPathsMigration::V1
-
- def version
- '20170316163845'
- end
-
- def name
- "FakeRenameReservedPathMigrationV1"
- end
-end
diff --git a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb
index 4460fd3d185..0e7b909fce9 100644
--- a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb
+++ b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb
@@ -140,7 +140,8 @@ RSpec.shared_examples 'unlicensed cycle analytics request params' do
weight: 1,
epic_id: 2,
iteration_id: 3,
- my_reaction_emoji: 'tumbsup'
+ my_reaction_emoji: 'thumbsup',
+ not: { assignee_username: 'test' }
)
end
@@ -149,6 +150,7 @@ RSpec.shared_examples 'unlicensed cycle analytics request params' do
expect(data_collector_params).to exclude(:epic_id)
expect(data_collector_params).to exclude(:iteration_id)
expect(data_collector_params).to exclude(:my_reaction_emoji)
+ expect(data_collector_params).to exclude(:not)
end
end
end
diff --git a/spec/support/shared_examples/ci/deployable_shared_examples.rb b/spec/support/shared_examples/ci/deployable_shared_examples.rb
index 4f43d38e604..0781eec1b4b 100644
--- a/spec/support/shared_examples/ci/deployable_shared_examples.rb
+++ b/spec/support/shared_examples/ci/deployable_shared_examples.rb
@@ -166,6 +166,28 @@ RSpec.shared_examples 'a deployable job' do
expect(deployment).to be_failed
end
+
+ context 'when the job is a stop job' do
+ before do
+ job.update!(environment: 'review', options: { environment: { action: 'stop' } })
+ end
+
+ it 'enqueues Environments::StopJobFailedWorker' do
+ expect(Environments::StopJobFailedWorker)
+ .to receive(:perform_async)
+
+ subject
+ end
+ end
+
+ context 'when the job is not a stop job' do
+ it 'does not enqueue Environments::StopJobFailedWorker' do
+ expect(Environments::StopJobFailedWorker)
+ .not_to receive(:perform_async)
+
+ subject
+ end
+ end
end
context 'when transits to skipped' do
diff --git a/spec/workers/environments/stop_job_failed_worker_spec.rb b/spec/workers/environments/stop_job_failed_worker_spec.rb
new file mode 100644
index 00000000000..952b96ee3ec
--- /dev/null
+++ b/spec/workers/environments/stop_job_failed_worker_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Environments::StopJobFailedWorker, feature_category: :continuous_delivery do
+ describe '#perform' do
+ let_it_be_with_refind(:environment) { create(:environment, state: :stopping) }
+
+ subject { described_class.new.perform(job.id) }
+
+ shared_examples_for 'recovering a stuck stopping environment' do
+ context 'when the job is not a stop job' do
+ let(:job) { non_stop_job }
+
+ it 'does not recover the environment' do
+ expect { subject }.not_to change { environment.reload.state }
+ end
+ end
+
+ context 'when the stop job is not failed' do
+ let(:job) { stop_job }
+
+ before do
+ job.update!(status: :success)
+ end
+
+ it 'does not recover the environment' do
+ expect { subject }.not_to change { environment.reload.state }
+ end
+ end
+
+ context 'when the stop job is failed' do
+ let(:job) { stop_job }
+
+ it 'recovers the environment' do
+ expect { subject }
+ .to change { environment.reload.state }
+ .from('stopping')
+ .to('available')
+ end
+ end
+ end
+
+ context 'with build job' do
+ let!(:stop_job) do
+ create(
+ :ci_build,
+ :stop_review_app,
+ environment: environment.name,
+ project: environment.project,
+ status: :failed
+ )
+ end
+
+ let!(:non_stop_job) do
+ create(
+ :ci_build,
+ :start_review_app,
+ environment: environment.name,
+ project: environment.project,
+ status: :failed
+ )
+ end
+
+ it_behaves_like 'recovering a stuck stopping environment'
+ end
+
+ context 'with bridge job' do
+ let!(:stop_job) do
+ create(
+ :ci_bridge,
+ :stop_review_app,
+ environment: environment.name,
+ project: environment.project,
+ status: :failed
+ )
+ end
+
+ let!(:non_stop_job) do
+ create(
+ :ci_bridge,
+ :start_review_app,
+ environment: environment.name,
+ project: environment.project,
+ status: :failed
+ )
+ end
+
+ it_behaves_like 'recovering a stuck stopping environment'
+ end
+
+ context 'when job does not exist' do
+ it 'does not raise exception' do
+ expect { described_class.new.perform(non_existing_record_id) }
+ .not_to raise_error
+ end
+ end
+ end
+end