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--.rubocop_todo.yml16
-rw-r--r--.rubocop_todo/layout/first_array_element_indentation.yml357
-rw-r--r--.rubocop_todo/layout/hash_alignment.yml732
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js39
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js82
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js55
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue16
-rw-r--r--app/controllers/admin/plan_limits_controller.rb7
-rw-r--r--app/models/concerns/bulk_users_by_email_load.rb24
-rw-r--r--app/models/container_repository.rb39
-rw-r--r--app/models/group.rb1
-rw-r--r--app/models/project.rb1
-rw-r--r--app/services/concerns/members/bulk_create_users.rb10
-rw-r--r--app/services/members/creator_service.rb2
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml185
-rw-r--r--app/views/admin/application_settings/ci_cd.html.haml3
-rw-r--r--app/workers/container_registry/migration/guard_worker.rb52
-rw-r--r--app/workers/database/batched_background_migration/ci_database_worker.rb4
-rw-r--r--app/workers/database/batched_background_migration/single_database_worker.rb6
-rw-r--r--app/workers/database/batched_background_migration_worker.rb4
-rw-r--r--config/feature_flags/development/track_error_tracking_activity.yml8
-rw-r--r--config/feature_flags/ops/execute_batched_migrations_on_schedule_ci_database.yml8
-rw-r--r--db/migrate/20220316202200_add_migration_plan_to_container_repositories.rb10
-rw-r--r--db/migrate/20220316202402_add_text_limit_to_container_repositories_migration_plan.rb13
-rw-r--r--db/post_migrate/20220316202640_populate_container_repositories_migration_plan.rb23
-rw-r--r--db/schema_migrations/202203162022001
-rw-r--r--db/schema_migrations/202203162024021
-rw-r--r--db/schema_migrations/202203162026401
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/clusters/kas.md2
-rw-r--r--doc/administration/index.md2
-rw-r--r--doc/administration/monitoring/gitlab_self_monitoring_project/index.md2
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md1
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/api/runners.md2
-rw-r--r--doc/ci/pipelines/merge_request_pipelines.md2
-rw-r--r--doc/ci/pipelines/multi_project_pipelines.md2
-rw-r--r--doc/development/i18n/proofreader.md6
-rw-r--r--doc/integration/jira/development_panel.md22
-rw-r--r--doc/integration/omniauth.md2
-rw-r--r--doc/public_access/public_access.md101
-rw-r--r--doc/user/admin_area/settings/visibility_and_access_controls.md8
-rw-r--r--doc/user/gitlab_com/index.md2
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/permissions.md4
-rw-r--r--doc/user/project/import/github.md2
-rw-r--r--doc/user/project/index.md2
-rw-r--r--doc/user/project/repository/forking_workflow.md2
-rw-r--r--doc/user/project/settings/import_export.md2
-rw-r--r--doc/user/project/settings/index.md2
-rw-r--r--doc/user/project/working_with_projects.md10
-rw-r--r--doc/user/public_access.md98
-rw-r--r--doc/user/snippets.md2
-rw-r--r--lib/api/invitations.rb2
-rw-r--r--lib/container_registry/gitlab_api_client.rb24
-rw-r--r--lib/gitlab/background_migration/populate_container_repository_migration_plan.rb51
-rw-r--r--lib/gitlab/usage_data_counters/known_events/error_tracking.yml2
-rw-r--r--locale/gitlab.pot42
-rw-r--r--spec/features/admin/admin_settings_spec.rb29
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb3
-rw-r--r--spec/frontend/vue_mr_widget/extensions/test_report/index_spec.js149
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js4
-rw-r--r--spec/lib/container_registry/gitlab_api_client_spec.rb85
-rw-r--r--spec/lib/gitlab/background_migration/populate_container_repository_migration_plan_spec.rb44
-rw-r--r--spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb34
-rw-r--r--spec/models/concerns/approvable_base_spec.rb4
-rw-r--r--spec/models/container_repository_spec.rb141
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/requests/api/internal/container_registry/migration_spec.rb15
-rw-r--r--spec/requests/api/invitations_spec.rb95
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/repositories_spec.rb2
-rw-r--r--spec/requests/api/v3/github_spec.rb2
-rw-r--r--spec/services/ci/retry_build_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/custom_dashboard_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/default_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/dynamic_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/system_dashboard_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/transient_embed_service_spec.rb2
-rw-r--r--spec/services/notification_recipients/builder/default_spec.rb2
-rw-r--r--spec/services/projects/container_repository/cleanup_tags_service_spec.rb2
-rw-r--r--spec/support/shared_examples/models/concerns/bulk_users_by_email_load_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb77
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb16
-rw-r--r--spec/views/admin/application_settings/_ci_cd.html.haml_spec.rb87
-rw-r--r--spec/workers/container_registry/migration/guard_worker_spec.rb72
-rw-r--r--spec/workers/database/batched_background_migration/ci_database_worker_spec.rb2
-rw-r--r--spec/workers/database/batched_background_migration_worker_spec.rb2
94 files changed, 2611 insertions, 446 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 62fc9e6da84..ca0b4356679 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -11,13 +11,6 @@ Gitlab/PolicyRuleBoolean:
Exclude:
- 'ee/app/policies/ee/identity_provider_policy.rb'
-# Offense count: 771
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, IndentationWidth.
-# SupportedStyles: special_inside_parentheses, consistent, align_brackets
-Layout/FirstArrayElementIndentation:
- Enabled: false
-
# Offense count: 1684
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
@@ -25,15 +18,6 @@ Layout/FirstArrayElementIndentation:
Layout/FirstHashElementIndentation:
Enabled: false
-# Offense count: 3233
-# Cop supports --auto-correct.
-# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
-# SupportedHashRocketStyles: key, separator, table
-# SupportedColonStyles: key, separator, table
-# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
-Layout/HashAlignment:
- Enabled: false
-
# Offense count: 14544
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
diff --git a/.rubocop_todo/layout/first_array_element_indentation.yml b/.rubocop_todo/layout/first_array_element_indentation.yml
new file mode 100644
index 00000000000..d613d083f9f
--- /dev/null
+++ b/.rubocop_todo/layout/first_array_element_indentation.yml
@@ -0,0 +1,357 @@
+---
+# Cop supports --auto-correct.
+Layout/FirstArrayElementIndentation:
+ # Offense count: 1133
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'app/controllers/abuse_reports_controller.rb'
+ - 'app/controllers/admin/application_settings_controller.rb'
+ - 'app/controllers/admin/broadcast_messages_controller.rb'
+ - 'app/controllers/admin/plan_limits_controller.rb'
+ - 'app/controllers/boards/issues_controller.rb'
+ - 'app/controllers/groups_controller.rb'
+ - 'app/controllers/projects/issues_controller.rb'
+ - 'app/controllers/projects/merge_requests_controller.rb'
+ - 'app/controllers/projects/pipelines_controller.rb'
+ - 'app/controllers/projects_controller.rb'
+ - 'app/finders/issuable_finder.rb'
+ - 'app/finders/merge_requests/by_approvals_finder.rb'
+ - 'app/finders/user_groups_counter.rb'
+ - 'app/helpers/diff_helper.rb'
+ - 'app/helpers/search_helper.rb'
+ - 'app/models/ci/job_token/scope.rb'
+ - 'app/models/container_repository.rb'
+ - 'app/models/group.rb'
+ - 'app/models/integration.rb'
+ - 'app/models/integrations/bamboo.rb'
+ - 'app/models/internal_id.rb'
+ - 'app/models/member.rb'
+ - 'app/models/merge_request.rb'
+ - 'app/models/namespace.rb'
+ - 'app/models/packages/package.rb'
+ - 'app/models/project.rb'
+ - 'app/models/projects/topic.rb'
+ - 'app/models/todo.rb'
+ - 'app/models/user.rb'
+ - 'app/services/ci/delete_objects_service.rb'
+ - 'app/services/labels/transfer_service.rb'
+ - 'app/services/milestones/transfer_service.rb'
+ - 'app/workers/ssh_keys/expired_notification_worker.rb'
+ - 'config/initializers/postgres_partitioning.rb'
+ - 'db/post_migrate/20210812013042_remove_duplicate_project_authorizations.rb'
+ - 'ee/app/controllers/projects/vulnerability_feedback_controller.rb'
+ - 'ee/app/finders/autocomplete/project_invited_groups_finder.rb'
+ - 'ee/app/finders/geo/project_registry_finder.rb'
+ - 'ee/app/finders/merge_requests/by_approvers_finder.rb'
+ - 'ee/app/graphql/mutations/vulnerabilities/create.rb'
+ - 'ee/app/helpers/ee/application_settings_helper.rb'
+ - 'ee/app/helpers/ee/trial_helper.rb'
+ - 'ee/app/models/ee/epic.rb'
+ - 'ee/app/models/ee/user.rb'
+ - 'ee/app/models/protected_environment.rb'
+ - 'ee/app/serializers/dashboard_environments_serializer.rb'
+ - 'ee/app/serializers/merge_request_compliance_entity.rb'
+ - 'ee/app/services/app_sec/dast/profiles/update_service.rb'
+ - 'ee/app/services/vulnerabilities/create_service_base.rb'
+ - 'ee/lib/ee/api/helpers/award_emoji.rb'
+ - 'ee/lib/ee/gitlab/geo_git_access.rb'
+ - 'ee/lib/gitlab/elastic/helper.rb'
+ - 'ee/lib/gitlab/sitemaps/url_extractor.rb'
+ - 'ee/lib/tasks/gitlab/seed/metrics.rake'
+ - 'ee/spec/controllers/admin/audit_log_reports_controller_spec.rb'
+ - 'ee/spec/controllers/admin/licenses/usage_exports_controller_spec.rb'
+ - 'ee/spec/controllers/groups/analytics/coverage_reports_controller_spec.rb'
+ - 'ee/spec/controllers/groups/security/merge_commit_reports_controller_spec.rb'
+ - 'ee/spec/controllers/projects/merge_requests_controller_spec.rb'
+ - 'ee/spec/features/admin/admin_dev_ops_reports_spec.rb'
+ - 'ee/spec/features/boards/boards_licensed_features_spec.rb'
+ - 'ee/spec/features/groups/group_roadmap_spec.rb'
+ - 'ee/spec/finders/billed_users_finder_spec.rb'
+ - 'ee/spec/finders/merge_requests/by_approvers_finder_spec.rb'
+ - 'ee/spec/finders/security/pipeline_vulnerabilities_finder_spec.rb'
+ - 'ee/spec/frontend/fixtures/dast_profiles.rb'
+ - 'ee/spec/frontend/fixtures/search.rb'
+ - 'ee/spec/graphql/mutations/incident_management/escalation_policy/create_spec.rb'
+ - 'ee/spec/graphql/resolvers/dora_metrics_resolver_spec.rb'
+ - 'ee/spec/graphql/resolvers/timebox_report_resolver_spec.rb'
+ - 'ee/spec/graphql/types/ci/pipeline_type_spec.rb'
+ - 'ee/spec/graphql/types/dast_scanner_profile_type_spec.rb'
+ - 'ee/spec/graphql/types/dast_site_profile_type_spec.rb'
+ - 'ee/spec/helpers/paid_feature_callout_helper_spec.rb'
+ - 'ee/spec/helpers/trial_status_widget_helper_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/auth/ldap/access_levels_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/auth/ldap/sync/group_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/usage_data_spec.rb'
+ - 'ee/spec/lib/gitlab/checks/diff_check_spec.rb'
+ - 'ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb'
+ - 'ee/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb'
+ - 'ee/spec/lib/gitlab/ci/templates/Jobs/dast_default_branch_gitlab_ci_yaml_spec.rb'
+ - 'ee/spec/lib/gitlab/ci/templates/Jobs/load_performance_testing_gitlab_ci_yaml_spec.rb'
+ - 'ee/spec/lib/gitlab/ci/yaml_processor_spec.rb'
+ - 'ee/spec/lib/gitlab/graphql/aggregations/epics/epic_node_spec.rb'
+ - 'ee/spec/lib/gitlab/graphql/aggregations/issues/lazy_block_aggregate_spec.rb'
+ - 'ee/spec/lib/gitlab/graphql/loaders/bulk_epic_aggregate_loader_spec.rb'
+ - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_builds_metric_spec.rb'
+ - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_ci_builds_metric_spec.rb'
+ - 'ee/spec/lib/gitlab/vulnerabilities/parser_spec.rb'
+ - 'ee/spec/models/analytics/cycle_analytics/group_value_stream_spec.rb'
+ - 'ee/spec/models/application_setting_spec.rb'
+ - 'ee/spec/models/approval_state_spec.rb'
+ - 'ee/spec/models/burndown_spec.rb'
+ - 'ee/spec/models/concerns/ee/noteable_spec.rb'
+ - 'ee/spec/models/concerns/geo/verification_state_spec.rb'
+ - 'ee/spec/models/ee/iterations/cadence_spec.rb'
+ - 'ee/spec/models/ee/namespace_spec.rb'
+ - 'ee/spec/models/ee/release_spec.rb'
+ - 'ee/spec/models/group_wiki_repository_spec.rb'
+ - 'ee/spec/models/project_spec.rb'
+ - 'ee/spec/models/requirements_management/test_report_spec.rb'
+ - 'ee/spec/models/security/orchestration_policy_configuration_spec.rb'
+ - 'ee/spec/models/security/orchestration_policy_rule_schedule_spec.rb'
+ - 'ee/spec/models/security/scan_spec.rb'
+ - 'ee/spec/models/security/training_provider_spec.rb'
+ - 'ee/spec/models/snippet_repository_spec.rb'
+ - 'ee/spec/policies/project_policy_spec.rb'
+ - 'ee/spec/requests/admin/user_permission_exports_controller_spec.rb'
+ - 'ee/spec/requests/api/analytics/project_deployment_frequency_spec.rb'
+ - 'ee/spec/requests/api/experiments_spec.rb'
+ - 'ee/spec/requests/api/graphql/analytics/devops_adoption/enabled_namespaces_spec.rb'
+ - 'ee/spec/requests/api/graphql/group/epics_spec.rb'
+ - 'ee/spec/requests/api/graphql/mutations/quality_management/test_cases/create_spec.rb'
+ - 'ee/spec/requests/api/graphql/mutations/releases/create_spec.rb'
+ - 'ee/spec/requests/api/graphql/mutations/releases/update_spec.rb'
+ - 'ee/spec/requests/api/graphql/project/alert_management/payload_fields_spec.rb'
+ - 'ee/spec/requests/api/graphql/project/incident_management/escalation_policy/rules_spec.rb'
+ - 'ee/spec/requests/api/graphql/project/merge_requests_spec.rb'
+ - 'ee/spec/requests/api/ldap_group_links_spec.rb'
+ - 'ee/spec/requests/api/members_spec.rb'
+ - 'ee/spec/services/analytics/cycle_analytics/value_streams/update_service_spec.rb'
+ - 'ee/spec/services/audit_events/export_csv_service_spec.rb'
+ - 'ee/spec/services/gitlab_subscriptions/fetch_purchase_eligible_namespaces_service_spec.rb'
+ - 'ee/spec/services/groups/seat_usage_export_service_spec.rb'
+ - 'ee/spec/services/iterations/cadences/create_iterations_in_advance_service_spec.rb'
+ - 'ee/spec/services/iterations/cadences/create_service_spec.rb'
+ - 'ee/spec/services/protected_environments/base_service_spec.rb'
+ - 'ee/spec/services/search_service_spec.rb'
+ - 'ee/spec/services/security/security_orchestration_policies/process_scan_result_policy_service_spec.rb'
+ - 'ee/spec/services/security/store_findings_metadata_service_spec.rb'
+ - 'ee/spec/services/timebox_report_service_spec.rb'
+ - 'ee/spec/services/user_permissions/export_service_spec.rb'
+ - 'ee/spec/support/shared_examples/services/search_notes_shared_examples.rb'
+ - 'ee/spec/workers/geo/scheduler/scheduler_worker_spec.rb'
+ - 'lib/gitlab/background_migration/fix_merge_request_diff_commit_users.rb'
+ - 'lib/gitlab/database/migration_helpers.rb'
+ - 'lib/gitlab/email/message/in_product_marketing/team.rb'
+ - 'lib/gitlab/email/message/in_product_marketing/trial.rb'
+ - 'lib/gitlab/email/message/in_product_marketing/verify.rb'
+ - 'lib/gitlab/import_export/base/relation_factory.rb'
+ - 'lib/gitlab/import_export/json/streaming_serializer.rb'
+ - 'lib/gitlab/integrations/sti_type.rb'
+ - 'lib/gitlab/kroki.rb'
+ - 'lib/gitlab/object_hierarchy.rb'
+ - 'lib/gitlab/pagination/keyset/simple_order_builder.rb'
+ - 'lib/gitlab/project_authorizations.rb'
+ - 'lib/gitlab/usage_data.rb'
+ - 'lib/system_check/app/authorized_keys_permission_check.rb'
+ - 'qa/qa/resource/protected_branch.rb'
+ - 'qa/qa/specs/features/api/1_manage/project_access_token_spec.rb'
+ - 'qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb'
+ - 'qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb'
+ - 'qa/qa/specs/features/api/3_create/gitaly/gitaly_mtls_spec.rb'
+ - 'qa/qa/specs/features/api/3_create/gitaly/praefect_dataloss_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/3_create/web_ide/open_web_ide_from_diff_tab_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb'
+ - 'qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb'
+ - 'qa/qa/specs/features/ee/api/1_manage/user/minimal_access_user_spec.rb'
+ - 'qa/qa/specs/features/ee/api/9_enablement/elasticsearch/advanced_global_advanced_syntax_search_spec.rb'
+ - 'qa/qa/specs/features/ee/api/9_enablement/elasticsearch/elasticsearch_api_spec.rb'
+ - 'qa/qa/specs/features/ee/api/9_enablement/elasticsearch/index_tests/main_index/blob_index_spec.rb'
+ - 'qa/qa/specs/features/ee/api/9_enablement/elasticsearch/nightly_elasticsearch_test_spec.rb'
+ - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb'
+ - 'qa/qa/specs/features/ee/browser_ui/4_verify/new_discussion_not_dropping_merge_trains_mr_spec.rb'
+ - 'spec/controllers/concerns/send_file_upload_spec.rb'
+ - 'spec/controllers/graphql_controller_spec.rb'
+ - 'spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb'
+ - 'spec/controllers/projects/ci/lints_controller_spec.rb'
+ - 'spec/controllers/projects/pipelines/tests_controller_spec.rb'
+ - 'spec/controllers/projects/pipelines_controller_spec.rb'
+ - 'spec/deprecation_toolkit_env.rb'
+ - 'spec/experiments/new_project_sast_enabled_experiment_spec.rb'
+ - 'spec/features/clusters/create_agent_spec.rb'
+ - 'spec/features/issues/filtered_search/filter_issues_spec.rb'
+ - 'spec/features/issues/filtered_search/visual_tokens_spec.rb'
+ - 'spec/finders/ci/daily_build_group_report_results_finder_spec.rb'
+ - 'spec/finders/deploy_tokens/tokens_finder_spec.rb'
+ - 'spec/frontend/fixtures/search.rb'
+ - 'spec/graphql/mutations/commits/create_spec.rb'
+ - 'spec/graphql/resolvers/ci/test_suite_resolver_spec.rb'
+ - 'spec/graphql/types/ci/runner_architecture_type_spec.rb'
+ - 'spec/graphql/types/ci/runner_platform_type_spec.rb'
+ - 'spec/graphql/types/metrics/dashboard_type_spec.rb'
+ - 'spec/graphql/types/packages/composer/metadatum_type_spec.rb'
+ - 'spec/graphql/types/packages/tag_type_spec.rb'
+ - 'spec/helpers/application_settings_helper_spec.rb'
+ - 'spec/helpers/commits_helper_spec.rb'
+ - 'spec/helpers/issuables_description_templates_helper_spec.rb'
+ - 'spec/helpers/listbox_helper_spec.rb'
+ - 'spec/helpers/users_helper_spec.rb'
+ - 'spec/lib/api/helpers/merge_requests_helpers_spec.rb'
+ - 'spec/lib/banzai/filter/references/label_reference_filter_spec.rb'
+ - 'spec/lib/csv_builders/stream_spec.rb'
+ - 'spec/lib/expand_variables_spec.rb'
+ - 'spec/lib/gitlab/analytics/usage_trends/workers_argument_builder_spec.rb'
+ - 'spec/lib/gitlab/background_migration/backfill_integrations_type_new_spec.rb'
+ - 'spec/lib/gitlab/background_migration/cleanup_orphaned_lfs_objects_projects_spec.rb'
+ - 'spec/lib/gitlab/background_migration/migrate_merge_request_diff_commit_users_spec.rb'
+ - 'spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb'
+ - 'spec/lib/gitlab/ci/ansi2json_spec.rb'
+ - 'spec/lib/gitlab/ci/config/entry/product/parallel_spec.rb'
+ - 'spec/lib/gitlab/ci/config/entry/root_spec.rb'
+ - 'spec/lib/gitlab/ci/config/external/mapper_spec.rb'
+ - 'spec/lib/gitlab/ci/parsers/security/common_spec.rb'
+ - 'spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb'
+ - 'spec/lib/gitlab/ci/reports/codequality_reports_spec.rb'
+ - 'spec/lib/gitlab/ci/variables/builder/group_spec.rb'
+ - 'spec/lib/gitlab/ci/variables/builder/project_spec.rb'
+ - 'spec/lib/gitlab/ci/variables/builder_spec.rb'
+ - 'spec/lib/gitlab/ci/variables/collection/sort_spec.rb'
+ - 'spec/lib/gitlab/ci/yaml_processor/result_spec.rb'
+ - 'spec/lib/gitlab/ci/yaml_processor_spec.rb'
+ - 'spec/lib/gitlab/conflict/file_spec.rb'
+ - 'spec/lib/gitlab/database/each_database_spec.rb'
+ - 'spec/lib/gitlab/database/load_balancing/rack_middleware_spec.rb'
+ - 'spec/lib/gitlab/database/load_balancing/sticking_spec.rb'
+ - 'spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb'
+ - 'spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb'
+ - 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb'
+ - 'spec/lib/gitlab/database/partitioning/time_partition_spec.rb'
+ - 'spec/lib/gitlab/database/partitioning_spec.rb'
+ - 'spec/lib/gitlab/database/similarity_score_spec.rb'
+ - 'spec/lib/gitlab/diff/char_diff_spec.rb'
+ - 'spec/lib/gitlab/diff/file_collection_sorter_spec.rb'
+ - 'spec/lib/gitlab/elasticsearch/logs/pods_spec.rb'
+ - 'spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb'
+ - 'spec/lib/gitlab/gitaly_client/blob_service_spec.rb'
+ - 'spec/lib/gitlab/github_import/parallel_scheduling_spec.rb'
+ - 'spec/lib/gitlab/gitlab_import/importer_spec.rb'
+ - 'spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb'
+ - 'spec/lib/gitlab/hook_data/release_builder_spec.rb'
+ - 'spec/lib/gitlab/import_export/group/tree_restorer_spec.rb'
+ - 'spec/lib/gitlab/import_export/group/tree_saver_spec.rb'
+ - 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
+ - 'spec/lib/gitlab/kubernetes/rollout_instances_spec.rb'
+ - 'spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb'
+ - 'spec/lib/gitlab/pagination/keyset/column_order_definition_spec.rb'
+ - 'spec/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder_spec.rb'
+ - 'spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/order_values_loader_strategy_spec.rb'
+ - 'spec/lib/gitlab/pagination/keyset/iterator_spec.rb'
+ - 'spec/lib/gitlab/pagination/keyset/order_spec.rb'
+ - 'spec/lib/gitlab/project_transfer_spec.rb'
+ - 'spec/lib/gitlab/prometheus_client_spec.rb'
+ - 'spec/lib/gitlab/push_options_spec.rb'
+ - 'spec/lib/gitlab/rack_attack/request_spec.rb'
+ - 'spec/lib/gitlab/search/abuse_detection_spec.rb'
+ - 'spec/lib/gitlab/search/found_blob_spec.rb'
+ - 'spec/lib/gitlab/serializer/ci/variables_spec.rb'
+ - 'spec/lib/gitlab/ssh_public_key_spec.rb'
+ - 'spec/lib/gitlab/usage_data/topology_spec.rb'
+ - 'spec/lib/gitlab/usage_data_spec.rb'
+ - 'spec/lib/gitlab/utils_spec.rb'
+ - 'spec/lib/gitlab/webpack/manifest_spec.rb'
+ - 'spec/lib/peek/views/bullet_detailed_spec.rb'
+ - 'spec/lib/system_check/incoming_email_check_spec.rb'
+ - 'spec/migrations/add_premium_and_ultimate_plan_limits_spec.rb'
+ - 'spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb'
+ - 'spec/migrations/slice_merge_request_diff_commit_migrations_spec.rb'
+ - 'spec/models/analytics/cycle_analytics/project_stage_spec.rb'
+ - 'spec/models/application_setting_spec.rb'
+ - 'spec/models/ci/build_spec.rb'
+ - 'spec/models/ci/build_trace_spec.rb'
+ - 'spec/models/ci/daily_build_group_report_result_spec.rb'
+ - 'spec/models/ci/pipeline_spec.rb'
+ - 'spec/models/ci/unit_test_spec.rb'
+ - 'spec/models/clusters/applications/cert_manager_spec.rb'
+ - 'spec/models/clusters/applications/elastic_stack_spec.rb'
+ - 'spec/models/clusters/platforms/kubernetes_spec.rb'
+ - 'spec/models/commit_collection_spec.rb'
+ - 'spec/models/compare_spec.rb'
+ - 'spec/models/concerns/id_in_ordered_spec.rb'
+ - 'spec/models/concerns/noteable_spec.rb'
+ - 'spec/models/diff_note_spec.rb'
+ - 'spec/models/discussion_spec.rb'
+ - 'spec/models/group_group_link_spec.rb'
+ - 'spec/models/group_spec.rb'
+ - 'spec/models/integration_spec.rb'
+ - 'spec/models/integrations/chat_message/issue_message_spec.rb'
+ - 'spec/models/integrations/chat_message/wiki_page_message_spec.rb'
+ - 'spec/models/label_note_spec.rb'
+ - 'spec/models/merge_request/cleanup_schedule_spec.rb'
+ - 'spec/models/merge_request_diff_spec.rb'
+ - 'spec/models/merge_request_spec.rb'
+ - 'spec/models/operations/feature_flags/strategy_spec.rb'
+ - 'spec/models/project_group_link_spec.rb'
+ - 'spec/models/repository_spec.rb'
+ - 'spec/models/wiki_directory_spec.rb'
+ - 'spec/policies/concerns/crud_policy_helpers_spec.rb'
+ - 'spec/presenters/ci/build_runner_presenter_spec.rb'
+ - 'spec/requests/api/ci/jobs_spec.rb'
+ - 'spec/requests/api/ci/runner/jobs_request_post_spec.rb'
+ - 'spec/requests/api/ci/runners_spec.rb'
+ - 'spec/requests/api/deploy_tokens_spec.rb'
+ - 'spec/requests/api/graphql/ci/config_spec.rb'
+ - 'spec/requests/api/graphql/ci/jobs_spec.rb'
+ - 'spec/requests/api/graphql/project/cluster_agents_spec.rb'
+ - 'spec/requests/api/graphql/project/issue/designs/designs_spec.rb'
+ - 'spec/requests/api/graphql/project/milestones_spec.rb'
+ - 'spec/requests/api/graphql/usage_trends_measurements_spec.rb'
+ - 'spec/requests/api/issues/post_projects_issues_spec.rb'
+ - 'spec/requests/api/issues/put_projects_issues_spec.rb'
+ - 'spec/requests/api/merge_requests_spec.rb'
+ - 'spec/requests/api/task_completion_status_spec.rb'
+ - 'spec/requests/projects/ci/promeheus_metrics/histograms_controller_spec.rb'
+ - 'spec/requests/projects/issues_controller_spec.rb'
+ - 'spec/serializers/build_trace_entity_spec.rb'
+ - 'spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb'
+ - 'spec/serializers/merge_request_poll_widget_entity_spec.rb'
+ - 'spec/services/award_emojis/copy_service_spec.rb'
+ - 'spec/services/ci/compare_test_reports_service_spec.rb'
+ - 'spec/services/ci/find_exposed_artifacts_service_spec.rb'
+ - 'spec/services/design_management/move_designs_service_spec.rb'
+ - 'spec/services/git/tag_hooks_service_spec.rb'
+ - 'spec/services/import/gitlab_projects/create_project_service_spec.rb'
+ - 'spec/services/jira_connect/sync_service_spec.rb'
+ - 'spec/services/merge_requests/link_lfs_objects_service_spec.rb'
+ - 'spec/services/merge_requests/refresh_service_spec.rb'
+ - 'spec/services/repositories/changelog_service_spec.rb'
+ - 'spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb'
+ - 'spec/services/security/merge_reports_service_spec.rb'
+ - 'spec/services/users/destroy_service_spec.rb'
+ - 'spec/simplecov_env.rb'
+ - 'spec/support/atlassian/jira_connect/schemata.rb'
+ - 'spec/support/capybara.rb'
+ - 'spec/support/helpers/test_env.rb'
+ - 'spec/support/helpers/usage_data_helpers.rb'
+ - 'spec/support/matchers/background_migrations_matchers.rb'
+ - 'spec/support/matchers/exceed_query_limit.rb'
+ - 'spec/support/migrations_helpers/vulnerabilities_findings_helper.rb'
+ - 'spec/support/prometheus/additional_metrics_shared_examples.rb'
+ - 'spec/support/shared_contexts/policies/group_policy_shared_context.rb'
+ - 'spec/support/shared_examples/graphql/label_fields.rb'
+ - 'spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb'
+ - 'spec/views/projects/issues/_related_branches.html.haml_spec.rb'
+ - 'tooling/lib/tooling/helm3_client.rb'
diff --git a/.rubocop_todo/layout/hash_alignment.yml b/.rubocop_todo/layout/hash_alignment.yml
new file mode 100644
index 00000000000..afcaab17ae1
--- /dev/null
+++ b/.rubocop_todo/layout/hash_alignment.yml
@@ -0,0 +1,732 @@
+---
+# Cop supports --auto-correct.
+Layout/HashAlignment:
+ # Offense count: 3804
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'app/controllers/admin/ci/variables_controller.rb'
+ - 'app/controllers/admin/system_info_controller.rb'
+ - 'app/controllers/oauth/token_info_controller.rb'
+ - 'app/controllers/projects/feature_flags_controller.rb'
+ - 'app/controllers/repositories/git_http_client_controller.rb'
+ - 'app/controllers/repositories/lfs_api_controller.rb'
+ - 'app/controllers/repositories/lfs_locks_api_controller.rb'
+ - 'app/controllers/uploads_controller.rb'
+ - 'app/graphql/mutations/award_emojis/toggle.rb'
+ - 'app/graphql/mutations/ci/runner/update.rb'
+ - 'app/graphql/mutations/design_management/move.rb'
+ - 'app/graphql/mutations/issues/set_severity.rb'
+ - 'app/graphql/mutations/security/ci_configuration/base_security_analyzer.rb'
+ - 'app/graphql/resolvers/ci/template_resolver.rb'
+ - 'app/graphql/resolvers/projects_resolver.rb'
+ - 'app/graphql/resolvers/users_resolver.rb'
+ - 'app/graphql/types/access_level_type.rb'
+ - 'app/graphql/types/admin/analytics/usage_trends/measurement_type.rb'
+ - 'app/graphql/types/board_list_type.rb'
+ - 'app/graphql/types/board_type.rb'
+ - 'app/graphql/types/ci/analytics_type.rb'
+ - 'app/graphql/types/ci/application_setting_type.rb'
+ - 'app/graphql/types/ci/build_need_type.rb'
+ - 'app/graphql/types/ci/ci_cd_setting_type.rb'
+ - 'app/graphql/types/ci/config/config_type.rb'
+ - 'app/graphql/types/ci/config/group_type.rb'
+ - 'app/graphql/types/ci/config/job_restriction_type.rb'
+ - 'app/graphql/types/ci/config/job_type.rb'
+ - 'app/graphql/types/ci/config/need_type.rb'
+ - 'app/graphql/types/ci/config/stage_type.rb'
+ - 'app/graphql/types/ci/detailed_status_type.rb'
+ - 'app/graphql/types/ci/group_type.rb'
+ - 'app/graphql/types/ci/job_artifact_type.rb'
+ - 'app/graphql/types/ci/job_token_scope_type.rb'
+ - 'app/graphql/types/ci/job_type.rb'
+ - 'app/graphql/types/ci/pipeline_message_type.rb'
+ - 'app/graphql/types/ci/pipeline_type.rb'
+ - 'app/graphql/types/ci/recent_failures_type.rb'
+ - 'app/graphql/types/ci/runner_architecture_type.rb'
+ - 'app/graphql/types/ci/runner_platform_type.rb'
+ - 'app/graphql/types/ci/runner_setup_type.rb'
+ - 'app/graphql/types/ci/runner_type.rb'
+ - 'app/graphql/types/ci/runner_web_url_edge.rb'
+ - 'app/graphql/types/ci/stage_type.rb'
+ - 'app/graphql/types/ci/status_action_type.rb'
+ - 'app/graphql/types/ci/template_type.rb'
+ - 'app/graphql/types/ci/test_case_type.rb'
+ - 'app/graphql/types/ci/test_report_summary_type.rb'
+ - 'app/graphql/types/ci/test_report_total_type.rb'
+ - 'app/graphql/types/ci/test_suite_summary_type.rb'
+ - 'app/graphql/types/ci/test_suite_type.rb'
+ - 'app/graphql/types/ci_configuration/sast/analyzers_entity_input_type.rb'
+ - 'app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb'
+ - 'app/graphql/types/ci_configuration/sast/entity_input_type.rb'
+ - 'app/graphql/types/ci_configuration/sast/entity_type.rb'
+ - 'app/graphql/types/ci_configuration/sast/options_entity_type.rb'
+ - 'app/graphql/types/ci_configuration/sast/type.rb'
+ - 'app/graphql/types/commit_action_type.rb'
+ - 'app/graphql/types/commit_type.rb'
+ - 'app/graphql/types/countable_connection_type.rb'
+ - 'app/graphql/types/design_management/design_collection_type.rb'
+ - 'app/graphql/types/design_management/design_fields.rb'
+ - 'app/graphql/types/design_management/version_type.rb'
+ - 'app/graphql/types/diff_paths_input_type.rb'
+ - 'app/graphql/types/diff_refs_type.rb'
+ - 'app/graphql/types/diff_stats_summary_type.rb'
+ - 'app/graphql/types/diff_stats_type.rb'
+ - 'app/graphql/types/environment_type.rb'
+ - 'app/graphql/types/evidence_type.rb'
+ - 'app/graphql/types/grafana_integration_type.rb'
+ - 'app/graphql/types/group_invitation_type.rb'
+ - 'app/graphql/types/group_member_type.rb'
+ - 'app/graphql/types/group_type.rb'
+ - 'app/graphql/types/invitation_interface.rb'
+ - 'app/graphql/types/issue_type.rb'
+ - 'app/graphql/types/jira_import_type.rb'
+ - 'app/graphql/types/jira_user_type.rb'
+ - 'app/graphql/types/label_type.rb'
+ - 'app/graphql/types/member_interface.rb'
+ - 'app/graphql/types/merge_request_connection_type.rb'
+ - 'app/graphql/types/merge_request_type.rb'
+ - 'app/graphql/types/metadata/kas_type.rb'
+ - 'app/graphql/types/metadata_type.rb'
+ - 'app/graphql/types/metrics/dashboard_type.rb'
+ - 'app/graphql/types/metrics/dashboards/annotation_type.rb'
+ - 'app/graphql/types/milestone_stats_type.rb'
+ - 'app/graphql/types/milestone_type.rb'
+ - 'app/graphql/types/namespace_type.rb'
+ - 'app/graphql/types/notes/diff_image_position_input_type.rb'
+ - 'app/graphql/types/notes/diff_position_base_input_type.rb'
+ - 'app/graphql/types/notes/diff_position_input_type.rb'
+ - 'app/graphql/types/notes/diff_position_type.rb'
+ - 'app/graphql/types/notes/discussion_type.rb'
+ - 'app/graphql/types/notes/note_type.rb'
+ - 'app/graphql/types/packages/package_details_type.rb'
+ - 'app/graphql/types/packages/package_file_type.rb'
+ - 'app/graphql/types/packages/package_type.rb'
+ - 'app/graphql/types/project_invitation_type.rb'
+ - 'app/graphql/types/project_member_type.rb'
+ - 'app/graphql/types/project_statistics_type.rb'
+ - 'app/graphql/types/project_type.rb'
+ - 'app/graphql/types/projects/service_type.rb'
+ - 'app/graphql/types/projects/services/jira_project_type.rb'
+ - 'app/graphql/types/projects/topic_type.rb'
+ - 'app/graphql/types/prometheus_alert_type.rb'
+ - 'app/graphql/types/release_asset_link_type.rb'
+ - 'app/graphql/types/release_assets_type.rb'
+ - 'app/graphql/types/release_links_type.rb'
+ - 'app/graphql/types/release_source_type.rb'
+ - 'app/graphql/types/release_type.rb'
+ - 'app/graphql/types/repository/blob_type.rb'
+ - 'app/graphql/types/repository_type.rb'
+ - 'app/graphql/types/resolvable_interface.rb'
+ - 'app/graphql/types/snippet_type.rb'
+ - 'app/graphql/types/snippets/blob_connection_type.rb'
+ - 'app/graphql/types/snippets/blob_type.rb'
+ - 'app/graphql/types/subscription_type.rb'
+ - 'app/graphql/types/task_completion_status.rb'
+ - 'app/graphql/types/tree/blob_type.rb'
+ - 'app/graphql/types/tree/entry_type.rb'
+ - 'app/graphql/types/tree/submodule_type.rb'
+ - 'app/graphql/types/tree/tree_entry_type.rb'
+ - 'app/graphql/types/tree/tree_type.rb'
+ - 'app/graphql/types/user_callout_type.rb'
+ - 'app/graphql/types/user_interface.rb'
+ - 'app/graphql/types/user_status_type.rb'
+ - 'app/graphql/types/work_item_type.rb'
+ - 'app/graphql/types/work_items/type_type.rb'
+ - 'app/helpers/avatars_helper.rb'
+ - 'app/helpers/blob_helper.rb'
+ - 'app/helpers/commits_helper.rb'
+ - 'app/helpers/environments_helper.rb'
+ - 'app/helpers/events_helper.rb'
+ - 'app/helpers/markup_helper.rb'
+ - 'app/helpers/preferences_helper.rb'
+ - 'app/helpers/projects_helper.rb'
+ - 'app/helpers/sorting_helper.rb'
+ - 'app/helpers/todos_helper.rb'
+ - 'app/helpers/wiki_helper.rb'
+ - 'app/mailers/abuse_report_mailer.rb'
+ - 'app/mailers/emails/projects.rb'
+ - 'app/models/bulk_imports/configuration.rb'
+ - 'app/models/ci/bridge.rb'
+ - 'app/models/ci/build_trace_metadata.rb'
+ - 'app/models/ci/pipeline.rb'
+ - 'app/models/compare.rb'
+ - 'app/models/concerns/ci/has_status.rb'
+ - 'app/models/concerns/enums/data_visualization_palette.rb'
+ - 'app/models/concerns/featurable.rb'
+ - 'app/models/concerns/issuable.rb'
+ - 'app/models/concerns/triggerable_hooks.rb'
+ - 'app/models/container_repository.rb'
+ - 'app/models/design_management/design.rb'
+ - 'app/models/design_management/design_action.rb'
+ - 'app/models/event.rb'
+ - 'app/models/grafana_integration.rb'
+ - 'app/models/group.rb'
+ - 'app/models/hooks/web_hook.rb'
+ - 'app/models/integrations/emails_on_push.rb'
+ - 'app/models/integrations/jira.rb'
+ - 'app/models/issuable_severity.rb'
+ - 'app/models/jira_connect_installation.rb'
+ - 'app/models/loose_foreign_keys/deleted_record.rb'
+ - 'app/models/merge_request.rb'
+ - 'app/models/merge_request_diff.rb'
+ - 'app/models/pages_domain.rb'
+ - 'app/models/project.rb'
+ - 'app/models/prometheus_alert.rb'
+ - 'app/models/serverless/domain_cluster.rb'
+ - 'app/models/snippet.rb'
+ - 'app/models/terraform/state.rb'
+ - 'app/models/user.rb'
+ - 'app/models/user_status.rb'
+ - 'app/models/wiki.rb'
+ - 'app/models/work_items/type.rb'
+ - 'app/presenters/analytics/cycle_analytics/stage_presenter.rb'
+ - 'app/presenters/project_presenter.rb'
+ - 'app/serializers/rollout_status_entity.rb'
+ - 'app/services/chat_names/authorize_user_service.rb'
+ - 'app/services/ci/archive_trace_service.rb'
+ - 'app/services/ci/job_artifacts/destroy_batch_service.rb'
+ - 'app/services/ci/list_config_variables_service.rb'
+ - 'app/services/ci/parse_dotenv_artifact_service.rb'
+ - 'app/services/ci/stuck_builds/drop_helpers.rb'
+ - 'app/services/groups/import_export/import_service.rb'
+ - 'app/services/issuable/import_csv/base_service.rb'
+ - 'app/services/issues/export_csv_service.rb'
+ - 'app/services/jira/requests/base.rb'
+ - 'app/services/merge_requests/mergeability_check_service.rb'
+ - 'app/services/merge_requests/push_options_handler_service.rb'
+ - 'app/services/merge_requests/toggle_attention_requested_service.rb'
+ - 'app/services/packages/conan/create_package_file_service.rb'
+ - 'app/services/packages/create_package_file_service.rb'
+ - 'app/services/packages/debian/create_package_file_service.rb'
+ - 'app/services/packages/npm/create_package_service.rb'
+ - 'app/services/projects/fork_service.rb'
+ - 'app/services/projects/lfs_pointers/lfs_download_service.rb'
+ - 'app/services/projects/update_remote_mirror_service.rb'
+ - 'app/uploaders/file_uploader.rb'
+ - 'app/workers/emails_on_push_worker.rb'
+ - 'app/workers/x509_issuer_crl_check_worker.rb'
+ - 'config/initializers/1_settings.rb'
+ - 'config/initializers/default_url_options.rb'
+ - 'config/initializers/rest-client-hostname_override.rb'
+ - 'config/routes/profile.rb'
+ - 'config/routes/project.rb'
+ - 'config/routes/unmatched_project.rb'
+ - 'config/routes/uploads.rb'
+ - 'db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb'
+ - 'db/migrate/20210804150320_create_base_work_item_types.rb'
+ - 'db/migrate/20210831203408_upsert_base_work_item_types.rb'
+ - 'db/migrate/20210901065504_add_index_on_name_and_id_to_public_groups.rb'
+ - 'db/post_migrate/20210311120156_backfill_push_event_payload_event_id_for_bigint_conversion.rb'
+ - 'db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb'
+ - 'db/post_migrate/20210701141346_finalize_ci_builds_stage_id_bigint_conversion.rb'
+ - 'db/post_migrate/20210707210916_finalize_ci_stages_bigint_conversion.rb'
+ - 'db/post_migrate/20210708011426_finalize_ci_builds_metadata_bigint_conversion.rb'
+ - 'db/post_migrate/20210802043253_finalize_push_event_payloads_bigint_conversion_3.rb'
+ - 'db/post_migrate/20210804151444_prepare_indexes_for_ci_job_artifact_bigint_conversion.rb'
+ - 'db/post_migrate/20210804153307_prepare_indexes_for_tagging_bigint_conversion.rb'
+ - 'db/post_migrate/20210804154407_prepare_indexes_for_ci_stage_bigint_conversion.rb'
+ - 'db/post_migrate/20210817024335_prepare_indexes_for_events_bigint_conversion.rb'
+ - 'db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb'
+ - 'ee/app/controllers/ee/search_controller.rb'
+ - 'ee/app/controllers/projects/integrations/zentao/issues_controller.rb'
+ - 'ee/app/graphql/ee/types/board_list_type.rb'
+ - 'ee/app/graphql/ee/types/board_type.rb'
+ - 'ee/app/graphql/ee/types/ci/runner_type.rb'
+ - 'ee/app/graphql/ee/types/group_type.rb'
+ - 'ee/app/graphql/ee/types/issue_type.rb'
+ - 'ee/app/graphql/ee/types/merge_request_type.rb'
+ - 'ee/app/graphql/ee/types/project_type.rb'
+ - 'ee/app/graphql/ee/types/repository/blob_type.rb'
+ - 'ee/app/graphql/mutations/iterations/cadences/create.rb'
+ - 'ee/app/graphql/mutations/iterations/cadences/destroy.rb'
+ - 'ee/app/graphql/mutations/iterations/cadences/update.rb'
+ - 'ee/app/graphql/mutations/iterations/delete.rb'
+ - 'ee/app/graphql/mutations/projects/set_locked.rb'
+ - 'ee/app/graphql/resolvers/iterations/cadences_resolver.rb'
+ - 'ee/app/graphql/resolvers/vulnerabilities_count_per_day_resolver.rb'
+ - 'ee/app/graphql/types/admin/cloud_licenses/current_license_type.rb'
+ - 'ee/app/graphql/types/admin/cloud_licenses/license_type.rb'
+ - 'ee/app/graphql/types/admin/cloud_licenses/subscription_future_entry_type.rb'
+ - 'ee/app/graphql/types/analytics/devops_adoption/enabled_namespace_type.rb'
+ - 'ee/app/graphql/types/analytics/devops_adoption/snapshot_type.rb'
+ - 'ee/app/graphql/types/app_sec/fuzzing/api/ci_configuration_type.rb'
+ - 'ee/app/graphql/types/app_sec/fuzzing/api/scan_profile_type.rb'
+ - 'ee/app/graphql/types/app_sec/fuzzing/coverage/corpus_type.rb'
+ - 'ee/app/graphql/types/boards/board_epic_type.rb'
+ - 'ee/app/graphql/types/boards/epic_board_type.rb'
+ - 'ee/app/graphql/types/boards/epic_list_metadata_type.rb'
+ - 'ee/app/graphql/types/boards/epic_list_type.rb'
+ - 'ee/app/graphql/types/boards/epic_user_preferences_type.rb'
+ - 'ee/app/graphql/types/burnup_chart_daily_totals_type.rb'
+ - 'ee/app/graphql/types/ci/code_coverage_activity_type.rb'
+ - 'ee/app/graphql/types/ci/code_coverage_summary_type.rb'
+ - 'ee/app/graphql/types/ci/code_quality_degradation_type.rb'
+ - 'ee/app/graphql/types/ci/minutes/namespace_monthly_usage_type.rb'
+ - 'ee/app/graphql/types/ci/minutes/project_monthly_usage_type.rb'
+ - 'ee/app/graphql/types/compliance_management/merge_requests/compliance_violation_type.rb'
+ - 'ee/app/graphql/types/dast/profile_branch_type.rb'
+ - 'ee/app/graphql/types/dast/profile_schedule_type.rb'
+ - 'ee/app/graphql/types/dast/profile_type.rb'
+ - 'ee/app/graphql/types/dast_scanner_profile_type.rb'
+ - 'ee/app/graphql/types/dast_site_profile_type.rb'
+ - 'ee/app/graphql/types/dast_site_validation_type.rb'
+ - 'ee/app/graphql/types/dora_metric_type.rb'
+ - 'ee/app/graphql/types/dora_type.rb'
+ - 'ee/app/graphql/types/epic_descendant_weight_sum_type.rb'
+ - 'ee/app/graphql/types/epic_issue_type.rb'
+ - 'ee/app/graphql/types/epic_type.rb'
+ - 'ee/app/graphql/types/external_issue_type.rb'
+ - 'ee/app/graphql/types/group_release_stats_type.rb'
+ - 'ee/app/graphql/types/instance_security_dashboard_type.rb'
+ - 'ee/app/graphql/types/iteration_type.rb'
+ - 'ee/app/graphql/types/iterations/cadence_type.rb'
+ - 'ee/app/graphql/types/merge_requests/approval_state_type.rb'
+ - 'ee/app/graphql/types/metric_image_type.rb'
+ - 'ee/app/graphql/types/path_lock_type.rb'
+ - 'ee/app/graphql/types/requirements_management/requirement_type.rb'
+ - 'ee/app/graphql/types/requirements_management/test_report_type.rb'
+ - 'ee/app/graphql/types/security/training_type.rb'
+ - 'ee/app/graphql/types/security/training_url_type.rb'
+ - 'ee/app/graphql/types/security_report_summary_type.rb'
+ - 'ee/app/graphql/types/security_scanners.rb'
+ - 'ee/app/graphql/types/time_report_stats_type.rb'
+ - 'ee/app/graphql/types/timebox_metrics_type.rb'
+ - 'ee/app/graphql/types/timebox_report_interface.rb'
+ - 'ee/app/graphql/types/timebox_report_type.rb'
+ - 'ee/app/graphql/types/vulnerabilities/asset_type.rb'
+ - 'ee/app/graphql/types/vulnerabilities/link_type.rb'
+ - 'ee/app/graphql/types/vulnerabilities_count_by_day_type.rb'
+ - 'ee/app/graphql/types/vulnerability/external_issue_link_type.rb'
+ - 'ee/app/graphql/types/vulnerability/issue_link_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/base_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/boolean_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/code_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/commit_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/diff_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/file_location_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/int_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/list_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/markdown_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/module_location_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/table_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/text_type.rb'
+ - 'ee/app/graphql/types/vulnerability_details/url_type.rb'
+ - 'ee/app/graphql/types/vulnerability_evidence_source_type.rb'
+ - 'ee/app/graphql/types/vulnerability_evidence_supporting_message_type.rb'
+ - 'ee/app/graphql/types/vulnerability_evidence_type.rb'
+ - 'ee/app/graphql/types/vulnerability_identifier_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/cluster_image_scanning_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/container_scanning_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/coverage_fuzzing_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/dast_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/dependency_scanning_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/generic_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/sast_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/secret_detection_type.rb'
+ - 'ee/app/graphql/types/vulnerability_request_response_header_type.rb'
+ - 'ee/app/graphql/types/vulnerability_request_type.rb'
+ - 'ee/app/graphql/types/vulnerability_response_type.rb'
+ - 'ee/app/graphql/types/vulnerability_scanner_type.rb'
+ - 'ee/app/graphql/types/vulnerability_severities_count_type.rb'
+ - 'ee/app/graphql/types/vulnerability_type.rb'
+ - 'ee/app/graphql/types/vulnerable_dependency_type.rb'
+ - 'ee/app/graphql/types/vulnerable_kubernetes_resource_type.rb'
+ - 'ee/app/graphql/types/vulnerable_package_type.rb'
+ - 'ee/app/graphql/types/vulnerable_projects_by_grade_type.rb'
+ - 'ee/app/helpers/ee/feature_flags_helper.rb'
+ - 'ee/app/helpers/ee/sorting_helper.rb'
+ - 'ee/app/models/allowed_email_domain.rb'
+ - 'ee/app/models/ci/minutes/quota.rb'
+ - 'ee/app/models/ee/application_setting.rb'
+ - 'ee/app/models/elastic/reindexing_task.rb'
+ - 'ee/app/models/gitlab_subscriptions/features.rb'
+ - 'ee/app/models/gitlab_subscriptions/upcoming_reconciliation.rb'
+ - 'ee/app/models/historical_data.rb'
+ - 'ee/app/models/incident_management/escalation_rule.rb'
+ - 'ee/app/models/incident_management/oncall_rotation.rb'
+ - 'ee/app/models/scim_identity.rb'
+ - 'ee/app/models/status_page/project_setting.rb'
+ - 'ee/app/serializers/ee/evidences/release_entity.rb'
+ - 'ee/app/services/audit_events/repository_push_audit_event_service.rb'
+ - 'ee/app/services/ci/external_pull_requests/process_github_event_service.rb'
+ - 'ee/app/services/ci_cd/setup_project.rb'
+ - 'ee/app/services/ee/issues/base_service.rb'
+ - 'ee/app/services/elastic/cluster_reindexing_service.rb'
+ - 'ee/app/services/elastic/process_bookkeeping_service.rb'
+ - 'ee/app/services/epics/issue_promote_service.rb'
+ - 'ee/app/services/external_status_checks/create_service.rb'
+ - 'ee/app/services/groups/memberships/export_service.rb'
+ - 'ee/app/services/namespaces/check_excess_storage_size_service.rb'
+ - 'ee/app/services/projects/setup_ci_cd.rb'
+ - 'ee/app/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service.rb'
+ - 'ee/config/routes/project.rb'
+ - 'ee/config/routes/uploads.rb'
+ - 'ee/lib/api/iterations.rb'
+ - 'ee/lib/api/merge_trains.rb'
+ - 'ee/lib/api/vulnerability_exports.rb'
+ - 'ee/lib/api/vulnerability_findings.rb'
+ - 'ee/lib/ee/api/helpers/issues_helpers.rb'
+ - 'ee/lib/ee/api/helpers/protected_branches_helpers.rb'
+ - 'ee/lib/ee/api/merge_requests.rb'
+ - 'ee/lib/ee/audit/project_changes_auditor.rb'
+ - 'ee/lib/ee/banzai/filter/references/epic_reference_filter.rb'
+ - 'ee/lib/ee/banzai/filter/references/vulnerability_reference_filter.rb'
+ - 'ee/lib/ee/gitlab/ci/pipeline/chain/validate/after_config.rb'
+ - 'ee/lib/ee/gitlab/quick_actions/issue_actions.rb'
+ - 'ee/lib/ee/gitlab/usage_data.rb'
+ - 'ee/lib/elastic/latest/config.rb'
+ - 'ee/lib/elastic/latest/merge_request_config.rb'
+ - 'ee/lib/gem_extensions/elasticsearch/model/indexing/instance_methods.rb'
+ - 'ee/lib/gitlab/auth/smartcard/certificate.rb'
+ - 'ee/lib/gitlab/auth/smartcard/ldap_certificate.rb'
+ - 'ee/lib/gitlab/ci/parsers/security/formatters/dependency_list.rb'
+ - 'ee/lib/gitlab/elastic/helper.rb'
+ - 'ee/lib/gitlab/elastic/indexer.rb'
+ - 'ee/lib/gitlab/geo/replication/base_transfer.rb'
+ - 'ee/lib/gitlab/prometheus/queries/packet_flow_query.rb'
+ - 'ee/spec/controllers/ee/projects/variables_controller_spec.rb'
+ - 'ee/spec/controllers/groups/epic_boards_controller_spec.rb'
+ - 'ee/spec/controllers/groups/issues_controller_spec.rb'
+ - 'ee/spec/controllers/projects/security/network_policies_controller_spec.rb'
+ - 'ee/spec/controllers/projects/settings/operations_controller_spec.rb'
+ - 'ee/spec/controllers/trials_controller_spec.rb'
+ - 'ee/spec/factories/dependencies.rb'
+ - 'ee/spec/features/billings/billing_plans_spec.rb'
+ - 'ee/spec/features/projects/environments/environments_spec.rb'
+ - 'ee/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb'
+ - 'ee/spec/features/projects/feature_flags/user_updates_feature_flag_spec.rb'
+ - 'ee/spec/finders/epics_finder_spec.rb'
+ - 'ee/spec/finders/merge_requests_finder_spec.rb'
+ - 'ee/spec/frontend/fixtures/dast_profiles.rb'
+ - 'ee/spec/graphql/ee/mutations/ci/runner/update_spec.rb'
+ - 'ee/spec/helpers/billing_plans_helper_spec.rb'
+ - 'ee/spec/helpers/routing/pseudonymization_helper_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/auth/ldap/access_levels_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/auth/ldap/config_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/background_migration/fix_incorrect_max_seats_used_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/ci/config/entry/need_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/ci/matching/runner_matcher_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/import_export/group/legacy_tree_saver_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/import_export/group/tree_saver_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/usage_data_spec.rb'
+ - 'ee/spec/lib/gitlab/auth/ldap/person_spec.rb'
+ - 'ee/spec/lib/gitlab/auth/smartcard/certificate_spec.rb'
+ - 'ee/spec/lib/gitlab/custom_file_templates_spec.rb'
+ - 'ee/spec/lib/gitlab/elastic/client_spec.rb'
+ - 'ee/spec/lib/gitlab/elastic/indexer_spec.rb'
+ - 'ee/spec/lib/gitlab/template/custom_templates_spec.rb'
+ - 'ee/spec/models/application_setting_spec.rb'
+ - 'ee/spec/models/approval_merge_request_rule_spec.rb'
+ - 'ee/spec/models/member_spec.rb'
+ - 'ee/spec/models/vulnerabilities/statistic_spec.rb'
+ - 'ee/spec/requests/api/graphql/app_sec/fuzzing/coverage/corpus_type_spec.rb'
+ - 'ee/spec/requests/api/graphql/group/epics_spec.rb'
+ - 'ee/spec/requests/api/internal/base_spec.rb'
+ - 'ee/spec/requests/api/issues_spec.rb'
+ - 'ee/spec/requests/api/templates_spec.rb'
+ - 'ee/spec/requests/ee/projects/service_desk_controller_spec.rb'
+ - 'ee/spec/requests/projects/security/dast_site_profiles_controller_spec.rb'
+ - 'ee/spec/requests/rack_attack_global_spec.rb'
+ - 'ee/spec/serializers/integrations/zentao_serializers/issue_entity_spec.rb'
+ - 'ee/spec/services/alert_management/network_alert_service_spec.rb'
+ - 'ee/spec/services/app_sec/dast/profiles/create_associations_service_spec.rb'
+ - 'ee/spec/services/audit_events/protected_branch_audit_event_service_spec.rb'
+ - 'ee/spec/services/ci/create_pipeline_service/cross_needs_artifacts_spec.rb'
+ - 'ee/spec/services/ci/process_pipeline_service_spec.rb'
+ - 'ee/spec/services/ci/retry_pipeline_service_spec.rb'
+ - 'ee/spec/services/ci/subscribe_bridge_service_spec.rb'
+ - 'ee/spec/services/merge_trains/check_status_service_spec.rb'
+ - 'ee/spec/services/merge_trains/create_pipeline_service_spec.rb'
+ - 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb'
+ - 'ee/spec/services/merge_trains/refresh_service_spec.rb'
+ - 'ee/spec/services/status_page/trigger_publish_service_spec.rb'
+ - 'ee/spec/services/status_page/unpublish_details_service_spec.rb'
+ - 'ee/spec/support/shared_examples/status_page/publish_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/status_page/reference_links_examples.rb'
+ - 'ee/spec/workers/scan_security_report_secrets_worker_spec.rb'
+ - 'lib/api/applications.rb'
+ - 'lib/api/broadcast_messages.rb'
+ - 'lib/api/bulk_imports.rb'
+ - 'lib/api/ci/job_artifacts.rb'
+ - 'lib/api/ci/jobs.rb'
+ - 'lib/api/ci/pipelines.rb'
+ - 'lib/api/ci/runner.rb'
+ - 'lib/api/ci/runners.rb'
+ - 'lib/api/concerns/packages/debian_distribution_endpoints.rb'
+ - 'lib/api/debian_project_packages.rb'
+ - 'lib/api/deploy_tokens.rb'
+ - 'lib/api/entities/project.rb'
+ - 'lib/api/feature_flags.rb'
+ - 'lib/api/group_labels.rb'
+ - 'lib/api/group_packages.rb'
+ - 'lib/api/groups.rb'
+ - 'lib/api/helm_packages.rb'
+ - 'lib/api/helpers/groups_helpers.rb'
+ - 'lib/api/helpers/merge_requests_helpers.rb'
+ - 'lib/api/helpers/snippets_helpers.rb'
+ - 'lib/api/issue_links.rb'
+ - 'lib/api/issues.rb'
+ - 'lib/api/labels.rb'
+ - 'lib/api/maven_packages.rb'
+ - 'lib/api/members.rb'
+ - 'lib/api/merge_requests.rb'
+ - 'lib/api/metrics/dashboard/annotations.rb'
+ - 'lib/api/metrics/user_starred_dashboards.rb'
+ - 'lib/api/milestone_responses.rb'
+ - 'lib/api/notes.rb'
+ - 'lib/api/pages_domains.rb'
+ - 'lib/api/project_packages.rb'
+ - 'lib/api/project_templates.rb'
+ - 'lib/api/projects.rb'
+ - 'lib/api/protected_branches.rb'
+ - 'lib/api/releases.rb'
+ - 'lib/api/rubygem_packages.rb'
+ - 'lib/api/sidekiq_metrics.rb'
+ - 'lib/api/users.rb'
+ - 'lib/backup/gitaly_backup.rb'
+ - 'lib/banzai/filter/references/abstract_reference_filter.rb'
+ - 'lib/banzai/reference_redactor.rb'
+ - 'lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb'
+ - 'lib/gitlab/abuse.rb'
+ - 'lib/gitlab/access.rb'
+ - 'lib/gitlab/application_rate_limiter.rb'
+ - 'lib/gitlab/auth/ldap/config.rb'
+ - 'lib/gitlab/auth/o_auth/auth_hash.rb'
+ - 'lib/gitlab/auth/o_auth/provider.rb'
+ - 'lib/gitlab/auth/o_auth/user.rb'
+ - 'lib/gitlab/background_migration/backfill_project_repositories.rb'
+ - 'lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid.rb'
+ - 'lib/gitlab/ci/ansi2html.rb'
+ - 'lib/gitlab/ci/ansi2json/parser.rb'
+ - 'lib/gitlab/ci/config/entry/processable.rb'
+ - 'lib/gitlab/ci/jwt_v2.rb'
+ - 'lib/gitlab/ci/pipeline/chain/validate/external.rb'
+ - 'lib/gitlab/ci/reports/security/scanner.rb'
+ - 'lib/gitlab/database/partitioning/sliding_list_strategy.rb'
+ - 'lib/gitlab/database/reflection.rb'
+ - 'lib/gitlab/diff/file_collection/compare.rb'
+ - 'lib/gitlab/email/attachment_uploader.rb'
+ - 'lib/gitlab/emoji.rb'
+ - 'lib/gitlab/etag_caching/middleware.rb'
+ - 'lib/gitlab/experimentation.rb'
+ - 'lib/gitlab/fogbugz_import/importer.rb'
+ - 'lib/gitlab/git/repository.rb'
+ - 'lib/gitlab/gitaly_client/commit_service.rb'
+ - 'lib/gitlab/gitaly_client/operation_service.rb'
+ - 'lib/gitlab/hook_data/project_member_builder.rb'
+ - 'lib/gitlab/import_export/group/legacy_tree_restorer.rb'
+ - 'lib/gitlab/import_export/group/relation_factory.rb'
+ - 'lib/gitlab/import_export/group/relation_tree_restorer.rb'
+ - 'lib/gitlab/import_export/members_mapper.rb'
+ - 'lib/gitlab/import_export/project/import_task.rb'
+ - 'lib/gitlab/import_export/shared.rb'
+ - 'lib/gitlab/kubernetes.rb'
+ - 'lib/gitlab/marginalia/comment.rb'
+ - 'lib/gitlab/metrics/dashboard/stages/grafana_formatter.rb'
+ - 'lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics.rb'
+ - 'lib/gitlab/metrics/dashboard/validator/client.rb'
+ - 'lib/gitlab/metrics/exporter/metrics_middleware.rb'
+ - 'lib/gitlab/metrics/samplers/puma_sampler.rb'
+ - 'lib/gitlab/metrics/samplers/ruby_sampler.rb'
+ - 'lib/gitlab/no_cache_headers.rb'
+ - 'lib/gitlab/sidekiq_daemon/memory_killer.rb'
+ - 'lib/gitlab/sidekiq_middleware/server_metrics.rb'
+ - 'lib/gitlab/slash_commands/presenters/base.rb'
+ - 'lib/gitlab/template/gitignore_template.rb'
+ - 'lib/gitlab/visibility_level.rb'
+ - 'lib/product_analytics/event_params.rb'
+ - 'lib/sidebars/projects/menus/infrastructure_menu.rb'
+ - 'lib/tasks/gitlab/import_export/export.rake'
+ - 'lib/tasks/gitlab/import_export/import.rake'
+ - 'lib/tasks/tanuki_emoji.rake'
+ - 'qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb'
+ - 'qa/qa/specs/features/ee/browser_ui/1_manage/project/project_templates_spec.rb'
+ - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb'
+ - 'qa/qa/support/loglinking.rb'
+ - 'qa/spec/support/loglinking_spec.rb'
+ - 'spec/controllers/concerns/product_analytics_tracking_spec.rb'
+ - 'spec/controllers/concerns/redis_tracking_spec.rb'
+ - 'spec/controllers/oauth/token_info_controller_spec.rb'
+ - 'spec/controllers/omniauth_callbacks_controller_spec.rb'
+ - 'spec/controllers/projects/artifacts_controller_spec.rb'
+ - 'spec/controllers/projects/feature_flags_controller_spec.rb'
+ - 'spec/controllers/projects/grafana_api_controller_spec.rb'
+ - 'spec/controllers/projects/issues_controller_spec.rb'
+ - 'spec/controllers/projects/merge_requests_controller_spec.rb'
+ - 'spec/controllers/projects/registry/tags_controller_spec.rb'
+ - 'spec/controllers/projects/service_desk_controller_spec.rb'
+ - 'spec/controllers/search_controller_spec.rb'
+ - 'spec/factories/ci/builds.rb'
+ - 'spec/factories/ci/stages.rb'
+ - 'spec/factories/groups.rb'
+ - 'spec/features/dashboard/datetime_on_tooltips_spec.rb'
+ - 'spec/features/groups/issues_spec.rb'
+ - 'spec/features/profiles/active_sessions_spec.rb'
+ - 'spec/features/projects/badges/coverage_spec.rb'
+ - 'spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb'
+ - 'spec/features/projects/feature_flags/user_updates_feature_flag_spec.rb'
+ - 'spec/features/projects/jobs/user_browses_jobs_spec.rb'
+ - 'spec/features/projects/milestones/milestones_sorting_spec.rb'
+ - 'spec/features/projects/new_project_spec.rb'
+ - 'spec/features/projects/pipelines/pipeline_spec.rb'
+ - 'spec/features/projects/pipelines/pipelines_spec.rb'
+ - 'spec/features/snippets/user_creates_snippet_spec.rb'
+ - 'spec/features/users/login_spec.rb'
+ - 'spec/finders/ci/pipelines_for_merge_request_finder_spec.rb'
+ - 'spec/finders/group_descendants_finder_spec.rb'
+ - 'spec/finders/group_members_finder_spec.rb'
+ - 'spec/finders/template_finder_spec.rb'
+ - 'spec/graphql/mutations/releases/update_spec.rb'
+ - 'spec/graphql/resolvers/ci/config_resolver_spec.rb'
+ - 'spec/graphql/resolvers/design_management/versions_resolver_spec.rb'
+ - 'spec/helpers/sorting_helper_spec.rb'
+ - 'spec/helpers/storage_helper_spec.rb'
+ - 'spec/helpers/wiki_helper_spec.rb'
+ - 'spec/initializers/00_rails_disable_joins_spec.rb'
+ - 'spec/lib/backup/gitaly_backup_spec.rb'
+ - 'spec/lib/banzai/filter/repository_link_filter_spec.rb'
+ - 'spec/lib/gitlab/asciidoc_spec.rb'
+ - 'spec/lib/gitlab/auth/ldap/auth_hash_spec.rb'
+ - 'spec/lib/gitlab/auth/ldap/config_spec.rb'
+ - 'spec/lib/gitlab/auth/ldap/person_spec.rb'
+ - 'spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb'
+ - 'spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb'
+ - 'spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb'
+ - 'spec/lib/gitlab/background_migration/encrypt_integration_properties_spec.rb'
+ - 'spec/lib/gitlab/ci/config/entry/port_spec.rb'
+ - 'spec/lib/gitlab/ci/config/entry/root_spec.rb'
+ - 'spec/lib/gitlab/ci/config/external/mapper_spec.rb'
+ - 'spec/lib/gitlab/ci/lint_spec.rb'
+ - 'spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb'
+ - 'spec/lib/gitlab/ci/variables/builder_spec.rb'
+ - 'spec/lib/gitlab/ci/yaml_processor_spec.rb'
+ - 'spec/lib/gitlab/data_builder/note_spec.rb'
+ - 'spec/lib/gitlab/database/migration_helpers_spec.rb'
+ - 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb'
+ - 'spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb'
+ - 'spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb'
+ - 'spec/lib/gitlab/diff/highlight_cache_spec.rb'
+ - 'spec/lib/gitlab/diff/lines_unfolder_spec.rb'
+ - 'spec/lib/gitlab/diff/position_spec.rb'
+ - 'spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb'
+ - 'spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb'
+ - 'spec/lib/gitlab/etag_caching/middleware_spec.rb'
+ - 'spec/lib/gitlab/etag_caching/router/graphql_spec.rb'
+ - 'spec/lib/gitlab/git/repository_spec.rb'
+ - 'spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb'
+ - 'spec/lib/gitlab/hook_data/issuable_builder_spec.rb'
+ - 'spec/lib/gitlab/import_export/attributes_finder_spec.rb'
+ - 'spec/lib/gitlab/import_export/group/object_builder_spec.rb'
+ - 'spec/lib/gitlab/import_export/group/relation_tree_restorer_spec.rb'
+ - 'spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb'
+ - 'spec/lib/gitlab/import_export/project/sample/relation_tree_restorer_spec.rb'
+ - 'spec/lib/gitlab/import_sources_spec.rb'
+ - 'spec/lib/gitlab/instrumentation_helper_spec.rb'
+ - 'spec/lib/gitlab/metrics/dashboard/importers/prometheus_metrics_spec.rb'
+ - 'spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb'
+ - 'spec/lib/gitlab/metrics/dashboard/validator_spec.rb'
+ - 'spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb'
+ - 'spec/lib/gitlab/metrics/subscribers/action_view_spec.rb'
+ - 'spec/lib/gitlab/metrics/subscribers/active_record_spec.rb'
+ - 'spec/lib/gitlab/metrics/subscribers/load_balancing_spec.rb'
+ - 'spec/lib/gitlab/metrics/web_transaction_spec.rb'
+ - 'spec/lib/gitlab/omniauth_initializer_spec.rb'
+ - 'spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb'
+ - 'spec/lib/gitlab/slug/environment_spec.rb'
+ - 'spec/lib/gitlab/tracking_spec.rb'
+ - 'spec/lib/gitlab/usage_data/topology_spec.rb'
+ - 'spec/lib/gitlab/usage_data_counters/note_counter_spec.rb'
+ - 'spec/lib/gitlab/usage_data_spec.rb'
+ - 'spec/lib/gitlab/utils_spec.rb'
+ - 'spec/lib/gitlab/word_diff/parser_spec.rb'
+ - 'spec/lib/marginalia_spec.rb'
+ - 'spec/lib/security/ci_configuration/sast_build_action_spec.rb'
+ - 'spec/mailers/emails/pipelines_spec.rb'
+ - 'spec/migrations/20210804150320_create_base_work_item_types_spec.rb'
+ - 'spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb'
+ - 'spec/migrations/20211117084814_migrate_remaining_u2f_registrations_spec.rb'
+ - 'spec/migrations/20211126204445_add_task_to_work_item_types_spec.rb'
+ - 'spec/models/ci/build_spec.rb'
+ - 'spec/models/ci/pipeline_spec.rb'
+ - 'spec/models/clusters/platforms/kubernetes_spec.rb'
+ - 'spec/models/container_registry/event_spec.rb'
+ - 'spec/models/design_management/version_spec.rb'
+ - 'spec/models/group_spec.rb'
+ - 'spec/models/integrations/chat_message/pipeline_message_spec.rb'
+ - 'spec/models/integrations/drone_ci_spec.rb'
+ - 'spec/models/merge_request_spec.rb'
+ - 'spec/models/namespace_spec.rb'
+ - 'spec/models/operations/feature_flag_spec.rb'
+ - 'spec/models/pages_domain_spec.rb'
+ - 'spec/models/project_spec.rb'
+ - 'spec/models/remote_mirror_spec.rb'
+ - 'spec/models/repository_spec.rb'
+ - 'spec/models/user_spec.rb'
+ - 'spec/presenters/clusters/cluster_presenter_spec.rb'
+ - 'spec/presenters/project_presenter_spec.rb'
+ - 'spec/requests/api/ci/job_artifacts_spec.rb'
+ - 'spec/requests/api/ci/jobs_spec.rb'
+ - 'spec/requests/api/ci/runner/jobs_request_post_spec.rb'
+ - 'spec/requests/api/feature_flags_spec.rb'
+ - 'spec/requests/api/graphql/ci/config_spec.rb'
+ - 'spec/requests/api/graphql/ci/runner_spec.rb'
+ - 'spec/requests/api/graphql/mutations/releases/update_spec.rb'
+ - 'spec/requests/api/graphql/packages/conan_spec.rb'
+ - 'spec/requests/api/graphql/project/issue/design_collection/version_spec.rb'
+ - 'spec/requests/api/graphql/project/terraform/state_spec.rb'
+ - 'spec/requests/api/graphql/project/terraform/states_spec.rb'
+ - 'spec/requests/api/graphql/query_spec.rb'
+ - 'spec/requests/api/groups_spec.rb'
+ - 'spec/requests/api/internal/base_spec.rb'
+ - 'spec/requests/api/issues/get_group_issues_spec.rb'
+ - 'spec/requests/api/suggestions_spec.rb'
+ - 'spec/requests/api/unleash_spec.rb'
+ - 'spec/requests/git_http_spec.rb'
+ - 'spec/requests/openid_connect_spec.rb'
+ - 'spec/requests/projects/merge_requests_discussions_spec.rb'
+ - 'spec/routing/project_routing_spec.rb'
+ - 'spec/serializers/ci/lint/job_entity_spec.rb'
+ - 'spec/serializers/container_repository_entity_spec.rb'
+ - 'spec/serializers/merge_request_metrics_helper_spec.rb'
+ - 'spec/services/ci/create_downstream_pipeline_service_spec.rb'
+ - 'spec/services/ci/create_pipeline_service/logger_spec.rb'
+ - 'spec/services/ci/create_pipeline_service/tags_spec.rb'
+ - 'spec/services/ci/job_artifacts/create_service_spec.rb'
+ - 'spec/services/ci/retry_build_service_spec.rb'
+ - 'spec/services/deployments/link_merge_requests_service_spec.rb'
+ - 'spec/services/discussions/capture_diff_note_positions_service_spec.rb'
+ - 'spec/services/groups/import_export/import_service_spec.rb'
+ - 'spec/services/issuable/bulk_update_service_spec.rb'
+ - 'spec/services/issues/create_service_spec.rb'
+ - 'spec/services/merge_requests/build_service_spec.rb'
+ - 'spec/services/merge_requests/create_service_spec.rb'
+ - 'spec/services/merge_requests/update_service_spec.rb'
+ - 'spec/services/metrics/dashboard/clone_dashboard_service_spec.rb'
+ - 'spec/services/notes/create_service_spec.rb'
+ - 'spec/services/notes/destroy_service_spec.rb'
+ - 'spec/services/packages/debian/parse_debian822_service_spec.rb'
+ - 'spec/services/projects/destroy_service_spec.rb'
+ - 'spec/services/suggestions/apply_service_spec.rb'
+ - 'spec/support/helpers/create_environments_helpers.rb'
+ - 'spec/support/helpers/kubernetes_helpers.rb'
+ - 'spec/support/helpers/migrations_helpers/work_item_types_helper.rb'
+ - 'spec/support/helpers/seed_helper.rb'
+ - 'spec/support/helpers/stub_object_storage.rb'
+ - 'spec/support/helpers/test_env.rb'
+ - 'spec/support/helpers/usage_data_helpers.rb'
+ - 'spec/support/migrations_helpers/vulnerabilities_findings_helper.rb'
+ - 'spec/support/shared_contexts/bulk_imports_requests_shared_context.rb'
+ - 'spec/support/shared_contexts/design_management_shared_contexts.rb'
+ - 'spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb'
+ - 'spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb'
+ - 'spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb'
+ - 'spec/support/shared_examples/routing/resource_routing_shared_examples.rb'
+ - 'spec/support/shared_examples/routing/wiki_routing_shared_examples.rb'
+ - 'spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb'
+ - 'spec/tooling/danger/datateam_spec.rb'
+ - 'spec/views/projects/tags/index.html.haml_spec.rb'
+ - 'spec/workers/emails_on_push_worker_spec.rb'
+ - 'spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb'
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
index 684386883c8..dc29b3dfa38 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -86,7 +86,7 @@ export default {
);
},
statusIconName() {
- if (this.hasFetchError) return EXTENSION_ICONS.error;
+ if (this.hasFetchError) return EXTENSION_ICONS.failed;
if (this.isLoadingSummary) return null;
return this.statusIcon(this.collapsedData);
@@ -214,7 +214,7 @@ export default {
// To allow for text to be selected we check if the the user is clicking
// or selecting, if they are selecting the time difference should be
// more than 200ms
- if (up - this.down < 200) {
+ if (up - this.down < 200 && !e?.target?.closest('.btn-icon')) {
this.toggleCollapsed(e);
}
},
@@ -264,7 +264,7 @@ export default {
category="tertiary"
data-testid="toggle-button"
size="small"
- @click.self="toggleCollapsed"
+ @click="toggleCollapsed"
/>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js
new file mode 100644
index 00000000000..cd5cfb6837c
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js
@@ -0,0 +1,39 @@
+import { __, n__, s__, sprintf } from '~/locale';
+
+const digitText = (bold = false) => (bold ? '%{strong_start}%d%{strong_end}' : '%d');
+const noText = (bold = false) => (bold ? '%{strong_start}no%{strong_end}' : 'no');
+
+export const TESTS_FAILED_STATUS = 'failed';
+export const ERROR_STATUS = 'error';
+
+export const i18n = {
+ label: s__('Reports|Test summary'),
+ loading: s__('Reports|Test summary results are loading'),
+ error: s__('Reports|Test summary failed to load results'),
+ fullReport: s__('Reports|Full report'),
+
+ noChanges: (bold) => s__(`Reports|${noText(bold)} changed test results`),
+ resultsString: (combinedString, resolvedString) =>
+ sprintf(s__('Reports|%{combinedString} and %{resolvedString}'), {
+ combinedString,
+ resolvedString,
+ }),
+
+ summaryText: (name, resultsString) =>
+ sprintf(__('%{name}: %{resultsString}'), { name, resultsString }),
+
+ failedClause: (failed, bold) =>
+ n__(`${digitText(bold)} failed`, `${digitText(bold)} failed`, failed),
+ erroredClause: (errored, bold) =>
+ n__(`${digitText(bold)} error`, `${digitText(bold)} errors`, errored),
+ resolvedClause: (resolved, bold) =>
+ n__(`${digitText(bold)} fixed test result`, `${digitText(bold)} fixed test results`, resolved),
+ totalClause: (total, bold) =>
+ n__(`${digitText(bold)} total test`, `${digitText(bold)} total tests`, total),
+
+ reportError: s__('Reports|An error occurred while loading report'),
+ reportErrorWithName: (name) =>
+ sprintf(s__('Reports|An error occurred while loading %{name} results'), { name }),
+ headReportParsingError: s__('Reports|Head report parsing error:'),
+ baseReportParsingError: s__('Reports|Base report parsing error:'),
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
new file mode 100644
index 00000000000..65d9257903f
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
@@ -0,0 +1,82 @@
+import { uniqueId } from 'lodash';
+import axios from '~/lib/utils/axios_utils';
+import { EXTENSION_ICONS } from '../../constants';
+import { summaryTextBuilder, reportTextBuilder, reportSubTextBuilder } from './utils';
+import { i18n, TESTS_FAILED_STATUS, ERROR_STATUS } from './constants';
+
+export default {
+ name: 'WidgetTestSummary',
+ enablePolling: true,
+ i18n,
+ expandEvent: 'i_testing_summary_widget_total',
+ props: ['testResultsPath', 'headBlobPath', 'pipeline'],
+ computed: {
+ summary(data) {
+ if (data.parsingInProgress) {
+ return this.$options.i18n.loading;
+ }
+ if (data.hasSuiteError) {
+ return this.$options.i18n.error;
+ }
+ return summaryTextBuilder(this.$options.i18n.label, data.summary);
+ },
+ statusIcon(data) {
+ if (data.parsingInProgress) {
+ return null;
+ }
+ if (data.status === TESTS_FAILED_STATUS) {
+ return EXTENSION_ICONS.warning;
+ }
+ if (data.hasSuiteError) {
+ return EXTENSION_ICONS.failed;
+ }
+ return EXTENSION_ICONS.success;
+ },
+ tertiaryButtons() {
+ return [
+ {
+ text: this.$options.i18n.fullReport,
+ href: `${this.pipeline.path}/test_report`,
+ target: '_blank',
+ },
+ ];
+ },
+ },
+ methods: {
+ fetchCollapsedData() {
+ return axios.get(this.testResultsPath).then(({ data = {}, status }) => {
+ return {
+ data: {
+ hasSuiteError: data.suites?.some((suite) => suite.status === ERROR_STATUS),
+ parsingInProgress: status === 204,
+ ...data,
+ },
+ };
+ });
+ },
+ fetchFullData() {
+ return Promise.resolve(this.prepareReports());
+ },
+ suiteIcon(suite) {
+ if (suite.status === ERROR_STATUS) {
+ return EXTENSION_ICONS.error;
+ }
+ if (suite.status === TESTS_FAILED_STATUS) {
+ return EXTENSION_ICONS.failed;
+ }
+ return EXTENSION_ICONS.success;
+ },
+ prepareReports() {
+ return this.collapsedData.suites.map((suite) => {
+ return {
+ id: uniqueId('suite-'),
+ text: reportTextBuilder(suite),
+ subtext: reportSubTextBuilder(suite),
+ icon: {
+ name: this.suiteIcon(suite),
+ },
+ };
+ });
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
new file mode 100644
index 00000000000..a74ed20362f
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
@@ -0,0 +1,55 @@
+import { i18n } from './constants';
+
+const textBuilder = (results, boldNumbers = false) => {
+ const { failed, errored, resolved, total } = results;
+
+ const failedOrErrored = (failed || 0) + (errored || 0);
+ const failedString = failed ? i18n.failedClause(failed, boldNumbers) : null;
+ const erroredString = errored ? i18n.erroredClause(errored, boldNumbers) : null;
+ const combinedString =
+ failed && errored ? `${failedString}, ${erroredString}` : failedString || erroredString;
+ const resolvedString = resolved ? i18n.resolvedClause(resolved, boldNumbers) : null;
+ const totalString = total ? i18n.totalClause(total, boldNumbers) : null;
+
+ let resultsString = i18n.noChanges(boldNumbers);
+
+ if (failedOrErrored) {
+ if (resolved) {
+ resultsString = i18n.resultsString(combinedString, resolvedString);
+ } else {
+ resultsString = combinedString;
+ }
+ } else if (resolved) {
+ resultsString = resolvedString;
+ }
+
+ return `${resultsString}, ${totalString}`;
+};
+
+export const summaryTextBuilder = (name = '', results = {}) => {
+ const resultsString = textBuilder(results, true);
+ return i18n.summaryText(name, resultsString);
+};
+
+export const reportTextBuilder = ({ name = '', summary = {}, status }) => {
+ if (!name) {
+ return i18n.reportError;
+ }
+ if (status === 'error') {
+ return i18n.reportErrorWithName(name);
+ }
+
+ const resultsString = textBuilder(summary);
+ return i18n.summaryText(name, resultsString);
+};
+
+export const reportSubTextBuilder = ({ suite_errors }) => {
+ const errors = [];
+ if (suite_errors?.head) {
+ errors.push(`${i18n.headReportParsingError} ${suite_errors.head}`);
+ }
+ if (suite_errors?.base) {
+ errors.push(`${i18n.baseReportParsingError} ${suite_errors.base}`);
+ }
+ return errors.join('<br />');
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 965746e79fb..4b3ad288768 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -47,6 +47,7 @@ import getStateQuery from './queries/get_state.query.graphql';
import terraformExtension from './extensions/terraform';
import accessibilityExtension from './extensions/accessibility';
import codeQualityExtension from './extensions/code_quality';
+import testReportExtension from './extensions/test_report';
export default {
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
@@ -191,6 +192,9 @@ export default {
shouldRenderTerraformPlans() {
return Boolean(this.mr?.terraformReportsPath);
},
+ shouldRenderTestReport() {
+ return Boolean(this.mr?.testResultsPath);
+ },
mergeError() {
let { mergeError } = this.mr;
@@ -252,6 +256,11 @@ export default {
this.registerAccessibilityExtension();
}
},
+ shouldRenderTestReport(newVal) {
+ if (newVal) {
+ this.registerTestReportExtension();
+ }
+ },
},
mounted() {
MRWidgetService.fetchInitialData()
@@ -502,6 +511,11 @@ export default {
registerExtension(codeQualityExtension);
}
},
+ registerTestReportExtension() {
+ if (this.shouldRenderTestReport && this.shouldShowExtension) {
+ registerExtension(testReportExtension);
+ }
+ },
},
};
</script>
@@ -574,7 +588,7 @@ export default {
/>
<grouped-test-reports-app
- v-if="mr.testResultsPath"
+ v-if="mr.testResultsPath && !shouldShowExtension"
class="js-reports-container"
:endpoint="mr.testResultsPath"
:head-blob-path="mr.headBlobPath"
diff --git a/app/controllers/admin/plan_limits_controller.rb b/app/controllers/admin/plan_limits_controller.rb
index 420fd93fad5..364e7fe6036 100644
--- a/app/controllers/admin/plan_limits_controller.rb
+++ b/app/controllers/admin/plan_limits_controller.rb
@@ -38,6 +38,13 @@ class Admin::PlanLimitsController < Admin::ApplicationController
pypi_max_file_size
terraform_module_max_file_size
generic_packages_max_file_size
+ ci_pipeline_size
+ ci_active_jobs
+ ci_project_subscriptions
+ ci_pipeline_schedules
+ ci_needs_size_limit
+ ci_registered_group_runners
+ ci_registered_project_runners
])
end
end
diff --git a/app/models/concerns/bulk_users_by_email_load.rb b/app/models/concerns/bulk_users_by_email_load.rb
new file mode 100644
index 00000000000..edbd3e21458
--- /dev/null
+++ b/app/models/concerns/bulk_users_by_email_load.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module BulkUsersByEmailLoad
+ extend ActiveSupport::Concern
+
+ included do
+ def users_by_emails(emails)
+ Gitlab::SafeRequestLoader.execute(resource_key: user_by_email_resource_key, resource_ids: emails) do |emails|
+ # have to consider all emails - even secondary, so use all_emails here
+ grouped_users_by_email = User.by_any_email(emails).preload(:emails).group_by(&:all_emails)
+
+ grouped_users_by_email.each_with_object({}) do |(found_emails, users), h|
+ found_emails.each { |e| h[e] = users.first if emails.include?(e) } # don't include all emails for an account, only the ones we want
+ end
+ end
+ end
+
+ private
+
+ def user_by_email_resource_key
+ "user_by_email_for_#{User.name.underscore.pluralize}:#{self.class}:#{self.id}"
+ end
+ end
+end
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index fa03d73646d..e3cedb608c1 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -9,10 +9,13 @@ class ContainerRepository < ApplicationRecord
WAITING_CLEANUP_STATUSES = %i[cleanup_scheduled cleanup_unfinished].freeze
REQUIRING_CLEANUP_STATUSES = %i[cleanup_unscheduled cleanup_scheduled].freeze
+
IDLE_MIGRATION_STATES = %w[default pre_import_done import_done import_aborted import_skipped].freeze
ACTIVE_MIGRATION_STATES = %w[pre_importing importing].freeze
- ABORTABLE_MIGRATION_STATES = (ACTIVE_MIGRATION_STATES + %w[pre_import_done default]).freeze
MIGRATION_STATES = (IDLE_MIGRATION_STATES + ACTIVE_MIGRATION_STATES).freeze
+ ABORTABLE_MIGRATION_STATES = (ACTIVE_MIGRATION_STATES + %w[pre_import_done default]).freeze
+
+ IRRECONCILABLE_MIGRATIONS_STATUSES = %w[import_in_progress pre_import_in_progress pre_import_canceled import_canceled].freeze
MIGRATION_PHASE_1_STARTED_AT = Date.new(2021, 11, 4).freeze
@@ -32,7 +35,7 @@ class ContainerRepository < ApplicationRecord
enum status: { delete_scheduled: 0, delete_failed: 1 }
enum expiration_policy_cleanup_status: { cleanup_unscheduled: 0, cleanup_scheduled: 1, cleanup_unfinished: 2, cleanup_ongoing: 3 }
- enum migration_skipped_reason: { not_in_plan: 0, too_many_retries: 1, too_many_tags: 2, root_namespace_in_deny_list: 3 }
+ enum migration_skipped_reason: { not_in_plan: 0, too_many_retries: 1, too_many_tags: 2, root_namespace_in_deny_list: 3, migration_canceled: 4 }
delegate :client, :gitlab_api_client, to: :registry
@@ -114,15 +117,15 @@ class ContainerRepository < ApplicationRecord
end
event :finish_pre_import do
- transition %i[pre_importing import_aborted] => :pre_import_done
+ transition %i[pre_importing importing import_aborted] => :pre_import_done
end
event :start_import do
- transition pre_import_done: :importing
+ transition %i[pre_import_done pre_importing importing import_aborted] => :importing
end
event :finish_import do
- transition %i[importing import_aborted] => :import_done
+ transition %i[pre_importing importing import_aborted] => :import_done
end
event :already_migrated do
@@ -138,11 +141,11 @@ class ContainerRepository < ApplicationRecord
end
event :retry_pre_import do
- transition import_aborted: :pre_importing
+ transition %i[pre_importing importing import_aborted] => :pre_importing
end
event :retry_import do
- transition import_aborted: :importing
+ transition %i[pre_importing importing import_aborted] => :importing
end
before_transition any => :pre_importing do |container_repository|
@@ -276,24 +279,28 @@ class ContainerRepository < ApplicationRecord
def retry_aborted_migration
return unless migration_state == 'import_aborted'
- case external_import_status
+ reconcile_import_status(external_import_status) do
+ # If the import_status request fails, use the timestamp to guess current state
+ migration_pre_import_done_at ? retry_import : retry_pre_import
+ end
+ end
+
+ def reconcile_import_status(status)
+ case status
when 'native'
raise NativeImportError
- when 'import_in_progress'
+ when *IRRECONCILABLE_MIGRATIONS_STATUSES
nil
when 'import_complete'
finish_import
when 'import_failed'
retry_import
- when 'pre_import_in_progress'
- nil
when 'pre_import_complete'
finish_pre_import_and_start_import
when 'pre_import_failed'
retry_pre_import
else
- # If the import_status request fails, use the timestamp to guess current state
- migration_pre_import_done_at ? retry_import : retry_pre_import
+ yield
end
end
@@ -450,6 +457,12 @@ class ContainerRepository < ApplicationRecord
response
end
+ def migration_cancel
+ return :error unless gitlab_api_client.supports_gitlab_api?
+
+ gitlab_api_client.cancel_repository_import(self.path)
+ end
+
def self.build_from_path(path)
self.new(project: path.repository_project,
name: path.repository_name)
diff --git a/app/models/group.rb b/app/models/group.rb
index 14d088dd38b..d05a202e013 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -17,6 +17,7 @@ class Group < Namespace
include GroupAPICompatibility
include EachBatch
include BulkMemberAccessLoad
+ include BulkUsersByEmailLoad
include ChronicDurationAttribute
include RunnerTokenExpirationInterval
diff --git a/app/models/project.rb b/app/models/project.rb
index 155ebe88d33..8f8f6cbf81f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -37,6 +37,7 @@ class Project < ApplicationRecord
include EachBatch
include GitlabRoutingHelper
include BulkMemberAccessLoad
+ include BulkUsersByEmailLoad
include RunnerTokenExpirationInterval
include BlocksUnsafeSerialization
diff --git a/app/services/concerns/members/bulk_create_users.rb b/app/services/concerns/members/bulk_create_users.rb
index 3c57301cafc..e60c84af89e 100644
--- a/app/services/concerns/members/bulk_create_users.rb
+++ b/app/services/concerns/members/bulk_create_users.rb
@@ -51,12 +51,20 @@ module Members
users.concat(User.id_in(user_ids)) if user_ids.present?
users.uniq! # de-duplicate just in case as there is no controlling if user records and ids are sent multiple times
+ users_by_emails = source.users_by_emails(emails) # preloads our request store for all emails
+ # in case emails belong to a user that is being invited by user or user_id, remove them from
+ # emails and let users/user_ids handle it.
+ parsed_emails = emails.select do |email|
+ user = users_by_emails[email]
+ !user || (users.exclude?(user) && user_ids.exclude?(user.id))
+ end
+
if users.present?
# helps not have to perform another query per user id to see if the member exists later on when fetching
existing_members = source.members_and_requesters.with_user(users).index_by(&:user_id)
end
- [emails, users, existing_members]
+ [parsed_emails, users, existing_members]
end
end
end
diff --git a/app/services/members/creator_service.rb b/app/services/members/creator_service.rb
index fcce32ead94..10c7697f23d 100644
--- a/app/services/members/creator_service.rb
+++ b/app/services/members/creator_service.rb
@@ -114,7 +114,7 @@ module Members
User.find_by(id: user) # rubocop:todo CodeReuse/ActiveRecord
else
# must be an email or at least we'll consider it one
- User.find_by_any_email(user) || user
+ source.users_by_emails([user])[user] || user
end
end
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 41698f9720b..b3f79e4f369 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -1,80 +1,123 @@
-= form_for @application_setting, url: ci_cd_admin_application_settings_path(anchor: 'js-ci-cd-settings'), html: { class: 'fieldset-form' } do |f|
- = form_errors(@application_setting)
+.settings-content
+ = form_for @application_setting, url: ci_cd_admin_application_settings_path(anchor: 'js-ci-cd-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
- %fieldset
- .form-group
- .form-check
- = f.check_box :auto_devops_enabled, class: 'form-check-input'
- = f.label :auto_devops_enabled, class: 'form-check-label' do
- = s_('CICD|Default to Auto DevOps pipeline for all projects')
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :auto_devops_enabled, class: 'form-check-input'
+ = f.label :auto_devops_enabled, class: 'form-check-label' do
+ = s_('CICD|Default to Auto DevOps pipeline for all projects')
+ .form-text.text-muted
+ = s_('CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file.')
+ = link_to _('What is Auto DevOps?'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
+ .form-group
+ = f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
+ = f.text_field :auto_devops_domain, class: 'form-control gl-form-input', placeholder: 'example.com'
.form-text.text-muted
- = s_('CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file.')
- = link_to _('What is Auto DevOps?'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
- .form-group
- = f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
- = f.text_field :auto_devops_domain, class: 'form-control gl-form-input', placeholder: 'example.com'
- .form-text.text-muted
- = s_("AdminSettings|The default domain to use for Auto Review Apps and Auto Deploy stages in all projects.")
- = link_to _('Learn more.'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-review-apps'), target: '_blank', rel: 'noopener noreferrer'
+ = s_("AdminSettings|The default domain to use for Auto Review Apps and Auto Deploy stages in all projects.")
+ = link_to _('Learn more.'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-review-apps'), target: '_blank', rel: 'noopener noreferrer'
- .form-group
- .form-check
- = f.check_box :shared_runners_enabled, class: 'form-check-input'
- = f.label :shared_runners_enabled, class: 'form-check-label' do
- = s_("AdminSettings|Enable shared runners for new projects")
- .form-text.text-muted
- = s_("AdminSettings|All new projects can use the instance's shared runners by default.")
+ .form-group
+ .form-check
+ = f.check_box :shared_runners_enabled, class: 'form-check-input'
+ = f.label :shared_runners_enabled, class: 'form-check-label' do
+ = s_("AdminSettings|Enable shared runners for new projects")
+ .form-text.text-muted
+ = s_("AdminSettings|All new projects can use the instance's shared runners by default.")
- = render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f
+ = render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f
- .form-group
- = f.label :shared_runners_text, _('Shared runners details'), class: 'label-bold'
- = f.text_area :shared_runners_text, class: 'form-control gl-form-input', rows: 4
- .form-text.text-muted= _("Add a custom message with details about the instance's shared runners. The message is visible in group and project CI/CD settings, in the Runners section. Markdown is supported.")
- .form-group
- = f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold'
- = f.number_field :max_artifacts_size, class: 'form-control gl-form-input'
- .form-text.text-muted
- = _("The maximum file size for job artifacts.")
- = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
- .form-group
- = f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold'
- = f.text_field :default_artifacts_expire_in, class: 'form-control gl-form-input'
- .form-text.text-muted
- = html_escape(_("Set the default expiration time for job artifacts in all projects. Set to %{code_open}0%{code_close} to never expire artifacts by default. If no unit is written, it defaults to seconds. For example, these are all equivalent: %{code_open}3600%{code_close}, %{code_open}60 minutes%{code_close}, or %{code_open}one hour%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
- .form-group
- .form-check
- = f.check_box :keep_latest_artifact, class: 'form-check-input'
- = f.label :keep_latest_artifact, class: 'form-check-label' do
- = s_('AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines')
+ .form-group
+ = f.label :shared_runners_text, _('Shared runners details'), class: 'label-bold'
+ = f.text_area :shared_runners_text, class: 'form-control gl-form-input', rows: 4
+ .form-text.text-muted= _("Add a custom message with details about the instance's shared runners. The message is visible in group and project CI/CD settings, in the Runners section. Markdown is supported.")
+ .form-group
+ = f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold'
+ = f.number_field :max_artifacts_size, class: 'form-control gl-form-input'
.form-text.text-muted
- = s_('AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire.')
- .form-group
- = f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
- = f.text_field :archive_builds_in_human_readable, class: 'form-control gl-form-input'
- .form-text.text-muted
- = html_escape(_("Jobs older than the configured time are considered expired and are archived. Archived jobs can no longer be retried. Leave empty to never archive jobs automatically. The default unit is in days, but you can use other units, for example %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}. Minimum value is 1 day.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'archive-jobs')
- .form-group
- .form-check
- = f.check_box :protected_ci_variables, class: 'form-check-input'
- = f.label :protected_ci_variables, class: 'form-check-label' do
- = s_('AdminSettings|Protect CI/CD variables by default')
+ = _("The maximum file size for job artifacts.")
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
+ .form-group
+ = f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold'
+ = f.text_field :default_artifacts_expire_in, class: 'form-control gl-form-input'
.form-text.text-muted
- = s_('AdminSettings|New CI/CD variables in projects and groups default to protected.')
- .form-group
- = f.label :ci_config_path, _('Default CI/CD configuration file'), class: 'label-bold'
- = f.text_field :default_ci_config_path, class: 'form-control gl-form-input', placeholder: '.gitlab-ci.yml'
- %p.form-text.text-muted
- = _("The default CI/CD configuration file and path for new projects.").html_safe
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank', rel: 'noopener noreferrer'
- .form-group
- .form-check
- = f.check_box :suggest_pipeline_enabled, class: 'form-check-input'
- = f.label :suggest_pipeline_enabled, class: 'form-check-label' do
- = s_('AdminSettings|Enable pipeline suggestion banner')
+ = html_escape(_("Set the default expiration time for job artifacts in all projects. Set to %{code_open}0%{code_close} to never expire artifacts by default. If no unit is written, it defaults to seconds. For example, these are all equivalent: %{code_open}3600%{code_close}, %{code_open}60 minutes%{code_close}, or %{code_open}one hour%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
+ .form-group
+ .form-check
+ = f.check_box :keep_latest_artifact, class: 'form-check-input'
+ = f.label :keep_latest_artifact, class: 'form-check-label' do
+ = s_('AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines')
+ .form-text.text-muted
+ = s_('AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire.')
+ .form-group
+ = f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
+ = f.text_field :archive_builds_in_human_readable, class: 'form-control gl-form-input'
.form-text.text-muted
- = s_('AdminSettings|Display a banner on merge requests in projects with no pipelines to initiate steps to add a .gitlab-ci.yml file.')
+ = html_escape(_("Jobs older than the configured time are considered expired and are archived. Archived jobs can no longer be retried. Leave empty to never archive jobs automatically. The default unit is in days, but you can use other units, for example %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}. Minimum value is 1 day.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'archive-jobs')
+ .form-group
+ .form-check
+ = f.check_box :protected_ci_variables, class: 'form-check-input'
+ = f.label :protected_ci_variables, class: 'form-check-label' do
+ = s_('AdminSettings|Protect CI/CD variables by default')
+ .form-text.text-muted
+ = s_('AdminSettings|New CI/CD variables in projects and groups default to protected.')
+ .form-group
+ = f.label :ci_config_path, _('Default CI/CD configuration file'), class: 'label-bold'
+ = f.text_field :default_ci_config_path, class: 'form-control gl-form-input', placeholder: '.gitlab-ci.yml'
+ %p.form-text.text-muted
+ = _("The default CI/CD configuration file and path for new projects.").html_safe
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank', rel: 'noopener noreferrer'
+ .form-group
+ .form-check
+ = f.check_box :suggest_pipeline_enabled, class: 'form-check-input'
+ = f.label :suggest_pipeline_enabled, class: 'form-check-label' do
+ = s_('AdminSettings|Enable pipeline suggestion banner')
+ .form-text.text-muted
+ = s_('AdminSettings|Display a banner on merge requests in projects with no pipelines to initiate steps to add a .gitlab-ci.yml file.')
+
+ = f.submit _('Save changes'), class: "gl-button btn btn-confirm"
- = f.submit _('Save changes'), class: "gl-button btn btn-confirm"
+.settings-content
+ %h4
+ = s_('AdminSettings|CI/CD limits')
+ %p
+ = s_('AdminSettings|Set limit to 0 to disable it.')
+ .scrolling-tabs-container.inner-page-scroll-tabs
+ - if @plans.size > 1
+ %ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs.gl-mb-5
+ - @plans.each_with_index do |plan, index|
+ %li
+ = link_to admin_plan_limits_path(anchor: 'js-ci-cd-settings'), data: { target: "div#plan#{index}", action: "plan#{index}", toggle: 'tab'}, class: index == 0 ? 'active': '' do
+ = plan.name.capitalize
+ .tab-content.gl-tab-content
+ - @plans.each_with_index do |plan, index|
+ .tab-pane{ :id => "plan#{index}", class: index == 0 ? 'active': '' }
+ = form_for plan.actual_limits, url: admin_plan_limits_path(anchor: 'js-ci-cd-settings'), html: { class: 'fieldset-form' }, method: :post do |f|
+ = form_errors(plan)
+ %fieldset
+ = f.hidden_field(:plan_id, value: plan.id)
+ .form-group
+ = f.label :ci_pipeline_size, s_('AdminSettings|Maximum number of jobs in a single pipeline')
+ = f.number_field :ci_pipeline_size, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :ci_active_jobs, s_('AdminSettings|Total number of jobs in currently active pipelines')
+ = f.number_field :ci_active_jobs, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :ci_project_subscriptions, s_('AdminSettings|Maximum number of pipeline subscriptions to and from a project')
+ = f.number_field :ci_project_subscriptions, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :ci_pipeline_schedules, s_('AdminSettings|Maximum number of pipeline schedules')
+ = f.number_field :ci_pipeline_schedules, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :ci_needs_size_limit, s_('AdminSettings|Maximum number of DAG dependencies that a job can have')
+ = f.number_field :ci_needs_size_limit, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :ci_registered_group_runners, s_('AdminSettings|Maximum number of runners registered per group')
+ = f.number_field :ci_registered_group_runners, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :ci_registered_project_runners, s_('AdminSettings|Maximum number of runners registered per project')
+ = f.number_field :ci_registered_project_runners, class: 'form-control gl-form-input'
+ = f.submit s_('AdminSettings|Save %{name} limits').html_safe % { name: plan.name.capitalize }, class: 'btn gl-button btn-confirm'
diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml
index 762dba69e6a..aab4f44d4d7 100644
--- a/app/views/admin/application_settings/ci_cd.html.haml
+++ b/app/views/admin/application_settings/ci_cd.html.haml
@@ -20,8 +20,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Customize CI/CD settings, including Auto DevOps, shared runners, and job artifacts.')
- .settings-content
- = render 'ci_cd'
+ = render 'ci_cd'
= render_if_exists 'admin/application_settings/required_instance_ci_setting', expanded: expanded_by_default?
diff --git a/app/workers/container_registry/migration/guard_worker.rb b/app/workers/container_registry/migration/guard_worker.rb
index 77ae111c1cb..bab6b8c2a72 100644
--- a/app/workers/container_registry/migration/guard_worker.rb
+++ b/app/workers/container_registry/migration/guard_worker.rb
@@ -29,46 +29,45 @@ module ContainerRegistry
log_extra_metadata_on_done(:stale_migrations_count, repositories.to_a.size)
repositories.each do |repository|
- if abortable?(repository)
+ if actively_importing?(repository)
+ # if a repository is actively importing but not yet long_running, do nothing
+ if long_running_migration?(repository)
+ long_running_migration_ids << repository.id
+ cancel_long_running_migration(repository)
+ aborts_count += 1
+ end
+ else
repository.abort_import
aborts_count += 1
- else
- long_running_migration_ids << repository.id if long_running_migration?(repository)
end
end
log_extra_metadata_on_done(:aborted_stale_migrations_count, aborts_count)
if long_running_migration_ids.any?
- log_extra_metadata_on_done(:long_running_stale_migration_container_repository_ids, long_running_migration_ids)
+ log_extra_metadata_on_done(:aborted_long_running_migration_ids, long_running_migration_ids)
end
end
private
- # This can ping the Container Registry API.
- # We loop on a set of repositories to calls this function (see #perform)
- # In the worst case scenario, we have a n+1 API calls situation here.
- #
- # This is reasonable because the maximum amount of repositories looped
- # on is `25`. See ::ContainerRegistry::Migration.capacity.
- #
- # TODO We can remove this n+1 situation by having a Container Registry API
- # endpoint that accepts multiple repository paths at once. This is issue
+ # A repository is actively_importing if it has an importing migration state
+ # and that state matches the state in the registry
+ # TODO We can have an API call n+1 situation here. It can be solved when the
+ # endpoint accepts multiple repository paths at once. This is issue
# https://gitlab.com/gitlab-org/container-registry/-/issues/582
- def abortable?(repository)
- # early return to save one Container Registry API request
- return true unless repository.importing? || repository.pre_importing?
- return true unless external_migration_in_progress?(repository)
+ def actively_importing?(repository)
+ return false unless repository.importing? || repository.pre_importing?
+ return false unless external_state_matches_migration_state?(repository)
- false
+ true
end
def long_running_migration?(repository)
migration_start_timestamp(repository).before?(long_running_migration_threshold)
end
- def external_migration_in_progress?(repository)
+ def external_state_matches_migration_state?(repository)
status = repository.external_import_status
(status == 'pre_import_in_progress' && repository.pre_importing?) ||
@@ -96,6 +95,21 @@ module ContainerRegistry
def long_running_migration_threshold
@threshold ||= 30.minutes.ago
end
+
+ def cancel_long_running_migration(repository)
+ result = repository.migration_cancel
+
+ case result[:status]
+ when :ok
+ repository.skip_import(reason: :migration_canceled)
+ when :bad_request
+ repository.reconcile_import_status(result[:state]) do
+ repository.abort_import
+ end
+ else
+ repository.abort_import
+ end
+ end
end
end
end
diff --git a/app/workers/database/batched_background_migration/ci_database_worker.rb b/app/workers/database/batched_background_migration/ci_database_worker.rb
index 98ec6f98123..13314cf95e2 100644
--- a/app/workers/database/batched_background_migration/ci_database_worker.rb
+++ b/app/workers/database/batched_background_migration/ci_database_worker.rb
@@ -4,6 +4,10 @@ module Database
class CiDatabaseWorker # rubocop:disable Scalability/IdempotentWorker
include SingleDatabaseWorker
+ def self.enabled?
+ Feature.enabled?(:execute_batched_migrations_on_schedule_ci_database, type: :ops, default_enabled: :yaml)
+ end
+
def self.tracking_database
@tracking_database ||= Gitlab::Database::CI_DATABASE_NAME
end
diff --git a/app/workers/database/batched_background_migration/single_database_worker.rb b/app/workers/database/batched_background_migration/single_database_worker.rb
index 78c82a6549f..aeadda4b8e1 100644
--- a/app/workers/database/batched_background_migration/single_database_worker.rb
+++ b/app/workers/database/batched_background_migration/single_database_worker.rb
@@ -23,6 +23,10 @@ module Database
def tracking_database
raise NotImplementedError, "#{self.name} does not implement #{__method__}"
end
+
+ def enabled?
+ raise NotImplementedError, "#{self.name} does not implement #{__method__}"
+ end
# :nocov:
def lease_key
@@ -41,7 +45,7 @@ module Database
end
Gitlab::Database::SharedModel.using_connection(base_model.connection) do
- break unless Feature.enabled?(:execute_batched_migrations_on_schedule, type: :ops, default_enabled: :yaml) && active_migration
+ break unless self.class.enabled? && active_migration
with_exclusive_lease(active_migration.interval) do
# Now that we have the exclusive lease, reload migration in case another process has changed it.
diff --git a/app/workers/database/batched_background_migration_worker.rb b/app/workers/database/batched_background_migration_worker.rb
index 29804be832d..6a41fe70915 100644
--- a/app/workers/database/batched_background_migration_worker.rb
+++ b/app/workers/database/batched_background_migration_worker.rb
@@ -4,6 +4,10 @@ module Database
class BatchedBackgroundMigrationWorker # rubocop:disable Scalability/IdempotentWorker
include BatchedBackgroundMigration::SingleDatabaseWorker
+ def self.enabled?
+ Feature.enabled?(:execute_batched_migrations_on_schedule, type: :ops, default_enabled: :yaml)
+ end
+
def self.tracking_database
@tracking_database ||= Gitlab::Database::MAIN_DATABASE_NAME.to_sym
end
diff --git a/config/feature_flags/development/track_error_tracking_activity.yml b/config/feature_flags/development/track_error_tracking_activity.yml
deleted file mode 100644
index c21a8d1aede..00000000000
--- a/config/feature_flags/development/track_error_tracking_activity.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: track_error_tracking_activity
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82543
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/355112
-milestone: '14.9'
-type: development
-group: group::respond
-default_enabled: false
diff --git a/config/feature_flags/ops/execute_batched_migrations_on_schedule_ci_database.yml b/config/feature_flags/ops/execute_batched_migrations_on_schedule_ci_database.yml
new file mode 100644
index 00000000000..9efbbd014a1
--- /dev/null
+++ b/config/feature_flags/ops/execute_batched_migrations_on_schedule_ci_database.yml
@@ -0,0 +1,8 @@
+---
+name: execute_batched_migrations_on_schedule_ci_database
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83109
+rollout_issue_url:
+milestone: '14.9'
+type: ops
+group: group::database
+default_enabled: false
diff --git a/db/migrate/20220316202200_add_migration_plan_to_container_repositories.rb b/db/migrate/20220316202200_add_migration_plan_to_container_repositories.rb
new file mode 100644
index 00000000000..baeff52542f
--- /dev/null
+++ b/db/migrate/20220316202200_add_migration_plan_to_container_repositories.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddMigrationPlanToContainerRepositories < Gitlab::Database::Migration[1.0]
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20220316202402_add_text_limit_to_container_repositories_migration_plan
+ def change
+ add_column(:container_repositories, :migration_plan, :text)
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20220316202402_add_text_limit_to_container_repositories_migration_plan.rb b/db/migrate/20220316202402_add_text_limit_to_container_repositories_migration_plan.rb
new file mode 100644
index 00000000000..a9f653c742f
--- /dev/null
+++ b/db/migrate/20220316202402_add_text_limit_to_container_repositories_migration_plan.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddTextLimitToContainerRepositoriesMigrationPlan < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :container_repositories, :migration_plan, 255
+ end
+
+ def down
+ remove_text_limit :container_repositories, :migration_plan
+ end
+end
diff --git a/db/post_migrate/20220316202640_populate_container_repositories_migration_plan.rb b/db/post_migrate/20220316202640_populate_container_repositories_migration_plan.rb
new file mode 100644
index 00000000000..7eef227ec3c
--- /dev/null
+++ b/db/post_migrate/20220316202640_populate_container_repositories_migration_plan.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class PopulateContainerRepositoriesMigrationPlan < Gitlab::Database::Migration[1.0]
+ MIGRATION = 'PopulateContainerRepositoryMigrationPlan'
+ DELAY_INTERVAL = 2.minutes.to_i
+ BATCH_SIZE = 1500
+
+ disable_ddl_transaction!
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ define_batchable_model('container_repositories'),
+ MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE,
+ track_jobs: true
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/schema_migrations/20220316202200 b/db/schema_migrations/20220316202200
new file mode 100644
index 00000000000..62c236a6b67
--- /dev/null
+++ b/db/schema_migrations/20220316202200
@@ -0,0 +1 @@
+28722182d4ead079c8c0c36a5c075a29c5da93b4315af277311f561fc945d65f \ No newline at end of file
diff --git a/db/schema_migrations/20220316202402 b/db/schema_migrations/20220316202402
new file mode 100644
index 00000000000..b450772ac80
--- /dev/null
+++ b/db/schema_migrations/20220316202402
@@ -0,0 +1 @@
+006ef4f0c63e39e3874d39528ca6cfd233c17d6256d07bd2c0e7b2262d0a4825 \ No newline at end of file
diff --git a/db/schema_migrations/20220316202640 b/db/schema_migrations/20220316202640
new file mode 100644
index 00000000000..dad19b94335
--- /dev/null
+++ b/db/schema_migrations/20220316202640
@@ -0,0 +1 @@
+7ea2288c45d497e2fde56b1d7c1e82360ed40442a2501a24e9795380adf5b911 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 46398ed3f80..992319411b1 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -13724,6 +13724,8 @@ CREATE TABLE container_repositories (
migration_skipped_reason smallint,
migration_state text DEFAULT 'default'::text NOT NULL,
migration_aborted_in_state text,
+ migration_plan text,
+ CONSTRAINT check_05e9012f36 CHECK ((char_length(migration_plan) <= 255)),
CONSTRAINT check_13c58fe73a CHECK ((char_length(migration_state) <= 255)),
CONSTRAINT check_97f0249439 CHECK ((char_length(migration_aborted_in_state) <= 255))
);
diff --git a/doc/administration/clusters/kas.md b/doc/administration/clusters/kas.md
index e5c371b9d40..82bb1a35e02 100644
--- a/doc/administration/clusters/kas.md
+++ b/doc/administration/clusters/kas.md
@@ -116,7 +116,7 @@ If you get the following error message:
The project specified by the manifest (`root/kas-manifest001`)
doesn't exist or the project where the manifest is kept is private. To fix this issue,
-ensure the project path is correct and that the project's visibility is [set to public](../../public_access/public_access.md).
+ensure the project path is correct and that the project's visibility is [set to public](../../user/public_access.md).
### Configuration file not found
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 5b8de40e490..ec586b22bea 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -146,7 +146,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Issue closing pattern](issue_closing_pattern.md): Customize how to close an issue from commit messages.
- [Gitaly](gitaly/index.md): Configuring Gitaly, the Git repository storage service for GitLab.
- [Default labels](../user/admin_area/labels.md): Create labels that are automatically added to every new project.
-- [Restrict the use of public or internal projects](../public_access/public_access.md#restrict-use-of-public-or-internal-projects): Restrict the use of visibility levels for users when they create a project or a snippet.
+- [Restrict the use of public or internal projects](../user/public_access.md#restrict-use-of-public-or-internal-projects): Restrict the use of visibility levels for users when they create a project or a snippet.
- [Custom project templates](../user/admin_area/custom_project_templates.md): Configure a set of projects to be used as custom templates when creating a new project.
## Package Registry administration
diff --git a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
index 09cef51d14c..c8634a04f8d 100644
--- a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
+++ b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
@@ -19,7 +19,7 @@ GitLab provides administrators insights into the health of their GitLab instance
To provide a native experience (similar interacting with an application deployed using GitLab), a
project called **Monitoring** is created:
-- With [internal visibility](../../../public_access/public_access.md#internal-projects-and-groups).
+- With [internal visibility](../../../user/public_access.md#internal-projects-and-groups).
- Under a group called **GitLab Instance**.
The project is created specifically for visualizing and configuring the monitoring of your GitLab
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index a1bfae678c7..08f7d5095da 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -88,7 +88,6 @@ The following metrics are available:
| `gitlab_transaction_event_stuck_import_jobs_total` | Counter | 9.4 | Count of stuck import jobs | `projects_without_jid_count`, `projects_with_jid_count` |
| `gitlab_transaction_event_update_build_total` | Counter | 9.4 | Counter for update build for API `/jobs/request/:id` | |
| `gitlab_transaction_new_redis_connections_total` | Counter | 9.4 | Counter for new Redis connections | |
-| `gitlab_transaction_queue_duration_total` | Counter | 9.4 | Duration jobs were enqueued before processing | |
| `gitlab_transaction_rails_queue_duration_total` | Counter | 9.4 | Measures latency between GitLab Workhorse forwarding a request to Rails | `controller`, `action` |
| `gitlab_transaction_view_duration_total` | Counter | 9.4 | Duration for views | `controller`, `action`, `view` |
| `gitlab_view_rendering_duration_seconds` | Histogram | 10.2 | Duration for views (histogram) | `controller`, `action`, `view` |
diff --git a/doc/api/projects.md b/doc/api/projects.md
index ee649377745..3ca4125a2fa 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -19,7 +19,7 @@ Values for the project visibility level are:
- `internal`: the project can be cloned by any signed-in user except [external users](../user/permissions.md#external-users).
- `public`: the project can be accessed without any authentication.
-For more, read [Project visibility](../public_access/public_access.md).
+For more, read [Project visibility](../user/public_access.md).
## Project merge method
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 7437860239e..d9f50fd3ce2 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -48,7 +48,7 @@ GET /runners?tag_list=tag1,tag2
|------------|--------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
-| `status` | string | no | The status of runners to show, one of: `online` and `offline`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
+| `status` | string | no | The status of runners to show, one of: `online`, `offline` and `not_connected`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
diff --git a/doc/ci/pipelines/merge_request_pipelines.md b/doc/ci/pipelines/merge_request_pipelines.md
index d80b745e6bc..b6ebc2bf283 100644
--- a/doc/ci/pipelines/merge_request_pipelines.md
+++ b/doc/ci/pipelines/merge_request_pipelines.md
@@ -147,7 +147,7 @@ a warning that you must accept before you can trigger the pipeline.
Prerequisites:
- You must be a member of the parent project and have at least the [Developer role](../../user/permissions.md).
-- The fork project must be [visible](../../public_access/public_access.md) to the
+- The fork project must be [visible](../../user/public_access.md) to the
user running the pipeline. Otherwise, the **Pipelines** tab does not display
in the merge request.
diff --git a/doc/ci/pipelines/multi_project_pipelines.md b/doc/ci/pipelines/multi_project_pipelines.md
index 2163527e3ca..e6af9292fe1 100644
--- a/doc/ci/pipelines/multi_project_pipelines.md
+++ b/doc/ci/pipelines/multi_project_pipelines.md
@@ -282,7 +282,7 @@ tag in a different project.
Prerequisites:
-- The upstream project must be [public](../../public_access/public_access.md).
+- The upstream project must be [public](../../user/public_access.md).
- The user must have the Developer role
in the upstream project.
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 7c9777527ef..afc04045763 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -20,7 +20,7 @@ are very appreciative of the work done by translators and proofreaders!
- Arabic
- Proofreaders needed.
- Bosnian
- - Proofreaders needed.
+ - Haris Delalić - [GitLab](https://gitlab.com/haris.delalic), [Crowdin](https://crowdin.com/profile/haris.delalic)
- Bulgarian
- Lyubomir Vasilev - [Crowdin](https://crowdin.com/profile/lyubomirv)
- Catalan
@@ -38,7 +38,7 @@ are very appreciative of the work done by translators and proofreaders!
- Victor Wu - [GitLab](https://gitlab.com/_victorwu_), [Crowdin](https://crowdin.com/profile/victorwu)
- Ivan Ip - [GitLab](https://gitlab.com/lifehome), [Crowdin](https://crowdin.com/profile/lifehome)
- Croatian
- - Proofreaders needed.
+ - Haris Delalić - [GitLab](https://gitlab.com/haris.delalic), [Crowdin](https://crowdin.com/profile/haris.delalic)
- Czech
- Jan Urbanec - [GitLab](https://gitlab.com/TatranskyMedved), [Crowdin](https://crowdin.com/profile/Tatranskymedved)
- Danish
@@ -111,7 +111,7 @@ are very appreciative of the work done by translators and proofreaders!
- Andrey Komarov - [GitLab](https://gitlab.com/elkamarado), [Crowdin](https://crowdin.com/profile/kamarado)
- Iaroslav Postovalov - [GitLab](https://gitlab.com/CMDR_Tvis), [Crowdin](https://crowdin.com/profile/CMDR_Tvis)
- Serbian (Latin and Cyrillic)
- - Proofreaders needed.
+ - Haris Delalić - [GitLab](https://gitlab.com/haris.delalic), [Crowdin](https://crowdin.com/profile/haris.delalic)
- Sinhalese/Sinhala සිංහල
- හෙළබස (HelaBasa) - [GitLab](https://gitlab.com/helabasa), [Crowdin](https://crowdin.com/profile/helabasa)
- Slovak
diff --git a/doc/integration/jira/development_panel.md b/doc/integration/jira/development_panel.md
index 66810945d19..2f0ebea165a 100644
--- a/doc/integration/jira/development_panel.md
+++ b/doc/integration/jira/development_panel.md
@@ -4,25 +4,25 @@ group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# GitLab Jira Development panel integration **(FREE)**
+# GitLab Jira development panel integration **(FREE)**
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/233149) from GitLab Premium to GitLab Free in 13.4.
-With the Jira Development panel integration, you can reference Jira issues in GitLab.
+With the Jira development panel integration, you can reference Jira issues in GitLab.
When configured, activity (such as pipeline, deployment, and feature flags) displays in the Jira issue's
-[Development panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/).
-From the Development panel, you can open a detailed view and
+[development panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/).
+From the development panel, you can open a detailed view and
[take various actions](#use-the-integration), including creating a new merge request from a branch:
![Branch, Commit and Pull Requests links on Jira issue](img/jira_dev_panel_jira_setup_3.png)
-The information displayed in the Jira Development panel depends on where you mention the Jira issue ID:
+The information displayed in the Jira development panel depends on where you mention the Jira issue ID:
| Your mention of Jira issue ID in GitLab context | Automated effect in Jira issue |
|---------------------------------------------------|--------------------------------------------------------------------------------------------------------|
-| In a merge request title or description | Link to the MR is displayed in Development panel. |
-| In a branch name | Link to the branch is displayed in Development panel. |
-| In a commit message | Link to the commit is displayed in Development panel. |
+| In a merge request title or description | Link to the MR is displayed in the development panel. |
+| In a branch name | Link to the branch is displayed in the development panel. |
+| In a commit message | Link to the commit is displayed in the development panel. |
| In a commit message with Jira [Smart Commits](https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html) | Displays your custom comment or logged time spent and/or performs specified issue transition on merge. |
This integration connects all GitLab projects to projects in the Jira instance in either:
@@ -61,8 +61,8 @@ an issue transition, or add a custom comment, read the Atlassian page
## Configure the integration
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
-For an overview of how to configure Jira Development panel integration, see
-[Agile Management - GitLab Jira Development panel integration](https://www.youtube.com/watch?v=VjVTOmMl85M).
+For an overview of how to configure the Jira development panel integration, see
+[Agile Management - GitLab Jira development panel integration](https://www.youtube.com/watch?v=VjVTOmMl85M).
To simplify administration, we recommend that a GitLab group maintainer or group owner
(or, if possible, instance administrator in the case of self-managed GitLab) set up the integration.
@@ -89,7 +89,7 @@ This integration is not supported on GitLab instances under a
[relative URL](https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab).
For example, `http://example.com/gitlab`.
-## Troubleshoot the Development Panel
+## Troubleshoot the development panel
If you use Jira on your own server, go to the [Atlassian documentation](https://confluence.atlassian.com/jirakb/troubleshoot-the-development-panel-in-jira-server-574685212.html)
for general troubleshooting information.
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 7a4bcba25e4..cba07e505de 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -197,7 +197,7 @@ To enable automatic linking for SAML, see the [SAML setup instructions](saml.md#
## Create an external providers list
You can define a list of external OmniAuth providers.
-Users who create accounts or sign in to GitLab through the listed providers do not get access to [internal projects](../public_access/public_access.md#internal-projects-and-groups).
+Users who create accounts or sign in to GitLab through the listed providers do not get access to [internal projects](../user/public_access.md#internal-projects-and-groups).
To define the external providers list, use the full name of the provider,
for example, `google_oauth2` for Google. For provider names, see the
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 8eee8fc9a6b..8b665972918 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -1,98 +1,11 @@
---
-stage: Manage
-group: Workspace
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
-type: reference
+redirect_to: '../user/public_access.md'
+remove_date: '2022-06-18'
---
-# Project and group visibility **(FREE)**
+This document was moved to [another location](../user/public_access.md).
-GitLab allows users with the Owner role to set a project's or group's visibility as:
-
-- **Public**
-- **Internal**
-- **Private**
-
-These visibility levels affect who can see the project in the public access directory (`/public`
-for your GitLab instance). For example, <https://gitlab.com/public>.
-You can control the visibility of individual features with
-[project feature settings](../user/permissions.md#project-features).
-
-## Public projects and groups
-
-Public projects can be cloned **without any** authentication over HTTPS.
-
-They are listed in the public access directory (`/public`) for all users.
-
-**Any signed-in user** has the Guest role on the repository.
-
-NOTE:
-By default, `/public` is visible to unauthenticated users. However, if the
-[**Public** visibility level](../user/admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels)
-is restricted, `/public` is visible only to signed-in users.
-
-## Internal projects and groups
-
-Internal projects can be cloned by any signed-in user except
-[external users](../user/permissions.md#external-users).
-
-They are also listed in the public access directory (`/public`), but only for signed-in users.
-
-Any signed-in users except [external users](../user/permissions.md#external-users) have the
-Guest role on the repository.
-
-NOTE:
-From July 2019, the `Internal` visibility setting is disabled for new projects, groups,
-and snippets on GitLab.com. Existing projects, groups, and snippets using the `Internal`
-visibility setting keep this setting. You can read more about the change in the
-[relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/12388).
-
-## Private projects and groups
-
-Private projects can only be cloned and viewed by project members (except for guests).
-
-They appear in the public access directory (`/public`) for project members only.
-
-## Change project visibility
-
-Prerequisite:
-
-- You must have the Owner role for a project.
-
-1. On the top bar, select **Menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > General**.
-1. Expand **Visibility, project features, permissions**.
-1. Change **Project visibility** to either **Private**, **Internal**, or **Public**.
-1. Select **Save changes**.
-
-## Change group visibility
-
-Prerequisite:
-
-- You must have the Owner role for a group.
-
-1. On the top bar, select **Menu > Groups** and find your project.
-1. On the left sidebar, select **Settings > General**.
-1. Expand **Naming, visibility**.
-1. Under **Visibility level** select either **Private**, **Internal**, or **Public**.
-1. Select **Save changes**.
-
-## Restrict use of public or internal projects **(FREE SELF)**
-
-You can restrict the use of visibility levels for users when they create a project or a snippet.
-This is useful to prevent users from publicly exposing their repositories by accident. The
-restricted visibility settings do not apply to administrators.
-
-For details, see [Restricted visibility levels](../user/admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels).
-
-<!-- ## Troubleshooting
-
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
-
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+<!-- This redirect file can be deleted after <2022-06-18>. -->
+<!-- Redirects that point to other docs in the same project expire in three months. -->
+<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
index 2165dc54899..3eae0d0ff90 100644
--- a/doc/user/admin_area/settings/visibility_and_access_controls.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -87,7 +87,7 @@ Alternatively, projects that are marked for removal can be deleted immediately.
## Configure project visibility defaults
-To set the default [visibility levels for new projects](../../../public_access/public_access.md):
+To set the default [visibility levels for new projects](../../public_access.md):
1. Sign in to GitLab as a user with Administrator access level.
1. On the top bar, select **Menu > Admin**.
@@ -112,7 +112,7 @@ To set the default visibility levels for new [snippets](../../snippets.md):
1. Select **Save changes**.
For more details on snippet visibility, read
-[Project visibility](../../../public_access/public_access.md).
+[Project visibility](../../public_access.md).
## Configure group visibility defaults
@@ -146,7 +146,7 @@ To restrict visibility levels for projects, snippets, and selected pages:
1. Select **Save changes**.
For more details on project visibility, see
-[Project visibility](../../../public_access/public_access.md).
+[Project visibility](../../public_access.md).
## Configure allowed import sources
@@ -162,7 +162,7 @@ You can specify from which hosting sites users can [import their projects](../..
## Enable project export
To enable the export of
-[projects and their data](../../../user/project/settings/import_export.md#export-a-project-and-its-data):
+[projects and their data](../../project/settings/import_export.md#export-a-project-and-its-data):
1. Sign in to GitLab as a user with Administrator access level.
1. On the top bar, select **Menu > Admin**.
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 5cf6a505bee..7baf9382e66 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -395,7 +395,7 @@ doesn't return the following headers:
### Visibility settings
If created before GitLab 12.2 (July 2019), these items have the
-[Internal visibility](../../public_access/public_access.md#internal-projects-and-groups)
+[Internal visibility](../public_access.md#internal-projects-and-groups)
setting [disabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/12388):
- Projects
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 4b9ff7f64e8..6d0957d67f9 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -68,7 +68,7 @@ To create a group:
1. Enter a name for the group in **Group name**. For a list of words that cannot be used as group names, see
[reserved names](../reserved_names.md).
1. Enter a path for the group in **Group URL**, which is used for the [namespace](#namespaces).
-1. Choose the [visibility level](../../public_access/public_access.md).
+1. Choose the [visibility level](../public_access.md).
1. Personalize your GitLab experience by answering the following questions:
- What is your role?
- Who will be using this group?
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index c90f575e83a..ee081bb0fa8 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -149,7 +149,7 @@ The following table lists project permissions available for each role:
| [Projects](project/index.md):<br>View project [Audit Events](../administration/audit_events.md) | | | ✓ (*10*) | ✓ | ✓ |
| [Projects](project/index.md):<br>Add deploy keys | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Add new team members | | | | ✓ | ✓ |
-| [Projects](project/index.md):<br>Change [project features visibility](../public_access/public_access.md) level | | | | ✓ (*13*) | ✓ |
+| [Projects](project/index.md):<br>Change [project features visibility](public_access.md) level | | | | ✓ (*13*) | ✓ |
| [Projects](project/index.md):<br>Configure [webhooks](project/integrations/webhooks.md) | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Delete [wiki](project/wiki/index.md) pages | | | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>Edit comments (posted by any user) | | | | ✓ | ✓ |
@@ -224,7 +224,7 @@ The following table lists project permissions available for each role:
supported on GitLab SaaS Premium and above (excluding [trial licenses](https://about.gitlab.com/free-trial/)).
12. If the [tag is protected](#release-permissions-with-protected-tags), this depends on the access Developers and Maintainers are given.
13. A Maintainer can't change project features visibility level if
- [project visibility](../public_access/public_access.md) is set to private.
+ [project visibility](public_access.md) is set to private.
14. Attached design files are moved together with the issue even if the user doesn't have the
Developer role.
15. Guest users can only set metadata (for example, labels, assignees, or milestones)
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index 9f1c049045c..329d91916e8 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -30,7 +30,7 @@ The following aspects of a project are imported:
References to pull requests and issues are preserved (GitLab.com & 8.7+), and
each imported repository maintains visibility level unless that [visibility
-level is restricted](../../../public_access/public_access.md#restrict-use-of-public-or-internal-projects),
+level is restricted](../../public_access.md#restrict-use-of-public-or-internal-projects),
in which case it defaults to the default project visibility.
The namespace is a user or group in GitLab, such as `gitlab.com/janedoe` or
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 801c2520bda..455b5405ef6 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -11,7 +11,7 @@ your codebase. You can also use projects to track issues, plan work,
collaborate on code, and continuously build, test, and use
built-in CI/CD to deploy your app.
-Projects can be available [publicly, internally, or privately](../../public_access/public_access.md).
+Projects can be available [publicly, internally, or privately](../public_access.md).
GitLab does not limit the number of private projects you can create.
## Project features
diff --git a/doc/user/project/repository/forking_workflow.md b/doc/user/project/repository/forking_workflow.md
index ddeef65a6b5..0e6c98457c7 100644
--- a/doc/user/project/repository/forking_workflow.md
+++ b/doc/user/project/repository/forking_workflow.md
@@ -32,7 +32,7 @@ To fork an existing project in GitLab:
It must be unique in the namespace.
1. Optional. Add a **Project description**.
1. Select the **Visibility level** for your fork. For more information about
- visibility levels, read [Project and group visibility](../../../public_access/public_access.md).
+ visibility levels, read [Project and group visibility](../../public_access.md).
1. Select **Fork project**.
GitLab creates your fork, and redirects you to the new fork's page.
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index ae3decb8079..3b5e847e6cb 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -127,7 +127,7 @@ The following items are imported but changed slightly:
associated with such merge requests are created in a project during the import/export. Thus, the
number of branches in the exported project might be bigger than in the original project.
- If use of the `Internal` visibility level
- [is restricted](../../../public_access/public_access.md#restrict-use-of-public-or-internal-projects),
+ [is restricted](../../public_access.md#restrict-use-of-public-or-internal-projects),
all imported projects are given `Private` visibility.
Deploy keys aren't imported. To use deploy keys, you must enable them in your imported project and update protected branches.
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 342b8d80bcf..9ef6dcdd9a2 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -242,7 +242,7 @@ documentation, access permissions, and more. To do so from your project,
go to **Settings** > **General**, and expand the **Visibility, project features, permissions**
section.
-You can now change the [Project visibility](../../../public_access/public_access.md).
+You can now change the [Project visibility](../../public_access.md).
If you set **Project Visibility** to public, you can limit access to some features
to **Only Project Members**. In addition, you can select the option to
[Allow users to request access](../members/index.md#request-access-to-a-project).
diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md
index 19e77d18aca..f1934cfd680 100644
--- a/doc/user/project/working_with_projects.md
+++ b/doc/user/project/working_with_projects.md
@@ -99,7 +99,7 @@ To create a blank project:
- In the **Project description (optional)** field, enter the description of your project's dashboard.
- In the **Project target (optional)** field, select your project's deployment target.
This information helps GitLab better understand its users and their deployment requirements.
- - To modify the project's [viewing and access rights](../../public_access/public_access.md) for
+ - To modify the project's [viewing and access rights](../public_access.md) for
users, change the **Visibility Level**.
- To create README file so that the Git repository is initialized, has a default branch, and
can be cloned, select **Initialize repository with a README**.
@@ -132,7 +132,7 @@ To create a project from a built-in template:
slug as the URL path to the project. To change the slug, first enter the project name,
then change the slug.
- In the **Project description (optional)** field, enter the description of your project's dashboard.
- - To modify the project's [viewing and access rights](../../public_access/public_access.md) for users,
+ - To modify the project's [viewing and access rights](../public_access.md) for users,
change the **Visibility Level**.
1. Select **Create project**.
@@ -158,7 +158,7 @@ Custom project templates are available at:
slug as the URL path to the project. To change the slug, first enter the project name,
then change the slug.
- The description of your project's dashboard in the **Project description (optional)** field.
- - To modify the project's [viewing and access rights](../../public_access/public_access.md) for users,
+ - To modify the project's [viewing and access rights](../public_access.md) for users,
change the **Visibility Level**.
1. Select **Create project**.
@@ -184,7 +184,7 @@ To create a project from the HIPAA Audit Protocol template:
slug as the URL path to the project. To change the slug, first enter the project name,
then change the slug.
- In the **Project description (optional)** field, enter the description of your project's dashboard.
- - To modify the project's [viewing and access rights](../../public_access/public_access.md) for users,
+ - To modify the project's [viewing and access rights](../public_access.md) for users,
change the **Visibility Level**.
1. Select **Create project**.
@@ -250,7 +250,7 @@ remote: The private project namespace/myproject was created.
To view your new project, go to `https://gitlab.example.com/namespace/myproject`.
Your project's visibility is set to **Private** by default. To change project visibility, adjust your
-[project's settings](../../public_access/public_access.md#change-project-visibility).
+[project's settings](../public_access.md#change-project-visibility).
## Star a project
diff --git a/doc/user/public_access.md b/doc/user/public_access.md
new file mode 100644
index 00000000000..cca753a2830
--- /dev/null
+++ b/doc/user/public_access.md
@@ -0,0 +1,98 @@
+---
+stage: Manage
+group: Workspace
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+type: reference
+---
+
+# Project and group visibility **(FREE)**
+
+GitLab allows users with the Owner role to set a project's or group's visibility as:
+
+- **Public**
+- **Internal**
+- **Private**
+
+These visibility levels affect who can see the project in the public access directory (`/public`
+for your GitLab instance). For example, <https://gitlab.com/public>.
+You can control the visibility of individual features with
+[project feature settings](permissions.md#project-features).
+
+## Public projects and groups
+
+Public projects can be cloned **without any** authentication over HTTPS.
+
+They are listed in the public access directory (`/public`) for all users.
+
+**Any signed-in user** has the Guest role on the repository.
+
+NOTE:
+By default, `/public` is visible to unauthenticated users. However, if the
+[**Public** visibility level](admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels)
+is restricted, `/public` is visible only to signed-in users.
+
+## Internal projects and groups
+
+Internal projects can be cloned by any signed-in user except
+[external users](permissions.md#external-users).
+
+They are also listed in the public access directory (`/public`), but only for signed-in users.
+
+Any signed-in users except [external users](permissions.md#external-users) have the
+Guest role on the repository.
+
+NOTE:
+From July 2019, the `Internal` visibility setting is disabled for new projects, groups,
+and snippets on GitLab.com. Existing projects, groups, and snippets using the `Internal`
+visibility setting keep this setting. You can read more about the change in the
+[relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/12388).
+
+## Private projects and groups
+
+Private projects can only be cloned and viewed by project members (except for guests).
+
+They appear in the public access directory (`/public`) for project members only.
+
+## Change project visibility
+
+Prerequisite:
+
+- You must have the Owner role for a project.
+
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Visibility, project features, permissions**.
+1. Change **Project visibility** to either **Private**, **Internal**, or **Public**.
+1. Select **Save changes**.
+
+## Change group visibility
+
+Prerequisite:
+
+- You must have the Owner role for a group.
+
+1. On the top bar, select **Menu > Groups** and find your project.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Naming, visibility**.
+1. Under **Visibility level** select either **Private**, **Internal**, or **Public**.
+1. Select **Save changes**.
+
+## Restrict use of public or internal projects **(FREE SELF)**
+
+You can restrict the use of visibility levels for users when they create a project or a snippet.
+This is useful to prevent users from publicly exposing their repositories by accident. The
+restricted visibility settings do not apply to administrators.
+
+For details, see [Restricted visibility levels](admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
index 1750e2c8d10..cab18b221c1 100644
--- a/doc/user/snippets.md
+++ b/doc/user/snippets.md
@@ -22,7 +22,7 @@ using the [GitLab Workflow VS Code extension](project/repository/vscode.md).
GitLab provides two types of snippets:
- **Personal snippets**: Created independent of any project.
- You can set a [visibility level](../public_access/public_access.md)
+ You can set a [visibility level](public_access.md)
for your snippet: public, internal, or private.
- **Project snippets**: Always related to a specific project.
Project snippets can be visible publicly or to only group members.
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index d78576b5d5b..6ef2ce885ed 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -28,6 +28,8 @@ module API
optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues'
end
post ":id/invitations" do
+ ::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/354016')
+
params[:source] = find_source(source_type, params[:id])
authorize_admin_source!(source_type, params[:source])
diff --git a/lib/container_registry/gitlab_api_client.rb b/lib/container_registry/gitlab_api_client.rb
index 3cd7003d1f8..c627ad30311 100644
--- a/lib/container_registry/gitlab_api_client.rb
+++ b/lib/container_registry/gitlab_api_client.rb
@@ -5,10 +5,12 @@ module ContainerRegistry
include Gitlab::Utils::StrongMemoize
JSON_TYPE = 'application/json'
+ CANCEL_RESPONSE_STATUS_HEADER = 'status'
IMPORT_RESPONSES = {
200 => :already_imported,
202 => :ok,
+ 400 => :bad_request,
401 => :unauthorized,
404 => :not_found,
409 => :already_being_imported,
@@ -50,11 +52,29 @@ module ContainerRegistry
IMPORT_RESPONSES.fetch(response.status, :error)
end
+ # https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs-gitlab/api.md#import-repository
+ def cancel_repository_import(path)
+ response = with_import_token_faraday do |faraday_client|
+ faraday_client.delete(import_url_for(path))
+ end
+
+ status = IMPORT_RESPONSES.fetch(response.status, :error)
+ actual_state = response.body[CANCEL_RESPONSE_STATUS_HEADER]
+
+ { status: status, migration_state: actual_state }
+ end
+
# https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs-gitlab/api.md#get-repository-import-status
def import_status(path)
with_import_token_faraday do |faraday_client|
- body_hash = response_body(faraday_client.get(import_url_for(path)))
- body_hash['status'] || 'error'
+ response = faraday_client.get(import_url_for(path))
+
+ # Temporary solution for https://gitlab.com/gitlab-org/gitlab/-/issues/356085#solutions
+ # this will trigger a `retry_pre_import`
+ break 'pre_import_failed' unless response.success?
+
+ body_hash = response_body(response)
+ body_hash&.fetch('status') || 'error'
end
end
diff --git a/lib/gitlab/background_migration/populate_container_repository_migration_plan.rb b/lib/gitlab/background_migration/populate_container_repository_migration_plan.rb
new file mode 100644
index 00000000000..9e102ea1517
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_container_repository_migration_plan.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # The class to populates the migration_plan column of container_repositories
+ # with the current plan of the namespaces that owns the container_repository
+ #
+ # The plan can be NULL, in which case no UPDATE
+ # will be executed.
+ class PopulateContainerRepositoryMigrationPlan
+ def perform(start_id, end_id)
+ (start_id..end_id).each do |id|
+ execute(<<~SQL)
+ WITH selected_plan AS (
+ SELECT "plans"."name"
+ FROM "container_repositories"
+ INNER JOIN "projects" ON "projects"."id" = "container_repositories"."project_id"
+ INNER JOIN "namespaces" ON "namespaces"."id" = "projects"."namespace_id"
+ INNER JOIN "gitlab_subscriptions" ON "gitlab_subscriptions"."namespace_id" = "namespaces"."traversal_ids"[1]
+ INNER JOIN "plans" ON "plans"."id" = "gitlab_subscriptions"."hosted_plan_id"
+ WHERE "container_repositories"."id" = #{id}
+ )
+ UPDATE container_repositories
+ SET migration_plan = selected_plan.name
+ FROM selected_plan
+ WHERE container_repositories.id = #{id};
+ SQL
+ end
+
+ mark_job_as_succeeded(start_id, end_id)
+ end
+
+ private
+
+ def connection
+ @connection ||= ::ActiveRecord::Base.connection
+ end
+
+ def execute(sql)
+ connection.execute(sql)
+ end
+
+ def mark_job_as_succeeded(*arguments)
+ Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
+ self.class.name.demodulize,
+ arguments
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/known_events/error_tracking.yml b/lib/gitlab/usage_data_counters/known_events/error_tracking.yml
index a56e0a6d370..d80b711f8eb 100644
--- a/lib/gitlab/usage_data_counters/known_events/error_tracking.yml
+++ b/lib/gitlab/usage_data_counters/known_events/error_tracking.yml
@@ -3,9 +3,7 @@
category: error_tracking
redis_slot: error_tracking
aggregation: weekly
- feature_flag: track_error_tracking_activity
- name: error_tracking_view_list
category: error_tracking
redis_slot: error_tracking
aggregation: weekly
- feature_flag: track_error_tracking_activity
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 42cd6e72319..c71c614ebda 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -855,6 +855,9 @@ msgstr ""
msgid "%{name}, confirm your email address now!"
msgstr ""
+msgid "%{name}: %{resultsString}"
+msgstr ""
+
msgid "%{no_of_days} day"
msgid_plural "%{no_of_days} days"
msgstr[0] ""
@@ -2609,6 +2612,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|CI/CD limits"
+msgstr ""
+
msgid "AdminSettings|Configure Let's Encrypt"
msgstr ""
@@ -2648,6 +2654,24 @@ msgstr ""
msgid "AdminSettings|Maximum duration of a session for Git operations when 2FA is enabled."
msgstr ""
+msgid "AdminSettings|Maximum number of DAG dependencies that a job can have"
+msgstr ""
+
+msgid "AdminSettings|Maximum number of jobs in a single pipeline"
+msgstr ""
+
+msgid "AdminSettings|Maximum number of pipeline schedules"
+msgstr ""
+
+msgid "AdminSettings|Maximum number of pipeline subscriptions to and from a project"
+msgstr ""
+
+msgid "AdminSettings|Maximum number of runners registered per group"
+msgstr ""
+
+msgid "AdminSettings|Maximum number of runners registered per project"
+msgstr ""
+
msgid "AdminSettings|New CI/CD variables in projects and groups default to protected."
msgstr ""
@@ -2663,6 +2687,9 @@ msgstr ""
msgid "AdminSettings|Required pipeline configuration"
msgstr ""
+msgid "AdminSettings|Save %{name} limits"
+msgstr ""
+
msgid "AdminSettings|Select a CI/CD template"
msgstr ""
@@ -2678,6 +2705,9 @@ msgstr ""
msgid "AdminSettings|Set a CI/CD template as the required pipeline configuration for all projects in the instance. Project CI/CD configuration merges into the required pipeline configuration when the pipeline runs. %{link_start}What is a required pipeline configuration?%{link_end}"
msgstr ""
+msgid "AdminSettings|Set limit to 0 to disable it."
+msgstr ""
+
msgid "AdminSettings|Set the initial name and protections for the default branch of new repositories created in the instance."
msgstr ""
@@ -2699,6 +2729,9 @@ msgstr ""
msgid "AdminSettings|The template for the required pipeline configuration can be one of the GitLab-provided templates, or a custom template added to an instance template repository. %{link_start}How do I create an instance template repository?%{link_end}"
msgstr ""
+msgid "AdminSettings|Total number of jobs in currently active pipelines"
+msgstr ""
+
msgid "AdminStatistics|Active Users"
msgstr ""
@@ -31203,6 +31236,9 @@ msgstr ""
msgid "Reports|Filename"
msgstr ""
+msgid "Reports|Full report"
+msgstr ""
+
msgid "Reports|Head report parsing error:"
msgstr ""
@@ -31245,9 +31281,15 @@ msgstr ""
msgid "Reports|Test summary failed loading results"
msgstr ""
+msgid "Reports|Test summary failed to load results"
+msgstr ""
+
msgid "Reports|Test summary results are being parsed"
msgstr ""
+msgid "Reports|Test summary results are loading"
+msgstr ""
+
msgid "Reports|Tool"
msgstr ""
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index df93bd773a6..7bef82a7e5a 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -311,7 +311,9 @@ RSpec.describe 'Admin updates settings' do
end
context 'CI/CD page' do
- it 'change CI/CD settings' do
+ let_it_be(:default_plan) { create(:default_plan) }
+
+ it 'changes CI/CD settings' do
visit ci_cd_admin_application_settings_path
page.within('.as-ci-cd') do
@@ -329,6 +331,31 @@ RSpec.describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
+ it 'changes CI/CD limits', :aggregate_failures do
+ visit ci_cd_admin_application_settings_path
+
+ page.within('.as-ci-cd') do
+ fill_in 'plan_limits_ci_pipeline_size', with: 10
+ fill_in 'plan_limits_ci_active_jobs', with: 20
+ fill_in 'plan_limits_ci_project_subscriptions', with: 30
+ fill_in 'plan_limits_ci_pipeline_schedules', with: 40
+ fill_in 'plan_limits_ci_needs_size_limit', with: 50
+ fill_in 'plan_limits_ci_registered_group_runners', with: 60
+ fill_in 'plan_limits_ci_registered_project_runners', with: 70
+ click_button 'Save Default limits'
+ end
+
+ limits = default_plan.reload.limits
+ expect(limits.ci_pipeline_size).to eq(10)
+ expect(limits.ci_active_jobs).to eq(20)
+ expect(limits.ci_project_subscriptions).to eq(30)
+ expect(limits.ci_pipeline_schedules).to eq(40)
+ expect(limits.ci_needs_size_limit).to eq(50)
+ expect(limits.ci_registered_group_runners).to eq(60)
+ expect(limits.ci_registered_project_runners).to eq(70)
+ expect(page).to have_content 'Application limits saved successfully'
+ end
+
context 'Runner Registration' do
context 'when feature is enabled' do
before do
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 27f7c699c50..c9b21d4a4ae 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -17,6 +17,9 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
project.add_maintainer(user)
project_only_mwps.add_maintainer(user)
sign_in(user)
+
+ stub_feature_flags(refactor_mr_widgets_extensions: false)
+ stub_feature_flags(refactor_mr_widgets_extensions_user: false)
end
context 'new merge request', :sidekiq_might_not_need_inline do
diff --git a/spec/frontend/vue_mr_widget/extensions/test_report/index_spec.js b/spec/frontend/vue_mr_widget/extensions/test_report/index_spec.js
new file mode 100644
index 00000000000..472dbc104ce
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/extensions/test_report/index_spec.js
@@ -0,0 +1,149 @@
+import { GlButton } from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import testReportExtension from '~/vue_merge_request_widget/extensions/test_report';
+import { i18n } from '~/vue_merge_request_widget/extensions/test_report/constants';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { trimText } from 'helpers/text_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import axios from '~/lib/utils/axios_utils';
+import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container';
+import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
+import httpStatusCodes from '~/lib/utils/http_status';
+
+import { failedReport } from '../../../reports/mock_data/mock_data';
+import mixedResultsTestReports from '../../../reports/mock_data/new_and_fixed_failures_report.json';
+import newErrorsTestReports from '../../../reports/mock_data/new_errors_report.json';
+import newFailedTestReports from '../../../reports/mock_data/new_failures_report.json';
+import successTestReports from '../../../reports/mock_data/no_failures_report.json';
+import resolvedFailures from '../../../reports/mock_data/resolved_failures.json';
+
+const reportWithParsingErrors = failedReport;
+reportWithParsingErrors.suites[0].suite_errors = {
+ head: 'JUnit XML parsing failed: 2:24: FATAL: attributes construct error',
+ base: 'JUnit data parsing failed: string not matched',
+};
+
+describe('Test report extension', () => {
+ let wrapper;
+ let mock;
+
+ registerExtension(testReportExtension);
+
+ const endpoint = '/root/repo/-/merge_requests/4/test_reports.json';
+
+ const mockApi = (statusCode, data = mixedResultsTestReports) => {
+ mock.onGet(endpoint).reply(statusCode, data);
+ };
+
+ const findToggleCollapsedButton = () => wrapper.findByTestId('toggle-button');
+ const findTertiaryButton = () => wrapper.find(GlButton);
+ const findAllExtensionListItems = () => wrapper.findAllByTestId('extension-list-item');
+
+ const createComponent = () => {
+ wrapper = mountExtended(extensionsContainer, {
+ propsData: {
+ mr: {
+ testResultsPath: endpoint,
+ headBlobPath: 'head/blob/path',
+ pipeline: { path: 'pipeline/path' },
+ },
+ },
+ });
+ };
+
+ const createExpandedWidgetWithData = async (data = mixedResultsTestReports) => {
+ mockApi(httpStatusCodes.OK, data);
+ createComponent();
+ await waitForPromises();
+ findToggleCollapsedButton().trigger('click');
+ await waitForPromises();
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mock.restore();
+ });
+
+ describe('summary', () => {
+ it('displays loading text', () => {
+ mockApi(httpStatusCodes.OK);
+ createComponent();
+
+ expect(wrapper.text()).toContain(i18n.loading);
+ });
+
+ it('displays failed loading text', async () => {
+ mockApi(httpStatusCodes.INTERNAL_SERVER_ERROR);
+ createComponent();
+
+ await waitForPromises();
+
+ expect(wrapper.text()).toContain(i18n.error);
+ });
+
+ it.each`
+ description | mockData | expectedResult
+ ${'mixed test results'} | ${mixedResultsTestReports} | ${'Test summary: 2 failed and 2 fixed test results, 11 total tests'}
+ ${'unchanged test results'} | ${successTestReports} | ${'Test summary: no changed test results, 11 total tests'}
+ ${'tests with errors'} | ${newErrorsTestReports} | ${'Test summary: 2 errors, 11 total tests'}
+ ${'failed test results'} | ${newFailedTestReports} | ${'Test summary: 2 failed, 11 total tests'}
+ ${'resolved failures'} | ${resolvedFailures} | ${'Test summary: 4 fixed test results, 11 total tests'}
+ `('displays summary text for $description', async ({ mockData, expectedResult }) => {
+ mockApi(httpStatusCodes.OK, mockData);
+ createComponent();
+
+ await waitForPromises();
+
+ expect(wrapper.text()).toContain(expectedResult);
+ });
+
+ it('displays a link to the full report', async () => {
+ mockApi(httpStatusCodes.OK);
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findTertiaryButton().text()).toBe('Full report');
+ expect(findTertiaryButton().attributes('href')).toBe('pipeline/path/test_report');
+ });
+
+ it('shows an error when a suite has a parsing error', async () => {
+ mockApi(httpStatusCodes.OK, reportWithParsingErrors);
+ createComponent();
+
+ await waitForPromises();
+
+ expect(wrapper.text()).toContain(i18n.error);
+ });
+ });
+
+ describe('expanded data', () => {
+ it('displays summary for each suite', async () => {
+ await createExpandedWidgetWithData();
+
+ expect(trimText(findAllExtensionListItems().at(0).text())).toBe(
+ 'rspec:pg: 1 failed and 2 fixed test results, 8 total tests',
+ );
+ expect(trimText(findAllExtensionListItems().at(1).text())).toBe(
+ 'java ant: 1 failed, 3 total tests',
+ );
+ });
+
+ it('displays suite parsing errors', async () => {
+ await createExpandedWidgetWithData(reportWithParsingErrors);
+
+ const suiteText = trimText(findAllExtensionListItems().at(0).text());
+
+ expect(suiteText).toContain(
+ 'Head report parsing error: JUnit XML parsing failed: 2:24: FATAL: attributes construct error',
+ );
+ expect(suiteText).toContain(
+ 'Base report parsing error: JUnit data parsing failed: string not matched',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index 0540107ea5f..9a21389dbb4 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -1025,7 +1025,7 @@ describe('MrWidgetOptions', () => {
it('captures sentry error and displays error when poll has failed', () => {
expect(captureException).toHaveBeenCalledTimes(1);
expect(captureException).toHaveBeenCalledWith(new Error('Fetch error'));
- expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('error');
+ expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('failed');
});
});
});
@@ -1036,7 +1036,7 @@ describe('MrWidgetOptions', () => {
const itHandlesTheException = () => {
expect(captureException).toHaveBeenCalledTimes(1);
expect(captureException).toHaveBeenCalledWith(new Error('Fetch error'));
- expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('error');
+ expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('failed');
};
beforeEach(() => {
diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb
index 4fe229024e5..0fb3b53d42a 100644
--- a/spec/lib/container_registry/gitlab_api_client_spec.rb
+++ b/spec/lib/container_registry/gitlab_api_client_spec.rb
@@ -62,6 +62,7 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
where(:status_code, :expected_result) do
200 | :already_imported
202 | :ok
+ 400 | :bad_request
401 | :unauthorized
404 | :not_found
409 | :already_being_imported
@@ -86,6 +87,7 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
where(:status_code, :expected_result) do
200 | :already_imported
202 | :ok
+ 400 | :bad_request
401 | :unauthorized
404 | :not_found
409 | :already_being_imported
@@ -104,23 +106,68 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
end
end
- describe '#import_status' do
- subject { client.import_status(path) }
+ describe '#cancel_repository_import' do
+ subject { client.cancel_repository_import(path) }
+
+ where(:status_code, :expected_result) do
+ 200 | :already_imported
+ 202 | :ok
+ 400 | :bad_request
+ 401 | :unauthorized
+ 404 | :not_found
+ 409 | :already_being_imported
+ 418 | :error
+ 424 | :pre_import_failed
+ 425 | :already_being_imported
+ 429 | :too_many_imports
+ end
+
+ with_them do
+ before do
+ stub_import_cancel(path, status_code)
+ end
- before do
- stub_import_status(path, status)
+ it { is_expected.to eq({ status: expected_result, migration_state: nil }) }
end
- context 'with a status' do
+ context 'bad request' do
let(:status) { 'this_is_a_test' }
- it { is_expected.to eq(status) }
+ before do
+ stub_import_cancel(path, 400, status: status)
+ end
+
+ it { is_expected.to eq({ status: :bad_request, migration_state: status }) }
end
+ end
- context 'with no status' do
- let(:status) { nil }
+ describe '#import_status' do
+ subject { client.import_status(path) }
- it { is_expected.to eq('error') }
+ context 'with successful response' do
+ before do
+ stub_import_status(path, status)
+ end
+
+ context 'with a status' do
+ let(:status) { 'this_is_a_test' }
+
+ it { is_expected.to eq(status) }
+ end
+
+ context 'with no status' do
+ let(:status) { nil }
+
+ it { is_expected.to eq('error') }
+ end
+ end
+
+ context 'with non successful response' do
+ before do
+ stub_import_status(path, nil, status_code: 404)
+ end
+
+ it { is_expected.to eq('pre_import_failed') }
end
end
@@ -230,16 +277,32 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
.to_return(status: status_code, body: '')
end
- def stub_import_status(path, status)
+ def stub_import_status(path, status, status_code: 200)
stub_request(:get, "#{registry_api_url}/gitlab/v1/import/#{path}/")
.with(headers: { 'Accept' => described_class::JSON_TYPE, 'Authorization' => "bearer #{import_token}" })
.to_return(
- status: 200,
+ status: status_code,
body: { status: status }.to_json,
headers: { content_type: 'application/json' }
)
end
+ def stub_import_cancel(path, http_status, status: nil)
+ body = {}
+
+ if http_status == 400
+ body = { status: status }
+ end
+
+ stub_request(:delete, "#{registry_api_url}/gitlab/v1/import/#{path}/")
+ .with(headers: { 'Accept' => described_class::JSON_TYPE, 'Authorization' => "bearer #{import_token}" })
+ .to_return(
+ status: http_status,
+ body: body.to_json,
+ headers: { content_type: 'application/json' }
+ )
+ end
+
def stub_repository_details(path, with_size: true, status_code: 200, respond_with: {})
url = "#{registry_api_url}/gitlab/v1/repositories/#{path}/"
url += "?size=self" if with_size
diff --git a/spec/lib/gitlab/background_migration/populate_container_repository_migration_plan_spec.rb b/spec/lib/gitlab/background_migration/populate_container_repository_migration_plan_spec.rb
new file mode 100644
index 00000000000..0463f5a0c0d
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_container_repository_migration_plan_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::PopulateContainerRepositoryMigrationPlan, schema: 20220316202640 do
+ let_it_be(:container_repositories) { table(:container_repositories) }
+ let_it_be(:projects) { table(:projects) }
+ let_it_be(:namespaces) { table(:namespaces) }
+ let_it_be(:gitlab_subscriptions) { table(:gitlab_subscriptions) }
+ let_it_be(:plans) { table(:plans) }
+ let_it_be(:namespace_statistics) { table(:namespace_statistics) }
+
+ let!(:namepace1) { namespaces.create!(id: 1, type: 'Group', name: 'group1', path: 'group1', traversal_ids: [1]) }
+ let!(:namepace2) { namespaces.create!(id: 2, type: 'Group', name: 'group2', path: 'group2', traversal_ids: [2]) }
+ let!(:namepace3) { namespaces.create!(id: 3, type: 'Group', name: 'group3', path: 'group3', traversal_ids: [3]) }
+ let!(:sub_namespace) { namespaces.create!(id: 4, type: 'Group', name: 'group3', path: 'group3', parent_id: 1, traversal_ids: [1, 4]) }
+ let!(:plan1) { plans.create!(id: 1, name: 'plan1') }
+ let!(:plan2) { plans.create!(id: 2, name: 'plan2') }
+ let!(:gitlab_subscription1) { gitlab_subscriptions.create!(id: 1, namespace_id: 1, hosted_plan_id: 1) }
+ let!(:gitlab_subscription2) { gitlab_subscriptions.create!(id: 2, namespace_id: 2, hosted_plan_id: 2) }
+ let!(:project1) { projects.create!(id: 1, name: 'project1', path: 'project1', namespace_id: 4) }
+ let!(:project2) { projects.create!(id: 2, name: 'project2', path: 'project2', namespace_id: 2) }
+ let!(:project3) { projects.create!(id: 3, name: 'project3', path: 'project3', namespace_id: 3) }
+ let!(:container_repository1) { container_repositories.create!(id: 1, name: 'cr1', project_id: 1) }
+ let!(:container_repository2) { container_repositories.create!(id: 2, name: 'cr2', project_id: 2) }
+ let!(:container_repository3) { container_repositories.create!(id: 3, name: 'cr3', project_id: 3) }
+
+ let(:migration) { described_class.new }
+
+ subject do
+ migration.perform(1, 4)
+ end
+
+ it 'updates the migration_plan to match the actual plan', :aggregate_failures do
+ expect(Gitlab::Database::BackgroundMigrationJob).to receive(:mark_all_as_succeeded)
+ .with('PopulateContainerRepositoryMigrationPlan', [1, 4]).and_return(true)
+
+ subject
+
+ expect(container_repository1.reload.migration_plan).to eq('plan1')
+ expect(container_repository2.reload.migration_plan).to eq('plan2')
+ expect(container_repository3.reload.migration_plan).to eq(nil)
+ end
+end
diff --git a/spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb b/spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb
new file mode 100644
index 00000000000..7b5c8254163
--- /dev/null
+++ b/spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe PopulateContainerRepositoriesMigrationPlan, :aggregate_failures do
+ let_it_be(:namespaces) { table(:namespaces) }
+ let_it_be(:projects) { table(:projects) }
+ let_it_be(:container_repositories) { table(:container_repositories) }
+
+ let!(:namespace) { namespaces.create!(id: 1, name: 'namespace', path: 'namespace') }
+ let!(:project) { projects.create!(id: 1, name: 'project', path: 'project', namespace_id: 1) }
+ let!(:container_repository1) { container_repositories.create!(name: 'container_repository1', project_id: 1) }
+ let!(:container_repository2) { container_repositories.create!(name: 'container_repository2', project_id: 1) }
+ let!(:container_repository3) { container_repositories.create!(name: 'container_repository3', project_id: 1) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ end
+
+ it 'schedules jobs for container_repositories to populate migration_state' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 2.minutes, container_repository1.id, container_repository2.id)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 4.minutes, container_repository3.id, container_repository3.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/approvable_base_spec.rb b/spec/models/concerns/approvable_base_spec.rb
index 79053e98db7..2bf6a98a64d 100644
--- a/spec/models/concerns/approvable_base_spec.rb
+++ b/spec/models/concerns/approvable_base_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe ApprovableBase do
subject { merge_request.can_be_approved_by?(user) }
before do
- merge_request.project.add_developer(user)
+ merge_request.project.add_developer(user) if user
end
it 'returns true' do
@@ -64,7 +64,7 @@ RSpec.describe ApprovableBase do
subject { merge_request.can_be_unapproved_by?(user) }
before do
- merge_request.project.add_developer(user)
+ merge_request.project.add_developer(user) if user
end
it 'returns false' do
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index c8d86edc55f..3d9181dcfa2 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -208,7 +208,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
end
end
- it_behaves_like 'transitioning from allowed states', %w[import_aborted]
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing importing import_aborted]
it_behaves_like 'transitioning to pre_importing'
it_behaves_like 'transitioning out of import_aborted'
end
@@ -218,7 +218,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
subject { repository.finish_pre_import }
- it_behaves_like 'transitioning from allowed states', %w[pre_importing import_aborted]
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing importing import_aborted]
it 'sets migration_pre_import_done_at' do
expect { subject }.to change { repository.reload.migration_pre_import_done_at }
@@ -238,7 +238,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
end
end
- it_behaves_like 'transitioning from allowed states', %w[pre_import_done]
+ it_behaves_like 'transitioning from allowed states', %w[pre_import_done pre_importing importing import_aborted]
it_behaves_like 'transitioning to importing'
end
@@ -253,7 +253,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
end
end
- it_behaves_like 'transitioning from allowed states', %w[import_aborted]
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing importing import_aborted]
it_behaves_like 'transitioning to importing'
it_behaves_like 'no action when feature flag is disabled'
end
@@ -263,7 +263,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
subject { repository.finish_import }
- it_behaves_like 'transitioning from allowed states', %w[importing import_aborted]
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing importing import_aborted]
it_behaves_like 'queueing the next import'
it 'sets migration_import_done_at and queues the next import' do
@@ -334,7 +334,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
end
end
- it_behaves_like 'transitioning from allowed states', %w[pre_importing import_aborted]
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing importing import_aborted]
it_behaves_like 'transitioning to importing'
end
end
@@ -391,7 +391,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
describe '#retry_aborted_migration' do
subject { repository.retry_aborted_migration }
- shared_examples 'no action' do
+ context 'when migration_state is not aborted' do
it 'does nothing' do
expect { subject }.not_to change { repository.reload.migration_state }
@@ -399,104 +399,45 @@ RSpec.describe ContainerRepository, :aggregate_failures do
end
end
- shared_examples 'retrying the pre_import' do
- it 'retries the pre_import' do
- expect(repository).to receive(:migration_pre_import).and_return(:ok)
-
- expect { subject }.to change { repository.reload.migration_state }.to('pre_importing')
- end
- end
-
- shared_examples 'retrying the import' do
- it 'retries the import' do
- expect(repository).to receive(:migration_import).and_return(:ok)
-
- expect { subject }.to change { repository.reload.migration_state }.to('importing')
- end
- end
-
- context 'when migration_state is not aborted' do
- it_behaves_like 'no action'
- end
-
context 'when migration_state is aborted' do
before do
repository.abort_import
allow(repository.gitlab_api_client)
- .to receive(:import_status).with(repository.path).and_return(client_response)
+ .to receive(:import_status).with(repository.path).and_return(status)
end
- context 'native response' do
- let(:client_response) { 'native' }
-
- it 'raises an error' do
- expect { subject }.to raise_error(described_class::NativeImportError)
- end
- end
+ it_behaves_like 'reconciling migration_state' do
+ context 'error response' do
+ let(:status) { 'error' }
- context 'import_in_progress response' do
- let(:client_response) { 'import_in_progress' }
-
- it_behaves_like 'no action'
- end
-
- context 'import_complete response' do
- let(:client_response) { 'import_complete' }
-
- it 'finishes the import' do
- expect { subject }.to change { repository.reload.migration_state }.to('import_done')
- end
- end
-
- context 'import_failed response' do
- let(:client_response) { 'import_failed' }
-
- it_behaves_like 'retrying the import'
- end
-
- context 'pre_import_in_progress response' do
- let(:client_response) { 'pre_import_in_progress' }
-
- it_behaves_like 'no action'
- end
+ context 'migration_pre_import_done_at is NULL' do
+ it_behaves_like 'retrying the pre_import'
+ end
- context 'pre_import_complete response' do
- let(:client_response) { 'pre_import_complete' }
+ context 'migration_pre_import_done_at is not NULL' do
+ before do
+ repository.update_columns(
+ migration_pre_import_started_at: 5.minutes.ago,
+ migration_pre_import_done_at: Time.zone.now
+ )
+ end
- it 'finishes the pre_import and starts the import' do
- expect(repository).to receive(:finish_pre_import).and_call_original
- expect(repository).to receive(:migration_import).and_return(:ok)
-
- expect { subject }.to change { repository.reload.migration_state }.to('importing')
+ it_behaves_like 'retrying the import'
+ end
end
end
+ end
+ end
- context 'pre_import_failed response' do
- let(:client_response) { 'pre_import_failed' }
-
- it_behaves_like 'retrying the pre_import'
- end
-
- context 'error response' do
- let(:client_response) { 'error' }
-
- context 'migration_pre_import_done_at is NULL' do
- it_behaves_like 'retrying the pre_import'
- end
-
- context 'migration_pre_import_done_at is not NULL' do
- before do
- repository.update_columns(
- migration_pre_import_started_at: 5.minutes.ago,
- migration_pre_import_done_at: Time.zone.now
- )
- end
+ describe '#reconcile_import_status' do
+ subject { repository.reconcile_import_status(status) }
- it_behaves_like 'retrying the import'
- end
- end
+ before do
+ repository.abort_import
end
+
+ it_behaves_like 'reconciling migration_state'
end
describe '#tag' do
@@ -722,12 +663,12 @@ RSpec.describe ContainerRepository, :aggregate_failures do
end
context 'registry migration' do
- shared_examples 'handling the migration step' do |step|
- let(:client_response) { :foobar }
+ before do
+ allow(repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(true)
+ end
- before do
- allow(repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(true)
- end
+ shared_examples 'gitlab migration client request' do |step|
+ let(:client_response) { :foobar }
it 'returns the same response as the client' do
expect(repository.gitlab_api_client)
@@ -746,6 +687,10 @@ RSpec.describe ContainerRepository, :aggregate_failures do
expect(subject).to eq(:error)
end
end
+ end
+
+ shared_examples 'handling the migration step' do |step|
+ it_behaves_like 'gitlab migration client request', step
context 'too many imports' do
it 'raises an error when it receives too_many_imports as a response' do
@@ -767,6 +712,12 @@ RSpec.describe ContainerRepository, :aggregate_failures do
it_behaves_like 'handling the migration step', :import_repository
end
+
+ describe '#migration_cancel' do
+ subject { repository.migration_cancel }
+
+ it_behaves_like 'gitlab migration client request', :cancel_repository_import
+ end
end
describe '.build_from_path' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 45a2c134077..ea1333915d7 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -293,6 +293,8 @@ RSpec.describe Group do
end
end
+ it_behaves_like 'a BulkUsersByEmailLoad model'
+
context 'traversal_ids on create' do
context 'default traversal_ids' do
let(:group) { build(:group) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index fc7ac35ed41..432d8223a5a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -635,6 +635,8 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ it_behaves_like 'a BulkUsersByEmailLoad model'
+
describe '#all_pipelines' do
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/internal/container_registry/migration_spec.rb b/spec/requests/api/internal/container_registry/migration_spec.rb
index 27e99a21c65..35113c66f11 100644
--- a/spec/requests/api/internal/container_registry/migration_spec.rb
+++ b/spec/requests/api/internal/container_registry/migration_spec.rb
@@ -67,12 +67,17 @@ RSpec.describe API::Internal::ContainerRegistry::Migration do
it_behaves_like 'returning an error', with_message: "Couldn't transition from pre_importing to importing"
end
- end
- context 'with repository in importing migration state' do
- let(:repository) { create(:container_repository, :importing) }
+ context 'with repository in importing migration state' do
+ let(:repository) { create(:container_repository, :importing) }
+
+ it 'returns ok and does not update the migration state' do
+ expect { subject }
+ .not_to change { repository.reload.migration_state }
- it_behaves_like 'returning an error', with_message: "Couldn't transition from pre_importing to importing"
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
end
end
@@ -101,7 +106,7 @@ RSpec.describe API::Internal::ContainerRegistry::Migration do
context 'with repository in pre_importing migration state' do
let(:repository) { create(:container_repository, :pre_importing) }
- it_behaves_like 'returning an error', with_message: "Couldn't transition from importing to import_done"
+ it_behaves_like 'updating the repository migration status', from: 'pre_importing', to: 'import_done'
end
end
diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb
index 741cf793a77..0b9524a7ee7 100644
--- a/spec/requests/api/invitations_spec.rb
+++ b/spec/requests/api/invitations_spec.rb
@@ -278,12 +278,90 @@ RSpec.describe API::Invitations do
it_behaves_like 'POST /:source_type/:id/invitations', 'project' do
let(:source) { project }
end
+
+ it 'records queries', :request_store, :use_sql_query_cache do
+ post invitations_url(project, maintainer), params: { email: email, access_level: Member::DEVELOPER }
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post invitations_url(project, maintainer), params: { email: email2, access_level: Member::DEVELOPER }
+ end
+
+ emails = 'email3@example.com,email4@example.com,email5@example.com,email6@example.com,email7@example.com'
+
+ unresolved_n_plus_ones = 44 # old 48 with 12 per new email, currently there are 11 queries added per email
+
+ expect do
+ post invitations_url(project, maintainer), params: { email: emails, access_level: Member::DEVELOPER }
+ end.not_to exceed_all_query_limit(control.count).with_threshold(unresolved_n_plus_ones)
+ end
+
+ it 'records queries with secondary emails', :request_store, :use_sql_query_cache do
+ create(:email, email: email, user: create(:user))
+
+ post invitations_url(project, maintainer), params: { email: email, access_level: Member::DEVELOPER }
+
+ create(:email, email: email2, user: create(:user))
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post invitations_url(project, maintainer), params: { email: email2, access_level: Member::DEVELOPER }
+ end
+
+ create(:email, email: 'email4@example.com', user: create(:user))
+ create(:email, email: 'email6@example.com', user: create(:user))
+
+ emails = 'email3@example.com,email4@example.com,email5@example.com,email6@example.com,email7@example.com'
+
+ unresolved_n_plus_ones = 67 # currently there are 11 queries added per email
+
+ expect do
+ post invitations_url(project, maintainer), params: { email: emails, access_level: Member::DEVELOPER }
+ end.not_to exceed_all_query_limit(control.count).with_threshold(unresolved_n_plus_ones)
+ end
end
describe 'POST /groups/:id/invitations' do
it_behaves_like 'POST /:source_type/:id/invitations', 'group' do
let(:source) { group }
end
+
+ it 'records queries', :request_store, :use_sql_query_cache do
+ post invitations_url(group, maintainer), params: { email: email, access_level: Member::DEVELOPER }
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post invitations_url(group, maintainer), params: { email: email2, access_level: Member::DEVELOPER }
+ end
+
+ emails = 'email3@example.com,email4@example.com,email5@example.com,email6@example.com,email7@example.com'
+
+ unresolved_n_plus_ones = 36 # old 40 with 10 per new email, currently there are 9 queries added per email
+
+ expect do
+ post invitations_url(group, maintainer), params: { email: emails, access_level: Member::DEVELOPER }
+ end.not_to exceed_all_query_limit(control.count).with_threshold(unresolved_n_plus_ones)
+ end
+
+ it 'records queries with secondary emails', :request_store, :use_sql_query_cache do
+ create(:email, email: email, user: create(:user))
+
+ post invitations_url(group, maintainer), params: { email: email, access_level: Member::DEVELOPER }
+
+ create(:email, email: email2, user: create(:user))
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post invitations_url(group, maintainer), params: { email: email2, access_level: Member::DEVELOPER }
+ end
+
+ create(:email, email: 'email4@example.com', user: create(:user))
+ create(:email, email: 'email6@example.com', user: create(:user))
+
+ emails = 'email3@example.com,email4@example.com,email5@example.com,email6@example.com,email7@example.com'
+
+ unresolved_n_plus_ones = 62 # currently there are 9 queries added per email
+
+ expect do
+ post invitations_url(group, maintainer), params: { email: emails, access_level: Member::DEVELOPER }
+ end.not_to exceed_all_query_limit(control.count).with_threshold(unresolved_n_plus_ones)
+ end
end
shared_examples 'GET /:source_type/:id/invitations' do |source_type|
@@ -315,23 +393,6 @@ RSpec.describe API::Invitations do
end
end
- it 'avoids N+1 queries' do
- invite_member_by_email(source, source_type, email, maintainer)
-
- # Establish baseline
- get invitations_url(source, maintainer)
-
- control = ActiveRecord::QueryRecorder.new do
- get invitations_url(source, maintainer)
- end
-
- invite_member_by_email(source, source_type, email2, maintainer)
-
- expect do
- get invitations_url(source, maintainer)
- end.not_to exceed_query_limit(control)
- end
-
it 'does not find confirmed members' do
get invitations_url(source, maintainer)
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index a0f6d3d0081..6f1494a672c 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe API::ProjectImport, :aggregate_failures do
let(:namespace) { create(:group) }
before do
- namespace.add_owner(user)
+ namespace.add_owner(user) if user
end
shared_examples 'requires authentication' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fc1d815a64e..877c68f8791 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -777,7 +777,7 @@ RSpec.describe API::Projects do
subject { get api('/projects', current_user), params: params }
before do
- group_with_projects.add_owner(current_user)
+ group_with_projects.add_owner(current_user) if current_user
end
it 'returns non-public items based ordered by similarity' do
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 1d199a72d1d..d6d2bd5baf2 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -452,7 +452,7 @@ RSpec.describe API::Repositories do
it "compare commits between different projects" do
group = create(:group)
- group.add_owner(current_user)
+ group.add_owner(current_user) if current_user
forked_project = fork_project(project, current_user, repository: true, namespace: group)
forked_project.repository.create_ref('refs/heads/improve/awesome', 'refs/heads/improve/more-awesome')
diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb
index 838948132dd..5bfea15f0ca 100644
--- a/spec/requests/api/v3/github_spec.rb
+++ b/spec/requests/api/v3/github_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe API::V3::Github do
let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
end
describe 'GET /orgs/:namespace/repos' do
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 2421fd56c47..4a4ffe56867 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -342,7 +342,7 @@ RSpec.describe Ci::RetryBuildService do
end
shared_examples_for 'when build with dynamic environment is retried' do
- let_it_be(:other_developer) { create(:user).tap { |u| project.add_developer(other_developer) } }
+ let_it_be(:other_developer) { create(:user).tap { |u| project.add_developer(u) } }
let(:environment_name) { 'review/$CI_COMMIT_REF_SLUG-$GITLAB_USER_ID' }
diff --git a/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb b/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
index 5dc30c156ac..afeb1646005 100644
--- a/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memo
subject { described_class.new(*service_params) }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
end
describe '#raw_dashboard' do
diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
index 82321dbc822..127cec6275c 100644
--- a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Metrics::Dashboard::CustomMetricEmbedService do
let_it_be(:environment) { create(:environment, project: project) }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
end
let(:dashboard_path) { system_dashboard_path }
diff --git a/spec/services/metrics/dashboard/default_embed_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb
index 2ce10eac026..647778eadc1 100644
--- a/spec/services/metrics/dashboard/default_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_
let_it_be(:environment) { create(:environment, project: project) }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
end
describe '.valid_params?' do
diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
index 3c533b0c464..5eb8f24266c 100644
--- a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_
let_it_be(:environment) { create(:environment, project: project) }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
end
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
diff --git a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
index 33b7e3c85cd..d0cefdbeb30 100644
--- a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_ra
let(:service_params) { [project, user, { environment: environment }] }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
stub_application_setting(self_monitoring_project_id: project.id)
end
diff --git a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
index ced7c29b507..e1c6aaeec66 100644
--- a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memo
subject { described_class.new(*service_params) }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
end
describe '#raw_dashboard' do
diff --git a/spec/services/metrics/dashboard/transient_embed_service_spec.rb b/spec/services/metrics/dashboard/transient_embed_service_spec.rb
index 3fd0c97d909..53ea83c33d6 100644
--- a/spec/services/metrics/dashboard/transient_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/transient_embed_service_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memor
let_it_be(:environment) { create(:environment, project: project) }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
end
describe '.valid_params?' do
diff --git a/spec/services/notification_recipients/builder/default_spec.rb b/spec/services/notification_recipients/builder/default_spec.rb
index c142cc11384..4d0ddc7c4f7 100644
--- a/spec/services/notification_recipients/builder/default_spec.rb
+++ b/spec/services/notification_recipients/builder/default_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe NotificationRecipients::Builder::Default do
describe '#build!' do
let_it_be(:group) { create(:group, :public) }
- let_it_be(:project) { create(:project, :public, group: group).tap { |p| p.add_developer(project_watcher) } }
+ let_it_be(:project) { create(:project, :public, group: group).tap { |p| p.add_developer(project_watcher) if project_watcher } }
let_it_be(:target) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) }
diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
index 38a3e00c8e7..86c0ba4222c 100644
--- a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
let(:tags) { %w[latest A Ba Bb C D E] }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if user
stub_container_registry_config(enabled: true)
diff --git a/spec/support/shared_examples/models/concerns/bulk_users_by_email_load_shared_examples.rb b/spec/support/shared_examples/models/concerns/bulk_users_by_email_load_shared_examples.rb
new file mode 100644
index 00000000000..c3e9ff5c91a
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/bulk_users_by_email_load_shared_examples.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a BulkUsersByEmailLoad model' do
+ describe '#users_by_emails' do
+ let_it_be(:user1) { create(:user, emails: [create(:email, email: 'user1@example.com')]) }
+ let_it_be(:user2) { create(:user, emails: [create(:email, email: 'user2@example.com')]) }
+
+ subject(:model) { described_class.new(id: non_existing_record_id) }
+
+ context 'when nothing is loaded' do
+ let(:passed_emails) { [user1.emails.first.email, user2.email] }
+
+ it 'preforms the yielded query and supplies the data with only emails desired' do
+ expect(model.users_by_emails(passed_emails).keys).to contain_exactly(*passed_emails)
+ end
+ end
+
+ context 'when store is preloaded', :request_store do
+ let(:passed_emails) { [user1.emails.first.email, user2.email, user1.email] }
+ let(:resource_data) do
+ {
+ user1.emails.first.email => instance_double('User'),
+ user2.email => instance_double('User')
+ }
+ end
+
+ before do
+ Gitlab::SafeRequestStore["user_by_email_for_users:#{model.class.name}:#{model.id}"] = resource_data
+ end
+
+ it 'passes back loaded data and does not update the items that already exist' do
+ users_by_emails = model.users_by_emails(passed_emails)
+
+ expect(users_by_emails.keys).to contain_exactly(*passed_emails)
+ expect(users_by_emails).to include(resource_data.merge(user1.email => user1))
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index 17026f085bb..98d57e0dd8e 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -410,6 +410,22 @@ RSpec.shared_examples_for "bulk member creation" do
end
end
+ it 'with the same user sent more than once by user and by email' do
+ members = described_class.add_users(source, [user1, user1.email], :maintainer)
+
+ expect(members.map(&:user)).to contain_exactly(user1)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end
+
+ it 'with the same user sent more than once by user id and by email' do
+ members = described_class.add_users(source, [user1.id, user1.email], :maintainer)
+
+ expect(members.map(&:user)).to contain_exactly(user1)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end
+
context 'when a member already exists' do
before do
source.add_user(user1, :developer)
diff --git a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
index e1e75be2494..704198d6f04 100644
--- a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
@@ -116,3 +116,80 @@ RSpec.shared_examples 'not hitting graphql network errors with the container reg
expect_graphql_errors_to_be_empty
end
end
+
+RSpec.shared_examples 'reconciling migration_state' do
+ shared_examples 'no action' do
+ it 'does nothing' do
+ expect { subject }.not_to change { repository.reload.migration_state }
+
+ expect(subject).to eq(nil)
+ end
+ end
+
+ shared_examples 'retrying the pre_import' do
+ it 'retries the pre_import' do
+ expect(repository).to receive(:migration_pre_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_state }.to('pre_importing')
+ end
+ end
+
+ shared_examples 'retrying the import' do
+ it 'retries the import' do
+ expect(repository).to receive(:migration_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_state }.to('importing')
+ end
+ end
+
+ context 'native response' do
+ let(:status) { 'native' }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::NativeImportError)
+ end
+ end
+
+ context 'import_in_progress response' do
+ let(:status) { 'import_in_progress' }
+
+ it_behaves_like 'no action'
+ end
+
+ context 'import_complete response' do
+ let(:status) { 'import_complete' }
+
+ it 'finishes the import' do
+ expect { subject }.to change { repository.reload.migration_state }.to('import_done')
+ end
+ end
+
+ context 'import_failed response' do
+ let(:status) { 'import_failed' }
+
+ it_behaves_like 'retrying the import'
+ end
+
+ context 'pre_import_in_progress response' do
+ let(:status) { 'pre_import_in_progress' }
+
+ it_behaves_like 'no action'
+ end
+
+ context 'pre_import_complete response' do
+ let(:status) { 'pre_import_complete' }
+
+ it 'finishes the pre_import and starts the import' do
+ expect(repository).to receive(:finish_pre_import).and_call_original
+ expect(repository).to receive(:migration_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_state }.to('importing')
+ end
+ end
+
+ context 'pre_import_failed response' do
+ let(:status) { 'pre_import_failed' }
+
+ it_behaves_like 'retrying the pre_import'
+ end
+end
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
index d202c4e00f0..26731f34ed6 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database|
+RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database, feature_flag:|
include ExclusiveLeaseHelpers
describe 'defining the job attributes' do
@@ -39,6 +39,16 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
end
+ describe '.enabled?' do
+ it 'does not raise an error' do
+ expect { described_class.enabled? }.not_to raise_error
+ end
+
+ it 'returns true' do
+ expect(described_class.enabled?).to be_truthy
+ end
+ end
+
describe '#perform' do
subject(:worker) { described_class.new }
@@ -76,7 +86,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
context 'when the feature flag is disabled' do
before do
- stub_feature_flags(execute_batched_migrations_on_schedule: false)
+ stub_feature_flags(feature_flag => false)
end
it 'does nothing' do
@@ -89,7 +99,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
context 'when the feature flag is enabled' do
before do
- stub_feature_flags(execute_batched_migrations_on_schedule: true)
+ stub_feature_flags(feature_flag => true)
allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration).and_return(nil)
end
diff --git a/spec/views/admin/application_settings/_ci_cd.html.haml_spec.rb b/spec/views/admin/application_settings/_ci_cd.html.haml_spec.rb
new file mode 100644
index 00000000000..12593b88009
--- /dev/null
+++ b/spec/views/admin/application_settings/_ci_cd.html.haml_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'admin/application_settings/_ci_cd' do
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:application_setting) { build(:application_setting) }
+
+ let_it_be(:limits_attributes) do
+ {
+ ci_pipeline_size: 10,
+ ci_active_jobs: 20,
+ ci_project_subscriptions: 30,
+ ci_pipeline_schedules: 40,
+ ci_needs_size_limit: 50,
+ ci_registered_group_runners: 60,
+ ci_registered_project_runners: 70
+ }
+ end
+
+ let_it_be(:default_plan_limits) { create(:plan_limits, :default_plan, **limits_attributes) }
+
+ let(:page) { Capybara::Node::Simple.new(rendered) }
+
+ before do
+ assign(:application_setting, application_setting)
+ allow(view).to receive(:current_user) { admin }
+ allow(view).to receive(:expanded) { true }
+ end
+
+ subject { render partial: 'admin/application_settings/ci_cd' }
+
+ context 'limits' do
+ before do
+ assign(:plans, [default_plan_limits.plan])
+ end
+
+ it 'has fields for CI/CD limits', :aggregate_failures do
+ subject
+
+ expect(rendered).to have_field('Maximum number of jobs in a single pipeline', type: 'number')
+ expect(page.find_field('Maximum number of jobs in a single pipeline').value).to eq('10')
+
+ expect(rendered).to have_field('Total number of jobs in currently active pipelines', type: 'number')
+ expect(page.find_field('Total number of jobs in currently active pipelines').value).to eq('20')
+
+ expect(rendered).to have_field('Maximum number of pipeline subscriptions to and from a project', type: 'number')
+ expect(page.find_field('Maximum number of pipeline subscriptions to and from a project').value).to eq('30')
+
+ expect(rendered).to have_field('Maximum number of pipeline schedules', type: 'number')
+ expect(page.find_field('Maximum number of pipeline schedules').value).to eq('40')
+
+ expect(rendered).to have_field('Maximum number of DAG dependencies that a job can have', type: 'number')
+ expect(page.find_field('Maximum number of DAG dependencies that a job can have').value).to eq('50')
+
+ expect(rendered).to have_field('Maximum number of runners registered per group', type: 'number')
+ expect(page.find_field('Maximum number of runners registered per group').value).to eq('60')
+
+ expect(rendered).to have_field('Maximum number of runners registered per project', type: 'number')
+ expect(page.find_field('Maximum number of runners registered per project').value).to eq('70')
+ end
+
+ it 'does not display the plan name when there is only one plan' do
+ subject
+
+ expect(page).not_to have_selector('a[data-action="plan0"]')
+ end
+ end
+
+ context 'with multiple plans' do
+ let_it_be(:plan) { create(:plan, name: 'Ultimate') }
+ let_it_be(:ultimate_plan_limits) { create(:plan_limits, plan: plan, **limits_attributes) }
+
+ before do
+ assign(:plans, [default_plan_limits.plan, ultimate_plan_limits.plan])
+ end
+
+ it 'displays the plan name when there is more than one plan' do
+ subject
+
+ expect(page).to have_content('Default')
+ expect(page).to have_content('Ultimate')
+ expect(page).to have_selector('a[data-action="plan0"]')
+ expect(page).to have_selector('a[data-action="plan1"]')
+ end
+ end
+end
diff --git a/spec/workers/container_registry/migration/guard_worker_spec.rb b/spec/workers/container_registry/migration/guard_worker_spec.rb
index 7d1df320d4e..299d1204af3 100644
--- a/spec/workers/container_registry/migration/guard_worker_spec.rb
+++ b/spec/workers/container_registry/migration/guard_worker_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe ContainerRegistry::Migration::GuardWorker, :aggregate_failures do
- include_context 'container registry client'
-
let(:worker) { described_class.new }
describe '#perform' do
@@ -13,11 +11,12 @@ RSpec.describe ContainerRegistry::Migration::GuardWorker, :aggregate_failures do
let(:importing_migrations) { ::ContainerRepository.with_migration_states(:importing) }
let(:import_aborted_migrations) { ::ContainerRepository.with_migration_states(:import_aborted) }
let(:import_done_migrations) { ::ContainerRepository.with_migration_states(:import_done) }
+ let(:import_skipped_migrations) { ::ContainerRepository.with_migration_states(:import_skipped) }
subject { worker.perform }
before do
- stub_container_registry_config(enabled: true, api_url: registry_api_url, key: 'spec/fixtures/x509_certificate_pk.key')
+ stub_container_registry_config(enabled: true, api_url: 'http://container-registry', key: 'spec/fixtures/x509_certificate_pk.key')
allow(::ContainerRegistry::Migration).to receive(:max_step_duration).and_return(5.minutes)
end
@@ -26,20 +25,57 @@ RSpec.describe ContainerRegistry::Migration::GuardWorker, :aggregate_failures do
allow(::Gitlab).to receive(:com?).and_return(true)
end
- shared_examples 'not aborting any migration' do
- it 'will not abort the migration' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 1)
- expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 0)
- expect(worker).to receive(:log_extra_metadata_on_done).with(:long_running_stale_migration_container_repository_ids, [stale_migration.id])
+ shared_examples 'handling long running migrations' do
+ before do
+ allow_next_found_instance_of(ContainerRepository) do |repository|
+ allow(repository).to receive(:migration_cancel).and_return(migration_cancel_response)
+ end
+ end
- expect { subject }
- .to not_change(pre_importing_migrations, :count)
- .and not_change(pre_import_done_migrations, :count)
- .and not_change(importing_migrations, :count)
- .and not_change(import_done_migrations, :count)
- .and not_change(import_aborted_migrations, :count)
- .and not_change { stale_migration.reload.migration_state }
- .and not_change { ongoing_migration.migration_state }
+ context 'migration is canceled' do
+ let(:migration_cancel_response) { { status: :ok } }
+
+ it 'will not abort the migration' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_long_running_migration_ids, [stale_migration.id])
+
+ expect { subject }
+ .to change(import_skipped_migrations, :count)
+
+ expect(stale_migration.reload.migration_state).to eq('import_skipped')
+ expect(stale_migration.reload.migration_skipped_reason).to eq('migration_canceled')
+ end
+ end
+
+ context 'migration cancelation fails with an error' do
+ let(:migration_cancel_response) { { status: :error } }
+
+ it 'will abort the migration' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_long_running_migration_ids, [stale_migration.id])
+
+ expect { subject }
+ .to change(import_aborted_migrations, :count).by(1)
+ .and change { stale_migration.reload.migration_state }.to('import_aborted')
+ .and not_change { ongoing_migration.migration_state }
+ end
+ end
+
+ context 'migration receives bad request with a new status' do
+ let(:migration_cancel_response) { { status: :bad_request, migration_state: :import_done } }
+
+ it 'will abort the migration' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_long_running_migration_ids, [stale_migration.id])
+
+ expect { subject }
+ .to change(import_aborted_migrations, :count).by(1)
+ .and change { stale_migration.reload.migration_state }.to('import_aborted')
+ .and not_change { ongoing_migration.migration_state }
+ end
end
end
@@ -86,7 +122,7 @@ RSpec.describe ContainerRegistry::Migration::GuardWorker, :aggregate_failures do
context 'the client returns pre_import_in_progress' do
let(:import_status) { 'pre_import_in_progress' }
- it_behaves_like 'not aborting any migration'
+ it_behaves_like 'handling long running migrations'
end
end
@@ -141,7 +177,7 @@ RSpec.describe ContainerRegistry::Migration::GuardWorker, :aggregate_failures do
context 'the client returns import_in_progress' do
let(:import_status) { 'import_in_progress' }
- it_behaves_like 'not aborting any migration'
+ it_behaves_like 'handling long running migrations'
end
end
end
diff --git a/spec/workers/database/batched_background_migration/ci_database_worker_spec.rb b/spec/workers/database/batched_background_migration/ci_database_worker_spec.rb
index 2663c650986..f3cf5450048 100644
--- a/spec/workers/database/batched_background_migration/ci_database_worker_spec.rb
+++ b/spec/workers/database/batched_background_migration/ci_database_worker_spec.rb
@@ -3,5 +3,5 @@
require 'spec_helper'
RSpec.describe Database::BatchedBackgroundMigration::CiDatabaseWorker, :clean_gitlab_redis_shared_state do
- it_behaves_like 'it runs batched background migration jobs', 'ci'
+ it_behaves_like 'it runs batched background migration jobs', 'ci', feature_flag: :execute_batched_migrations_on_schedule_ci_database
end
diff --git a/spec/workers/database/batched_background_migration_worker_spec.rb b/spec/workers/database/batched_background_migration_worker_spec.rb
index a6c7db60abe..7f0883def3c 100644
--- a/spec/workers/database/batched_background_migration_worker_spec.rb
+++ b/spec/workers/database/batched_background_migration_worker_spec.rb
@@ -3,5 +3,5 @@
require 'spec_helper'
RSpec.describe Database::BatchedBackgroundMigrationWorker do
- it_behaves_like 'it runs batched background migration jobs', :main
+ it_behaves_like 'it runs batched background migration jobs', :main, feature_flag: :execute_batched_migrations_on_schedule
end