diff options
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 |