diff --git a/.rubocop_todo/gitlab/bounded_contexts.yml b/.rubocop_todo/gitlab/bounded_contexts.yml
index 4264c369266..cb5b74c2e23 100644
--- a/.rubocop_todo/gitlab/bounded_contexts.yml
+++ b/.rubocop_todo/gitlab/bounded_contexts.yml
@@ -2694,7 +2694,6 @@ Gitlab/BoundedContexts:
- 'ee/app/models/concerns/epics/metadata_cache_update.rb'
- 'ee/app/models/concerns/filterable_by_test_reports.rb'
- 'ee/app/models/concerns/health_status.rb'
- - 'ee/app/models/concerns/singleton_record.rb'
- 'ee/app/models/concerns/identity_verifiable.rb'
- 'ee/app/models/concerns/incident_management/base_pending_escalation.rb'
- 'ee/app/models/concerns/insights_feature.rb'
@@ -2705,6 +2704,7 @@ Gitlab/BoundedContexts:
- 'ee/app/models/concerns/product_analytics/schema_validator.rb'
- 'ee/app/models/concerns/product_analytics_helpers.rb'
- 'ee/app/models/concerns/scim_paginatable.rb'
+ - 'ee/app/models/concerns/singleton_record.rb'
- 'ee/app/models/concerns/visible_approvable.rb'
- 'ee/app/models/concerns/vulnerability_scopes.rb'
- 'ee/app/models/dast/branch.rb'
diff --git a/.rubocop_todo/gitlab/no_find_in_workers.yml b/.rubocop_todo/gitlab/no_find_in_workers.yml
index 858204ad39c..90995a155a6 100644
--- a/.rubocop_todo/gitlab/no_find_in_workers.yml
+++ b/.rubocop_todo/gitlab/no_find_in_workers.yml
@@ -77,7 +77,6 @@ Gitlab/NoFindInWorkers:
- 'ee/app/workers/create_github_webhook_worker.rb'
- 'ee/app/workers/dependencies/export_worker.rb'
- 'ee/app/workers/elastic/namespace_update_worker.rb'
- - 'ee/app/workers/elastic/project_transfer_worker.rb'
- 'ee/app/workers/gitlab/export/segmented_export_finalisation_worker.rb'
- 'ee/app/workers/gitlab/export/segmented_export_worker.rb'
- 'ee/app/workers/group_wikis/git_garbage_collect_worker.rb'
diff --git a/.rubocop_todo/gitlab/rspec/avoid_create_default_organization.yml b/.rubocop_todo/gitlab/rspec/avoid_create_default_organization.yml
index 1ac807fad09..63528fadac7 100644
--- a/.rubocop_todo/gitlab/rspec/avoid_create_default_organization.yml
+++ b/.rubocop_todo/gitlab/rspec/avoid_create_default_organization.yml
@@ -3,8 +3,6 @@ Gitlab/RSpec/AvoidCreateDefaultOrganization:
Details: grace period
Exclude:
- 'ee/spec/finders/ai/code_suggestion_events_finder_spec.rb'
- - 'ee/spec/lib/gitlab/auth/smartcard/ldap_certificate_spec.rb'
- - 'ee/spec/requests/smartcard_controller_spec.rb'
- 'spec/features/user_settings/personal_access_tokens_spec.rb'
- 'spec/helpers/organizations/organization_helper_spec.rb'
- 'spec/lib/gitlab/current/organization_spec.rb'
diff --git a/.rubocop_todo/gitlab/rspec/misplaced_ee_spec_file.yml b/.rubocop_todo/gitlab/rspec/misplaced_ee_spec_file.yml
index 1540f6c98b4..b423bb631aa 100644
--- a/.rubocop_todo/gitlab/rspec/misplaced_ee_spec_file.yml
+++ b/.rubocop_todo/gitlab/rspec/misplaced_ee_spec_file.yml
@@ -65,6 +65,7 @@ Gitlab/RSpec/MisplacedEeSpecFile:
- 'ee/spec/graphql/types/gitlab_subscriptions/member_management/users_queued_for_role_promotion_type_spec.rb'
- 'ee/spec/graphql/types/issue_connection_type_spec.rb'
- 'ee/spec/graphql/types/issue_type_spec.rb'
+ - 'ee/spec/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type_spec.rb'
- 'ee/spec/graphql/types/permission_types/group_spec.rb'
- 'ee/spec/graphql/types/permission_types/namespaces/base_spec.rb'
- 'ee/spec/graphql/types/permission_types/project_spec.rb'
diff --git a/.rubocop_todo/layout/empty_line_after_magic_comment.yml b/.rubocop_todo/layout/empty_line_after_magic_comment.yml
index 255a9114ddf..5027b466caf 100644
--- a/.rubocop_todo/layout/empty_line_after_magic_comment.yml
+++ b/.rubocop_todo/layout/empty_line_after_magic_comment.yml
@@ -305,7 +305,6 @@ Layout/EmptyLineAfterMagicComment:
- 'lib/gitlab/auth/blocked_user_tracker.rb'
- 'lib/gitlab/auth/otp/fortinet.rb'
- 'lib/gitlab/ci/secure_files/mobile_provision.rb'
- - 'lib/gitlab/cleanup/remote_uploads.rb'
- 'lib/gitlab/database/migrations/background_migration_helpers.rb'
- 'lib/gitlab/database/partitioning/detached_partition_dropper.rb'
- 'lib/gitlab/diff/highlight_cache.rb'
diff --git a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml
index 6a7791e4120..9ef8b0425ea 100644
--- a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml
+++ b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml
@@ -387,7 +387,6 @@ Layout/LineEndStringConcatenationIndentation:
- 'qa/qa/scenario/shared_attributes.rb'
- 'qa/qa/service/docker_run/product_analytics/browser_sdk_app.rb'
- 'qa/qa/service/docker_run/product_analytics/dotnet_sdk_app.rb'
- - 'qa/qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb'
- 'qa/qa/support/system_logs/kibana.rb'
- 'qa/qa/tools/revoke_user_personal_access_tokens.rb'
- 'qa/qa/tools/test_resources_handler.rb'
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 7a551684212..1e39e713099 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -46,7 +46,6 @@ Layout/LineLength:
- 'app/graphql/types/repository/blob_type.rb'
- 'app/graphql/types/repository_type.rb'
- 'app/graphql/types/snippets/visibility_scopes_enum.rb'
- - 'app/graphql/types/todo_action_enum.rb'
- 'app/helpers/application_helper.rb'
- 'app/helpers/application_settings_helper.rb'
- 'app/helpers/award_emoji_helper.rb'
@@ -1827,7 +1826,6 @@ Layout/LineLength:
- 'ee/spec/workers/iterations/roll_over_issues_worker_spec.rb'
- 'ee/spec/workers/iterations_update_status_worker_spec.rb'
- 'ee/spec/workers/merge_request_reset_approvals_worker_spec.rb'
- - 'ee/spec/workers/new_epic_worker_spec.rb'
- 'ee/spec/workers/repository_import_worker_spec.rb'
- 'ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb'
- 'ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb'
@@ -1982,7 +1980,6 @@ Layout/LineLength:
- 'lib/gitlab/auth/ldap/dn.rb'
- 'lib/gitlab/auth/ldap/person.rb'
- 'lib/gitlab/auth/o_auth/user.rb'
- - 'lib/gitlab/auth/saml/auth_hash.rb'
- 'lib/gitlab/auth/user_access_denied_reason.rb'
- 'lib/gitlab/background_migration/backfill_issue_search_data.rb'
- 'lib/gitlab/background_migration/backfill_iteration_cadence_id_for_boards.rb'
diff --git a/.rubocop_todo/lint/empty_block.yml b/.rubocop_todo/lint/empty_block.yml
index f5a6ba750cc..c46ac8f7879 100644
--- a/.rubocop_todo/lint/empty_block.yml
+++ b/.rubocop_todo/lint/empty_block.yml
@@ -87,7 +87,6 @@ Lint/EmptyBlock:
- 'spec/lib/gitlab/database/shared_model_spec.rb'
- 'spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb'
- 'spec/lib/gitlab/database/with_lock_retries_spec.rb'
- - 'spec/lib/gitlab/database_spec.rb'
- 'spec/lib/gitlab/etag_caching/router/graphql_spec.rb'
- 'spec/lib/gitlab/exclusive_lease_helpers_spec.rb'
- 'spec/lib/gitlab/exclusive_lease_spec.rb'
diff --git a/.rubocop_todo/lint/unused_method_argument.yml b/.rubocop_todo/lint/unused_method_argument.yml
index 1012c897477..7ee25a52041 100644
--- a/.rubocop_todo/lint/unused_method_argument.yml
+++ b/.rubocop_todo/lint/unused_method_argument.yml
@@ -66,7 +66,6 @@ Lint/UnusedMethodArgument:
- 'app/helpers/environment_helper.rb'
- 'app/helpers/groups/group_members_helper.rb'
- 'app/helpers/groups_helper.rb'
- - 'app/helpers/invite_members_helper.rb'
- 'app/helpers/issuables_helper.rb'
- 'app/helpers/issues_helper.rb'
- 'app/helpers/labels_helper.rb'
diff --git a/.rubocop_todo/performance/map_compact.yml b/.rubocop_todo/performance/map_compact.yml
index 4fe19051aa7..c83edc2b5c7 100644
--- a/.rubocop_todo/performance/map_compact.yml
+++ b/.rubocop_todo/performance/map_compact.yml
@@ -39,7 +39,6 @@ Performance/MapCompact:
- 'config/initializers/trusted_proxies.rb'
- 'config/initializers/wikicloth_redos_patch.rb'
- 'ee/app/graphql/ee/types/issue_connection_type.rb'
- - 'ee/app/graphql/resolvers/geo/registries_resolver.rb'
- 'ee/app/models/app_sec/fuzzing/api/ci_configuration.rb'
- 'ee/app/models/burndown.rb'
- 'ee/app/models/concerns/ee/project_security_scanners_information.rb'
diff --git a/.rubocop_todo/rails/date.yml b/.rubocop_todo/rails/date.yml
index ba4cb28d459..f0815b13715 100644
--- a/.rubocop_todo/rails/date.yml
+++ b/.rubocop_todo/rails/date.yml
@@ -74,19 +74,14 @@ Rails/Date:
- 'ee/spec/models/gitlab_subscription_spec.rb'
- 'ee/spec/models/iteration_spec.rb'
- 'ee/spec/requests/api/group_milestones_spec.rb'
- - 'ee/spec/requests/api/group_service_accounts_spec.rb'
- 'ee/spec/requests/api/issues_spec.rb'
- 'ee/spec/requests/api/license_spec.rb'
- 'ee/spec/requests/api/members_spec.rb'
- 'ee/spec/requests/api/namespaces_spec.rb'
- 'ee/spec/requests/api/project_milestones_spec.rb'
- - 'ee/spec/requests/custom_roles/manage_group_access_tokens/request_spec.rb'
- - 'ee/spec/requests/custom_roles/manage_project_access_tokens/request_spec.rb'
- 'ee/spec/requests/git_http_spec.rb'
- 'ee/spec/requests/gitlab_subscriptions/api/internal/upcoming_reconciliations_spec.rb'
- 'ee/spec/requests/gitlab_subscriptions/api/internal/users_spec.rb'
- - 'ee/spec/requests/groups/settings/access_tokens_controller_spec.rb'
- - 'ee/spec/requests/projects/settings/access_tokens_controller_spec.rb'
- 'ee/spec/services/app_sec/dast/profile_schedules/audit/update_service_spec.rb'
- 'ee/spec/services/audit_event_service_spec.rb'
- 'ee/spec/services/ci/minutes/additional_packs/create_service_spec.rb'
@@ -180,15 +175,11 @@ Rails/Date:
- 'spec/requests/api/graphql/mutations/work_items/update_spec.rb'
- 'spec/requests/api/graphql/work_item_spec.rb'
- 'spec/requests/api/internal/base_spec.rb'
- - 'spec/requests/api/personal_access_tokens/self_rotation_spec.rb'
- 'spec/requests/api/personal_access_tokens_spec.rb'
- 'spec/requests/api/project_statistics_spec.rb'
- - 'spec/requests/api/resource_access_tokens_spec.rb'
- 'spec/requests/api/users_spec.rb'
- 'spec/requests/git_http_spec.rb'
- - 'spec/requests/groups/settings/access_tokens_controller_spec.rb'
- 'spec/requests/jwt_controller_spec.rb'
- - 'spec/requests/projects/settings/access_tokens_controller_spec.rb'
- 'spec/requests/verifies_with_email_spec.rb'
- 'spec/serializers/entity_date_helper_spec.rb'
- 'spec/serializers/member_user_entity_spec.rb'
diff --git a/.rubocop_todo/rails/helper_instance_variable.yml b/.rubocop_todo/rails/helper_instance_variable.yml
index c4812f05ce4..84a3f273415 100644
--- a/.rubocop_todo/rails/helper_instance_variable.yml
+++ b/.rubocop_todo/rails/helper_instance_variable.yml
@@ -8,7 +8,6 @@ Rails/HelperInstanceVariable:
- 'app/helpers/award_emoji_helper.rb'
- 'app/helpers/blob_helper.rb'
- 'app/helpers/boards_helper.rb'
- - 'app/helpers/branches_helper.rb'
- 'app/helpers/breadcrumbs_helper.rb'
- 'app/helpers/ci/builds_helper.rb'
- 'app/helpers/commits_helper.rb'
diff --git a/.rubocop_todo/rspec/be_eq.yml b/.rubocop_todo/rspec/be_eq.yml
index a868fa4698f..5da3ebaa734 100644
--- a/.rubocop_todo/rspec/be_eq.yml
+++ b/.rubocop_todo/rspec/be_eq.yml
@@ -7,7 +7,6 @@ RSpec/BeEq:
- 'ee/spec/controllers/concerns/ee/routable_actions/sso_enforcement_redirect_spec.rb'
- 'ee/spec/controllers/concerns/routable_actions_spec.rb'
- 'ee/spec/controllers/ee/groups_controller_spec.rb'
- - 'ee/spec/controllers/ee/profiles/preferences_controller_spec.rb'
- 'ee/spec/controllers/ee/projects/blob_controller_spec.rb'
- 'ee/spec/controllers/ee/projects/jobs_controller_spec.rb'
- 'ee/spec/controllers/ee/projects/pages_controller_spec.rb'
@@ -26,7 +25,6 @@ RSpec/BeEq:
- 'ee/spec/features/admin/admin_emails_spec.rb'
- 'ee/spec/features/admin/admin_settings_spec.rb'
- 'ee/spec/features/admin/users/users_spec.rb'
- - 'ee/spec/features/epics/epic_work_item_sync_spec.rb'
- 'ee/spec/features/groups/settings/reporting_spec.rb'
- 'ee/spec/features/issues/user_bulk_edits_issues_spec.rb'
- 'ee/spec/features/projects/new_project_spec.rb'
diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index e8311e30340..ae84da0e357 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -781,7 +781,6 @@ RSpec/ContextWording:
- 'ee/spec/workers/incident_management/apply_incident_sla_exceeded_label_worker_spec.rb'
- 'ee/spec/workers/incident_management/oncall_rotations/persist_shifts_job_spec.rb'
- 'ee/spec/workers/merge_request_reset_approvals_worker_spec.rb'
- - 'ee/spec/workers/new_epic_worker_spec.rb'
- 'ee/spec/workers/personal_access_tokens/groups/policy_worker_spec.rb'
- 'ee/spec/workers/personal_access_tokens/instance/policy_worker_spec.rb'
- 'ee/spec/workers/post_receive_spec.rb'
diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml
index 0b112a57a92..eda55ff10ab 100644
--- a/.rubocop_todo/rspec/feature_category.yml
+++ b/.rubocop_todo/rspec/feature_category.yml
@@ -92,7 +92,6 @@ RSpec/FeatureCategory:
- 'ee/spec/graphql/ee/types/milestone_type_spec.rb'
- 'ee/spec/graphql/ee/types/mutation_type_spec.rb'
- 'ee/spec/graphql/ee/types/notes/noteable_interface_spec.rb'
- - 'ee/spec/graphql/ee/types/repository/blob_type_spec.rb'
- 'ee/spec/graphql/ee/types/user_merge_request_interaction_type_spec.rb'
- 'ee/spec/graphql/mutations/app_sec/fuzzing/coverage/corpus/create_spec.rb'
- 'ee/spec/graphql/mutations/audit_events/streaming/headers/destroy_spec.rb'
@@ -928,7 +927,6 @@ RSpec/FeatureCategory:
- 'ee/spec/services/app_sec/fuzzing/api/ci_configuration_create_service_spec.rb'
- 'ee/spec/services/app_sec/fuzzing/coverage/corpuses/create_service_spec.rb'
- 'ee/spec/services/application_settings/update_service_spec.rb'
- - 'ee/spec/services/applications/create_service_spec.rb'
- 'ee/spec/services/approval_rules/finalize_service_spec.rb'
- 'ee/spec/services/approval_rules/merge_request_rule_destroy_service_spec.rb'
- 'ee/spec/services/approval_rules/params_filtering_service_spec.rb'
@@ -2979,10 +2977,7 @@ RSpec/FeatureCategory:
- 'spec/lib/safe_zip/entry_spec.rb'
- 'spec/lib/safe_zip/extract_params_spec.rb'
- 'spec/lib/safe_zip/extract_spec.rb'
- - 'spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb'
- 'spec/lib/security/ci_configuration/sast_build_action_spec.rb'
- - 'spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb'
- - 'spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb'
- 'spec/lib/security/report_schema_version_matcher_spec.rb'
- 'spec/lib/serializers/unsafe_json_spec.rb'
- 'spec/lib/service_ping/devops_report_spec.rb'
@@ -3685,7 +3680,6 @@ RSpec/FeatureCategory:
- 'spec/serializers/user_serializer_spec.rb'
- 'spec/serializers/web_ide_terminal_entity_spec.rb'
- 'spec/serializers/web_ide_terminal_serializer_spec.rb'
- - 'spec/services/applications/create_service_spec.rb'
- 'spec/sidekiq/cron/job_gem_dependency_spec.rb'
- 'spec/sidekiq_cluster/sidekiq_cluster_spec.rb'
- 'spec/spam/concerns/has_spam_action_response_fields_spec.rb'
diff --git a/.rubocop_todo/rspec/named_subject.yml b/.rubocop_todo/rspec/named_subject.yml
index f083b76cdf3..61363d618e5 100644
--- a/.rubocop_todo/rspec/named_subject.yml
+++ b/.rubocop_todo/rspec/named_subject.yml
@@ -1382,7 +1382,6 @@ RSpec/NamedSubject:
- 'spec/helpers/gitlab_routing_helper_spec.rb'
- 'spec/helpers/groups/group_members_helper_spec.rb'
- 'spec/helpers/groups_helper_spec.rb'
- - 'spec/helpers/hooks_helper_spec.rb'
- 'spec/helpers/integrations_helper_spec.rb'
- 'spec/helpers/jira_connect_helper_spec.rb'
- 'spec/helpers/labels_helper_spec.rb'
@@ -1766,7 +1765,6 @@ RSpec/NamedSubject:
- 'spec/lib/gitlab/ci/yaml_processor_spec.rb'
- 'spec/lib/gitlab/cleanup/personal_access_tokens_spec.rb'
- 'spec/lib/gitlab/cleanup/project_uploads_spec.rb'
- - 'spec/lib/gitlab/cleanup/remote_uploads_spec.rb'
- 'spec/lib/gitlab/closing_issue_extractor_spec.rb'
- 'spec/lib/gitlab/cluster/lifecycle_events_spec.rb'
- 'spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb'
diff --git a/.rubocop_todo/rspec/verified_doubles.yml b/.rubocop_todo/rspec/verified_doubles.yml
index 8df1027a90c..91a5b806729 100644
--- a/.rubocop_todo/rspec/verified_doubles.yml
+++ b/.rubocop_todo/rspec/verified_doubles.yml
@@ -175,7 +175,6 @@ RSpec/VerifiedDoubles:
- 'ee/spec/workers/iterations/roll_over_issues_worker_spec.rb'
- 'ee/spec/workers/ldap_group_sync_worker_spec.rb'
- 'ee/spec/workers/merge_request_reset_approvals_worker_spec.rb'
- - 'ee/spec/workers/new_epic_worker_spec.rb'
- 'qa/spec/git/repository_spec.rb'
- 'qa/spec/page/base_spec.rb'
- 'qa/spec/page/validator_spec.rb'
diff --git a/.rubocop_todo/style/hash_each_methods.yml b/.rubocop_todo/style/hash_each_methods.yml
index 3b41caa7f0c..3474729179c 100644
--- a/.rubocop_todo/style/hash_each_methods.yml
+++ b/.rubocop_todo/style/hash_each_methods.yml
@@ -88,7 +88,6 @@ Style/HashEachMethods:
- 'lib/gitlab/utils/batch_loader.rb'
- 'lib/tasks/gitlab/db.rake'
- 'lib/tasks/gitlab/setup.rake'
- - 'qa/qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb'
- 'qa/spec/specs/runner_spec.rb'
- 'scripts/generate_rspec_pipeline.rb'
- 'spec/config/settings_spec.rb'
diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml
index 593a8b78953..d3b64c9cb95 100644
--- a/.rubocop_todo/style/inline_disable_annotation.yml
+++ b/.rubocop_todo/style/inline_disable_annotation.yml
@@ -1302,7 +1302,6 @@ Style/InlineDisableAnnotation:
- 'ee/app/workers/concerns/elastic/bulk_cron_worker.rb'
- 'ee/app/workers/concerns/geo/base_registry_sync_worker.rb'
- 'ee/app/workers/create_github_webhook_worker.rb'
- - 'ee/app/workers/elastic/migration_worker.rb'
- 'ee/app/workers/elastic/namespace_update_worker.rb'
- 'ee/app/workers/elastic_cluster_reindexing_cron_worker.rb'
- 'ee/app/workers/elastic_full_index_worker.rb'
@@ -1609,7 +1608,6 @@ Style/InlineDisableAnnotation:
- 'ee/spec/workers/ldap_all_groups_sync_worker_spec.rb'
- 'ee/spec/workers/ldap_group_sync_worker_spec.rb'
- 'ee/spec/workers/ldap_sync_worker_spec.rb'
- - 'ee/spec/workers/new_epic_worker_spec.rb'
- 'ee/spec/workers/projects/register_suggested_reviewers_project_worker_spec.rb'
- 'ee/spec/workers/update_all_mirrors_worker_spec.rb'
- 'lib/api/access_requests.rb'
@@ -1626,7 +1624,6 @@ Style/InlineDisableAnnotation:
- 'lib/api/ci/resource_groups.rb'
- 'lib/api/ci/runners.rb'
- 'lib/api/ci/secure_files.rb'
- - 'lib/api/ci/triggers.rb'
- 'lib/api/commit_statuses.rb'
- 'lib/api/custom_attributes_endpoints.rb'
- 'lib/api/dependency_proxy.rb'
@@ -1787,7 +1784,6 @@ Style/InlineDisableAnnotation:
- 'lib/gitlab/ci/trace/stream.rb'
- 'lib/gitlab/cleanup/personal_access_tokens.rb'
- 'lib/gitlab/cleanup/project_uploads.rb'
- - 'lib/gitlab/cleanup/remote_uploads.rb'
- 'lib/gitlab/cluster/lifecycle_events.rb'
- 'lib/gitlab/cluster/mixins/puma_cluster.rb'
- 'lib/gitlab/config/entry/composable_array.rb'
@@ -2051,9 +2047,6 @@ Style/InlineDisableAnnotation:
- 'qa/qa/service/shellout.rb'
- 'qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb'
- 'qa/qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/1_manage/integrations/jira_issues_list_spec.rb'
- 'qa/qa/specs/qa_deprecation_toolkit_env.rb'
- 'qa/qa/support/fips.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index e7609168123..8e856013cb6 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-b47256232c83b9a8b5811cc27c22801ab63ce879
+9277be2d60b30145b4afcbb171db3009d0beff7a
diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION
index 21047f66770..346bf99bcc4 100644
--- a/GITLAB_KAS_VERSION
+++ b/GITLAB_KAS_VERSION
@@ -1 +1 @@
-73665fe4d78f13031adb21ccd18fee421474b3e0
+7dd8b96b0d2229cad3ff85dc637ccda307062331
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 01ba5a601fb..6b13ddc17a0 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -62,7 +62,7 @@
{"name":"bigdecimal","version":"3.1.7","platform":"ruby","checksum":"e799b369a0005fc6d62eed7ef19139ac9bc319cc51470c637b9dcdf593600133"},
{"name":"bindata","version":"2.4.11","platform":"ruby","checksum":"c38e0c99ffcd80c10a0a7ae6c8586d2fe26bf245cbefac90bec8764523220f6a"},
{"name":"binding_of_caller","version":"1.0.0","platform":"ruby","checksum":"3aad25d1d538fc6e7972978f9bf512ccd992784009947c81633bea776713161d"},
-{"name":"bootsnap","version":"1.18.4","platform":"ruby","checksum":"ac4c42af397f7ee15521820198daeff545e4c360d2772c601fbdc2c07d92af55"},
+{"name":"bootsnap","version":"1.18.6","platform":"ruby","checksum":"0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00"},
{"name":"browser","version":"5.3.1","platform":"ruby","checksum":"62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c"},
{"name":"builder","version":"3.2.4","platform":"ruby","checksum":"99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10"},
{"name":"bullet","version":"7.2.0","platform":"ruby","checksum":"3502c8a1b4f5db77fc8f6d3dd89a6a8c1a968219a45e12ae6cbaa9c09967ea89"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 0f4056af3ee..0e896e27203 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -406,7 +406,7 @@ GEM
bindata (2.4.11)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
- bootsnap (1.18.4)
+ bootsnap (1.18.6)
msgpack (~> 1.2)
browser (5.3.1)
builder (3.2.4)
diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum
index 01ba5a601fb..6b13ddc17a0 100644
--- a/Gemfile.next.checksum
+++ b/Gemfile.next.checksum
@@ -62,7 +62,7 @@
{"name":"bigdecimal","version":"3.1.7","platform":"ruby","checksum":"e799b369a0005fc6d62eed7ef19139ac9bc319cc51470c637b9dcdf593600133"},
{"name":"bindata","version":"2.4.11","platform":"ruby","checksum":"c38e0c99ffcd80c10a0a7ae6c8586d2fe26bf245cbefac90bec8764523220f6a"},
{"name":"binding_of_caller","version":"1.0.0","platform":"ruby","checksum":"3aad25d1d538fc6e7972978f9bf512ccd992784009947c81633bea776713161d"},
-{"name":"bootsnap","version":"1.18.4","platform":"ruby","checksum":"ac4c42af397f7ee15521820198daeff545e4c360d2772c601fbdc2c07d92af55"},
+{"name":"bootsnap","version":"1.18.6","platform":"ruby","checksum":"0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00"},
{"name":"browser","version":"5.3.1","platform":"ruby","checksum":"62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c"},
{"name":"builder","version":"3.2.4","platform":"ruby","checksum":"99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10"},
{"name":"bullet","version":"7.2.0","platform":"ruby","checksum":"3502c8a1b4f5db77fc8f6d3dd89a6a8c1a968219a45e12ae6cbaa9c09967ea89"},
diff --git a/Gemfile.next.lock b/Gemfile.next.lock
index 0f4056af3ee..0e896e27203 100644
--- a/Gemfile.next.lock
+++ b/Gemfile.next.lock
@@ -406,7 +406,7 @@ GEM
bindata (2.4.11)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
- bootsnap (1.18.4)
+ bootsnap (1.18.6)
msgpack (~> 1.2)
browser (5.3.1)
builder (3.2.4)
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
index 703bf655b64..bee7f1cb6e0 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
@@ -1,4 +1,4 @@
-import { visitUrl, constructWebIDEPath } from '~/lib/utils/url_utility';
+import { visitUrl, constructWebIDEPath, appendLineRangeHashToUrl } from '~/lib/utils/url_utility';
import findAndFollowLink, { findAndFollowChildLink } from '~/lib/utils/navigation_utility';
import {
GO_TO_PROJECT_OVERVIEW,
@@ -54,7 +54,8 @@ export default class ShortcutsNavigation {
iid: window.gl.mrWidgetData?.iid,
});
if (path) {
- visitUrl(path, true);
+ const url = appendLineRangeHashToUrl(path);
+ visitUrl(url, true);
}
}
}
diff --git a/app/assets/javascripts/ide/init_gitlab_web_ide.js b/app/assets/javascripts/ide/init_gitlab_web_ide.js
index 731dbe8a528..f8a4df93f24 100644
--- a/app/assets/javascripts/ide/init_gitlab_web_ide.js
+++ b/app/assets/javascripts/ide/init_gitlab_web_ide.js
@@ -2,6 +2,7 @@ import { start } from '@gitlab/web-ide';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import Tracking from '~/tracking';
+import { getLineRangeFromHash } from '~/lib/utils/url_utility';
import {
getBaseConfig,
getOAuthConfig,
@@ -63,6 +64,8 @@ export const initGitlabWebIDE = async (el) => {
const isLanguageServerEnabled = gon?.features?.webIdeLanguageServer || false;
+ const lineRange = getLineRangeFromHash();
+
try {
// See ClientOnlyConfig https://gitlab.com/gitlab-org/gitlab-web-ide/-/blob/main/packages/web-ide-types/src/config.ts#L17
await start(rootEl, {
@@ -73,6 +76,7 @@ export const initGitlabWebIDE = async (el) => {
projectPath,
ref,
filePath,
+ lineRange,
mrId,
mrTargetProject: getMRTargetProject(),
forkInfo,
diff --git a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
index ab166b8fba1..447bed7f9d0 100644
--- a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
+++ b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
@@ -60,6 +60,8 @@ export default {
return {
isSelectedForReimport: false,
showMembershipsModal: false,
+ userHasSelectedNamespace: false,
+ showNamespaceRequiredError: false,
};
},
@@ -72,8 +74,13 @@ export default {
},
showMembershipsWarning() {
- const userNamespaceSelected = this.importTarget.targetNamespace === this.userNamespace;
- return (this.isImportNotStarted || this.isSelectedForReimport) && userNamespaceSelected;
+ const usersNamespaceIsSelected = this.importTarget.targetNamespace === this.userNamespace;
+
+ return this.isNotImporting && usersNamespaceIsSelected;
+ },
+
+ isNotImporting() {
+ return this.isImportNotStarted || this.isSelectedForReimport;
},
isFinished() {
@@ -128,6 +135,15 @@ export default {
this.updateImportTarget({ newName: value });
},
},
+ shouldBlockImportForNamespace() {
+ if (this.importTarget.targetNamespace) {
+ return false;
+ }
+
+ return (
+ !this.repo.importSource.target && this.isNotImporting && !this.userHasSelectedNamespace
+ );
+ },
},
methods: {
@@ -156,7 +172,9 @@ export default {
},
onImportClick() {
- if (this.showMembershipsWarning) {
+ if (this.shouldBlockImportForNamespace) {
+ this.showNamespaceRequiredError = true;
+ } else if (this.showMembershipsWarning) {
this.showMembershipsModal = true;
} else {
this.handleImportRepo();
@@ -164,6 +182,8 @@ export default {
},
onSelect(value) {
+ this.userHasSelectedNamespace = true;
+ this.showNamespaceRequiredError = false;
this.updateImportTarget({ targetNamespace: value });
},
},
@@ -209,6 +229,7 @@ export default {
@@ -224,6 +245,14 @@ export default {
data-testid="project-path-field"
/>
+
+ {{ s__('ImportProjects|Select a destination namespace.') }}
+
{{ displayFullPath }}
@@ -264,9 +293,11 @@ export default {
(repoId) => {
return {
newName: repo.importSource.sanitizedName,
- targetNamespace: state.defaultTargetNamespace,
+ targetNamespace: null,
};
};
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 6214db37166..a8a65284b55 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -831,3 +831,40 @@ export function stripRelativeUrlRootFromPath(path) {
return joinPaths('/', path.substring(relativeUrlRoot.length));
}
+
+const LINE_RANGE_HASH_REGEX = /^#L(\d+)(?:-(\d+))?$/;
+
+/**
+ * Retrieves the line number range from the url hash if it exists.
+ *
+ * @param {string} [hash=window.location.hash] - The URL hash string to parse. Defaults to the current window's location hash.
+ *
+ * @returns {{beginning: number, end: number}| null} An object containing the line number range selected,
+ * otherwise returns null
+ */
+export const getLineRangeFromHash = (hash = window.location.hash) => {
+ const match = hash.match(LINE_RANGE_HASH_REGEX);
+ if (!match || !match[1]) return null;
+
+ const beginning = parseInt(match[1], 10);
+ // If there's a second number (end of range), use it; otherwise use the start number
+ const end = match[2] ? parseInt(match[2], 10) : beginning;
+
+ return { beginning, end };
+};
+
+/**
+ * Appends the current window's line reference hash to a URL if it exists.
+ *
+ * @param {string} url - The base URL to which the line reference should be appended
+ * @param {string} [hash=window.location.hash] - The hash string to check and potentially append. Defaults to the current window's location hash.
+ * @returns {string} The URL with the line reference hash appended if the current window's
+ * hash matches the line reference pattern (#L or #L-),
+ * otherwise returns the original URL unchanged
+ */
+export function appendLineRangeHashToUrl(url, hash = window.location.hash) {
+ if (LINE_RANGE_HASH_REGEX.test(hash)) {
+ return url + hash;
+ }
+ return url;
+}
diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
index f0fc7cbd649..5e64fe773e1 100644
--- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue
+++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
@@ -6,7 +6,7 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { s__, __ } from '~/locale';
-import { visitUrl } from '~/lib/utils/url_utility';
+import { visitUrl, appendLineRangeHashToUrl } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
import ConfirmForkModal from '~/vue_shared/components/web_ide/confirm_fork_modal.vue';
import { keysFor, GO_TO_PROJECT_WEBIDE } from '~/behaviors/shortcuts/keybindings';
@@ -233,7 +233,8 @@ export default {
}
: {
handle: () => {
- visitUrl(this.webIdeUrl, true);
+ const url = appendLineRangeHashToUrl(this.webIdeUrl);
+ visitUrl(url, true);
},
};
diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml
index ad68a0587f2..b6d4dc6b9c9 100644
--- a/app/views/import/_githubish_status.html.haml
+++ b/app/views/import/_githubish_status.html.haml
@@ -3,7 +3,7 @@
- extra_data = local_assigns.fetch(:extra_data, {})
- filterable = local_assigns.fetch(:filterable, true)
- paginatable = local_assigns.fetch(:paginatable, false)
-- default_namespace_path = (local_assigns[:default_namespace] || current_user.namespace).full_path
+- default_namespace_path = local_assigns[:default_namespace]&.full_path
- cancel_path = local_assigns.fetch(:cancel_path, nil)
- details_path = local_assigns.fetch(:details_path, nil)
- provider_title = Gitlab::ImportSources.title(local_assigns.fetch(:provider))
diff --git a/db/migrate/20250523120208_add_schema_version_to_zoekt_node.rb b/db/migrate/20250523120208_add_schema_version_to_zoekt_node.rb
new file mode 100644
index 00000000000..24cfdee0322
--- /dev/null
+++ b/db/migrate/20250523120208_add_schema_version_to_zoekt_node.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddSchemaVersionToZoektNode < Gitlab::Database::Migration[2.3]
+ milestone '18.1'
+
+ def change
+ add_column :zoekt_nodes, :schema_version, :smallint, null: false, default: 0
+ end
+end
diff --git a/db/migrate/20250523120519_add_schema_version_to_zoekt_repository.rb b/db/migrate/20250523120519_add_schema_version_to_zoekt_repository.rb
new file mode 100644
index 00000000000..e4a654cfea0
--- /dev/null
+++ b/db/migrate/20250523120519_add_schema_version_to_zoekt_repository.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddSchemaVersionToZoektRepository < Gitlab::Database::Migration[2.3]
+ milestone '18.1'
+
+ def change
+ add_column :zoekt_repositories, :schema_version, :smallint, null: false, default: 0
+ end
+end
diff --git a/db/post_migrate/20250519065025_add_index_for_p_ci_pipeline_variables_on_project_id.rb b/db/post_migrate/20250519065025_add_index_for_p_ci_pipeline_variables_on_project_id.rb
new file mode 100644
index 00000000000..46f0cf8f8b2
--- /dev/null
+++ b/db/post_migrate/20250519065025_add_index_for_p_ci_pipeline_variables_on_project_id.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddIndexForPCiPipelineVariablesOnProjectId < Gitlab::Database::Migration[2.3]
+ include Gitlab::Database::PartitioningMigrationHelpers
+
+ disable_ddl_transaction!
+ milestone '18.1'
+
+ TABLE_NAME = :p_ci_pipeline_variables
+ INDEX_NAME = :index_p_ci_pipeline_variables_on_project_id
+ COLUMN_NAME = :project_id
+
+ def up
+ add_concurrent_partitioned_index(TABLE_NAME, COLUMN_NAME, name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_partitioned_index_by_name(TABLE_NAME, INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20250519065025 b/db/schema_migrations/20250519065025
new file mode 100644
index 00000000000..1d257edf5c7
--- /dev/null
+++ b/db/schema_migrations/20250519065025
@@ -0,0 +1 @@
+c245a850227686ec455e5da05a62e72c37df499286f245211f1f54269c304e85
\ No newline at end of file
diff --git a/db/schema_migrations/20250523120208 b/db/schema_migrations/20250523120208
new file mode 100644
index 00000000000..0a609d53794
--- /dev/null
+++ b/db/schema_migrations/20250523120208
@@ -0,0 +1 @@
+66a16305b024955bbc525a03118280c7986218cfbadfdd6938f28ca614d72fb2
\ No newline at end of file
diff --git a/db/schema_migrations/20250523120519 b/db/schema_migrations/20250523120519
new file mode 100644
index 00000000000..577aeaf32b4
--- /dev/null
+++ b/db/schema_migrations/20250523120519
@@ -0,0 +1 @@
+695693b64127b2e75eb3e038a78dc172735819232a7907a61d965418b4a370d8
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 0e61c9caef5..728968be951 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -26366,6 +26366,7 @@ CREATE TABLE zoekt_nodes (
indexed_bytes bigint DEFAULT 0 NOT NULL,
usable_storage_bytes bigint DEFAULT 0 NOT NULL,
usable_storage_bytes_locked_until timestamp with time zone,
+ schema_version smallint DEFAULT 0 NOT NULL,
CONSTRAINT check_32f39efba3 CHECK ((char_length(search_base_url) <= 1024)),
CONSTRAINT check_38c354a3c2 CHECK ((char_length(index_base_url) <= 1024))
);
@@ -26409,6 +26410,7 @@ CREATE TABLE zoekt_repositories (
size_bytes bigint DEFAULT 0 NOT NULL,
index_file_count integer DEFAULT 0 NOT NULL,
retries_left smallint DEFAULT 10 NOT NULL,
+ schema_version smallint DEFAULT 0 NOT NULL,
CONSTRAINT c_zoekt_repositories_on_project_id_and_project_identifier CHECK (((project_id IS NULL) OR (project_identifier = project_id))),
CONSTRAINT c_zoekt_repositories_on_retries_left CHECK (((retries_left > 0) OR ((retries_left = 0) AND (state >= 200))))
);
@@ -36541,6 +36543,8 @@ CREATE INDEX index_p_ci_job_annotations_on_project_id ON ONLY p_ci_job_annotatio
CREATE INDEX index_p_ci_job_artifact_reports_on_project_id ON ONLY p_ci_job_artifact_reports USING btree (project_id);
+CREATE INDEX index_p_ci_pipeline_variables_on_project_id ON ONLY p_ci_pipeline_variables USING btree (project_id);
+
CREATE INDEX index_p_ci_pipelines_config_on_project_id ON ONLY p_ci_pipelines_config USING btree (project_id);
CREATE INDEX index_p_ci_runner_machine_builds_on_project_id ON ONLY p_ci_runner_machine_builds USING btree (project_id);
diff --git a/doc/administration/backup_restore/backup_gitlab.md b/doc/administration/backup_restore/backup_gitlab.md
index 4794ae19f6a..9e872568669 100644
--- a/doc/administration/backup_restore/backup_gitlab.md
+++ b/doc/administration/backup_restore/backup_gitlab.md
@@ -27,16 +27,40 @@ For larger GitLab instances, alternative backup strategies include:
## Data included in a backup
+GitLab provides a command-line interface to back up your entire instance.
By default, the backup creates an archive in a single compressed tar file.
This file includes:
- Database data and configuration
-- Git repositories, container registry images, and uploaded content
-- Package registry data and CI/CD artifacts
- Account and group settings
-- Project and group wikis
-- Project-level secure files
-- External merge request diffs
+- CI/CD artifacts and job logs
+- Git repositories and LFS objects
+- External merge request diffs ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154914) in GitLab 17.1)
+- Package registry data and container registry images
+- Project and [group](../../user/project/wiki/group.md) wikis.
+- Project-level attachments and uploads
+- Secure Files ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121142) in GitLab 16.1)
+- GitLab Pages content
+- Terraform states
+- Snippets
+
+## Data not included in a backup
+
+- [Mattermost data](../../integration/mattermost/_index.md#back-up-gitlab-mattermost)
+- Redis (and thus Sidekiq jobs)
+- [Object storage](#object-storage) on Linux package (Omnibus) / Docker / Self-compiled installations
+
+- [Global server hooks](../server_hooks.md#create-global-server-hooks-for-all-repositories)
+- [File hooks](../file_hooks.md)
+- GitLab configuration files (`/etc/gitlab`)
+- TLS- and SSH-related keys and certificates
+- Other system files
+
+{{< alert type="warning" >}}
+
+You are highly advised to read about [storing configuration files](#storing-configuration-files) to back up those separately.
+
+{{< /alert >}}
## Simple backup procedure
@@ -196,41 +220,6 @@ GitLab uses Redis both as a cache store and to hold persistent data for our back
Elasticsearch is an optional database for advanced search. It can improve search
in both source-code level, and user generated content in issues, merge requests, and discussions. The [backup command](#backup-command) does _not_ back up Elasticsearch data. Elasticsearch data can be regenerated from PostgreSQL data after a restore. It is possible to [manually back up Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-restore.html).
-## Command-line interface
-
-GitLab provides a command-line interface to back up your entire instance,
-including:
-
-- Database
-- Attachments
-- Git repositories data
-- CI/CD job output logs
-- CI/CD job artifacts
-- LFS objects
-- Terraform states
-- Container registry images
-- GitLab Pages content
-- Packages
-- Snippets
-- [Group wikis](../../user/project/wiki/group.md)
-- Project-level Secure Files ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121142) in GitLab 16.1)
-- External merge request diffs ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154914) in GitLab 17.1)
-
-Backups do not include:
-
-- [Mattermost data](../../integration/mattermost/_index.md#back-up-gitlab-mattermost)
-- Redis (and thus Sidekiq jobs)
-- [Object storage](#object-storage) on Linux package (Omnibus) / Docker / Self-compiled installations
-- [Global server hooks](../server_hooks.md#create-global-server-hooks-for-all-repositories)
-- [File hooks](../file_hooks.md)
-
-{{< alert type="warning" >}}
-
-GitLab does not back up any configuration files (`/etc/gitlab`), TLS keys and certificates, or system
-files. You are highly advised to read about [storing configuration files](#storing-configuration-files).
-
-{{< /alert >}}
-
### Requirements
To be able to back up and restore, ensure that Rsync is installed on your
@@ -815,7 +804,7 @@ You can back up specific repositories using the `REPOSITORIES_PATHS` option.
Similarly, you can use `SKIP_REPOSITORIES_PATHS` to skip certain repositories.
Both options accept a comma-separated list of project or group paths. If you
specify a group path, all repositories in all projects in the group and
-descendent groups are included or skipped, depending on which option you used.
+descendant groups are included or skipped, depending on which option you used.
For example, to back up all repositories for all projects in Group A (`group-a`), the repository for
Project C in Group B (`group-b/project-c`),
diff --git a/doc/administration/backup_restore/restore_gitlab.md b/doc/administration/backup_restore/restore_gitlab.md
index 118b3e0c6ac..62ce5611471 100644
--- a/doc/administration/backup_restore/restore_gitlab.md
+++ b/doc/administration/backup_restore/restore_gitlab.md
@@ -65,10 +65,9 @@ Restore:
### Certain GitLab configuration must match the original backed up environment
-You likely also want to restore your previous `/etc/gitlab/gitlab.rb` (for Linux package installations)
+You likely want to separately restore your previous `/etc/gitlab/gitlab.rb` (for Linux package installations)
or `/home/git/gitlab/config/gitlab.yml` (for self-compiled installations) and
-any TLS keys, certificates (`/etc/gitlab/ssl`, `/etc/gitlab/trusted-certs`), or
-[SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+[any TLS or SSH keys and certificates](backup_gitlab.md#data-not-included-in-a-backup).
Certain configuration is coupled to data in PostgreSQL. For example:
@@ -453,7 +452,7 @@ that can cause data loss. The problem affects repositories that have been forked
You can restore specific repositories using the `REPOSITORIES_PATHS` and the `SKIP_REPOSITORIES_PATHS` options.
Both options accept a comma-separated list of project and group paths. If you
specify a group path, all repositories in all projects in the group and
-descendent groups are included or skipped, depending on which option you used.
+descendant groups are included or skipped, depending on which option you used.
Both the groups and projects must exist in the specified backup or on the target instance.
{{< alert type="note" >}}
diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md
index 76a87342699..5490d37b57b 100644
--- a/doc/api/container_registry.md
+++ b/doc/api/container_registry.md
@@ -448,7 +448,7 @@ curl --request DELETE \
## Instance-wide endpoints
-Beside the group- and project-specific GitLab APIs explained above,
+Beside the group- and project-specific GitLab APIs explained previously,
the container registry has its own endpoints.
To query those, follow the Registry's built-in mechanism to obtain and use an
[authentication token](https://distribution.github.io/distribution/spec/auth/token/).
diff --git a/doc/api/dora/metrics.md b/doc/api/dora/metrics.md
index f9048010f69..4b26d7e8c6a 100644
--- a/doc/api/dora/metrics.md
+++ b/doc/api/dora/metrics.md
@@ -99,7 +99,7 @@ Example response:
## The `value` field
-For both the project and group-level endpoints above, the `value` field in the
+For both the project and group-level endpoints described previously, the `value` field in the
API response has a different meaning depending on the provided `metric` query
parameter:
diff --git a/doc/api/license.md b/doc/api/license.md
index 69b7f2b358b..ab68ec43e32 100644
--- a/doc/api/license.md
+++ b/doc/api/license.md
@@ -134,7 +134,8 @@ Returns the following status codes:
Example request:
```shell
-curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/license/:id"
+curl --header "PRIVATE-TOKEN: " \
+--url "https://gitlab.example.com/api/v4/license/:id"
```
Example response:
@@ -175,7 +176,9 @@ POST /license
| `license` | string | yes | The license string |
```shell
-curl --request POST --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/license?license=eyJkYXRhIjoiMHM5Q...S01Udz09XG4ifQ=="
+curl --request POST \
+--header "PRIVATE-TOKEN: " \
+--url "https://gitlab.example.com/api/v4/license?license=eyJkYXRhIjoiMHM5Q...S01Udz09XG4ifQ=="
```
Example response:
@@ -221,7 +224,9 @@ DELETE /license/:id
| `id` | integer | yes | ID of the GitLab license. |
```shell
-curl --request DELETE --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/license/:id"
+curl --request DELETE \
+--header "PRIVATE-TOKEN: " \
+--url "https://gitlab.example.com/api/v4/license/:id"
```
Returns:
@@ -241,7 +246,9 @@ PUT /license/:id/refresh_billable_users
| `id` | integer | yes | ID of the GitLab license. |
```shell
-curl --request PUT --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/license/:id/refresh_billable_users"
+curl --request PUT \
+--header "PRIVATE-TOKEN: " \
+--url "https://gitlab.example.com/api/v4/license/:id/refresh_billable_users"
```
Example response:
@@ -271,7 +278,9 @@ GET /license/usage_export.csv
```
```shell
-curl --request GET --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/license/usage_export.csv"
+curl --request GET \
+--header "PRIVATE-TOKEN: " \
+--url "https://gitlab.example.com/api/v4/license/usage_export.csv"
```
Example response:
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index f84316e2437..9ffaf1cf10c 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -116,7 +116,7 @@ curl --request POST "https://gitlab.com/api/v4/projects/:id/snippets" \
-d @snippet.json
```
-`snippet.json` used in the above example request:
+`snippet.json` used in the previous example request:
```json
{
@@ -168,7 +168,7 @@ curl --request PUT "https://gitlab.com/api/v4/projects/:id/snippets/:snippet_id"
-d @snippet.json
```
-`snippet.json` used in the above example request:
+`snippet.json` used in the previous example request:
```json
{
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index 3bfda6c0741..f851f889f43 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -245,7 +245,7 @@ curl --request POST "https://gitlab.example.com/api/v4/snippets" \
-d @snippet.json
```
-`snippet.json` used in the above example request:
+`snippet.json` used in the previous example request:
```json
{
@@ -336,7 +336,7 @@ curl --request PUT "https://gitlab.example.com/api/v4/snippets/1" \
-d @snippet.json
```
-`snippet.json` used in the above example request:
+`snippet.json` used in the previous example request:
```json
{
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index 7261b959a8d..3aacd234a7c 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -891,17 +891,15 @@ use three periods with no space (`...`) instead of the `…` HTML entity or
For more information, see [code blocks](_index.md#code-blocks).
-Do not include any ellipses when documenting UI text.
-
For more information, see the [Microsoft Style Guide](https://learn.microsoft.com/en-us/style-guide/punctuation/ellipses).
-Use:
+Do not include any ellipses when documenting UI text. For example, use:
-- **Create new**
+- **Search or go to**
Instead of:
-- **Create new...**
+- **Search or go to...**
## email
diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md
index 6a9a89dfdf2..91be2af5a0a 100644
--- a/doc/operations/incident_management/integrations.md
+++ b/doc/operations/incident_management/integrations.md
@@ -363,7 +363,7 @@ curl --request POST \
{{< alert type="warning" >}}
Using your authorization key in the URL is insecure, as it's visible in server logs. We recommend
-using one of the above header options if your tooling supports it.
+using one of the header options described previously if your tooling supports it.
{{< /alert >}}
diff --git a/doc/subscriptions/gitlab_com/_index.md b/doc/subscriptions/gitlab_com/_index.md
index ac3bb2c413f..8e9b8ed4720 100644
--- a/doc/subscriptions/gitlab_com/_index.md
+++ b/doc/subscriptions/gitlab_com/_index.md
@@ -357,7 +357,7 @@ To purchase additional storage for your group on GitLab.com:
After your payment is processed, the extra storage is available for your group namespace.
-To confirm the available storage, follow the first three steps listed above.
+To confirm the available storage, follow the first three steps listed previously.
The **Purchased storage available** total is incremented by the amount purchased. All locked
projects are unlocked and their excess usage is deducted from the additional storage.
diff --git a/doc/user/analytics/value_streams_dashboard.md b/doc/user/analytics/value_streams_dashboard.md
index c01bb5d92e3..8a29acc415d 100644
--- a/doc/user/analytics/value_streams_dashboard.md
+++ b/doc/user/analytics/value_streams_dashboard.md
@@ -35,7 +35,7 @@ The Value Streams Dashboard includes panels that visualize the following metrics
With the Value Streams Dashboard, you can:
-- Track and compare the above metrics over a period of time.
+- Track and compare the metrics listed previously over a period of time.
- Identify downward trends early on.
- Understand security exposure.
- Drill down into individual projects or metrics to take actions for improvement.
diff --git a/doc/user/compliance/compliance_frameworks.md b/doc/user/compliance/compliance_frameworks.md
index 2be93cb1b5a..55c3d380daa 100644
--- a/doc/user/compliance/compliance_frameworks.md
+++ b/doc/user/compliance/compliance_frameworks.md
@@ -182,15 +182,13 @@ If the import is successful, the new compliance framework appears in the list. A
{{< /history >}}
-In GitLab Ultimate, you can define specific **requirements** for a compliance framework. Requirements are made up of one or more **controls**, which are checks against the configuration or behavior of projects that are assigned the framework. There is maximum of 5 controls per requirement.
+In GitLab Ultimate, you can define specific **requirements** for a compliance framework. Requirements are made up of one
+or more **controls**, which are checks against the configuration or behavior of projects that are assigned the framework. There is maximum of 5 controls per requirement.
-### Controls
+Each control includes logic that GitLab uses during scheduled or triggered scans to evaluate a project's adherence. For
+more details on how adherence is tracked, see [Compliance status report](compliance_center/compliance_status_report.md).
-Each control includes logic that GitLab uses during scheduled or triggered scans to evaluate a project's adherence. For more details on how adherence is tracked, see [Compliance status report](compliance_center/compliance_status_report.md).
-
-#### GitLab controls
-
-The following controls are available to use in framework requirements:
+You can use GitLab compliance controls or external controls for framework requirements.
### GitLab compliance controls
@@ -262,7 +260,7 @@ behavior of projects that are assigned to a compliance framework.
| Terraform enabled | `terraform_enabled` | Ensures that the [Terraform integration](../../administration/terraform_state.md) is enabled for the project. |
| Vulnerabilities SLO days over threshold | `vulnerabilities_slo_days_over_threshold` | Ensures that [vulnerabilities are addressed](../application_security/vulnerabilities/_index.md) inside SLO thresholds (180 days). |
-#### External controls
+### External controls
External controls are API calls to external systems that request the status of an external control or requirement.
diff --git a/doc/user/feature_flags.md b/doc/user/feature_flags.md
index 2619e995520..e5669f62d62 100644
--- a/doc/user/feature_flags.md
+++ b/doc/user/feature_flags.md
@@ -21,7 +21,7 @@ For help developing custom feature flags, see
If you don't see the feature flag tables below, view them at docs.gitlab.com.
-
+
diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md
index 1797645923a..d9a2c48f0e9 100644
--- a/doc/user/infrastructure/iac/terraform_state.md
+++ b/doc/user/infrastructure/iac/terraform_state.md
@@ -94,7 +94,7 @@ To configure GitLab CI/CD as a backend:
`gitlab-tofu plan` commands.
1. Trigger the manual `deploy` job from the previous pipeline. This action runs the `gitlab-tofu apply` command, which provisions the defined infrastructure.
-The output from the above commands should be viewable in the job logs.
+The output from the previous commands should be viewable in the job logs.
The `gitlab-tofu` CLI is a wrapper around the `tofu` CLI.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 34dc9492f16..430cd0f2069 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -860,10 +860,7 @@ URL references like `https://gitlab.com/gitlab-org/gitlab/-/issues/1234+s` are a
To update the rendered references if the assignee, milestone, or health status changed:
-- Edit the comment or description and save it.
-
-Issue [420807](https://gitlab.com/gitlab-org/gitlab/-/issues/420807) tracks improving how these
-references refresh.
+- Refresh the page.
### Comment preview on hover
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 08a69f875dd..1cbd0c28b23 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -32008,6 +32008,12 @@ msgstr ""
msgid "ImportProjects|Requesting your %{provider} repositories failed"
msgstr ""
+msgid "ImportProjects|Select a destination namespace."
+msgstr ""
+
+msgid "ImportProjects|Select namespace"
+msgstr ""
+
msgid "ImportProjects|Select the repositories you want to import"
msgstr ""
diff --git a/package.json b/package.json
index e7ff54148ae..ded02f5afd5 100644
--- a/package.json
+++ b/package.json
@@ -73,7 +73,7 @@
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@rails/actioncable": "7.1.501",
"@rails/ujs": "7.1.501",
- "@sentry/browser": "9.19.0",
+ "@sentry/browser": "9.22.0",
"@snowplow/browser-plugin-client-hints": "^3.24.2",
"@snowplow/browser-plugin-form-tracking": "^3.24.2",
"@snowplow/browser-plugin-ga-cookies": "^3.24.2",
@@ -157,7 +157,7 @@
"graphiql": "^3.7.1",
"graphql": "16.11.0",
"graphql-tag": "^2.11.0",
- "gridstack": "^11.1.2",
+ "gridstack": "^12.1.2",
"highlight.js": "^11.11.1",
"highlightjs-glimmer": "^2.2.2",
"immer": "^9.0.15",
diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb
index 175ae2fee8c..35c25966e87 100644
--- a/spec/features/import/manifest_import_spec.rb
+++ b/spec/features/import/manifest_import_spec.rb
@@ -34,7 +34,6 @@ RSpec.describe 'Import multiple repositories by uploading a manifest file', :js,
page.within(second_row) do
click_on 'Import'
end
- click_on 'Continue import'
wait_for_requests
diff --git a/spec/frontend/ide/init_gitlab_web_ide_spec.js b/spec/frontend/ide/init_gitlab_web_ide_spec.js
index f815efed4d3..341cb11ea0e 100644
--- a/spec/frontend/ide/init_gitlab_web_ide_spec.js
+++ b/spec/frontend/ide/init_gitlab_web_ide_spec.js
@@ -31,6 +31,8 @@ const TEST_BRANCH_NAME = '12345-foo-patch';
const TEST_USER_PREFERENCES_PATH = '/user/preferences';
const TEST_GITLAB_WEB_IDE_PUBLIC_PATH = 'test/webpack/assets/gitlab-web-ide/public/path';
const TEST_FILE_PATH = 'foo/README.md';
+const TEST_LINE_NUMBER = { beginning: 42, end: 42 };
+const TEST_LINE_RANGE = { beginning: 42, end: 46 };
const TEST_MR_ID = '7';
const TEST_MR_TARGET_PROJECT = 'gitlab-org/the-real-gitlab';
const TEST_SIGN_IN_PATH = 'sign-in';
@@ -124,6 +126,7 @@ describe('ide/init_gitlab_web_ide', () => {
projectPath: TEST_PROJECT_PATH,
ref: TEST_BRANCH_NAME,
filePath: TEST_FILE_PATH,
+ lineRange: null,
mrId: TEST_MR_ID,
mrTargetProject: '',
forkInfo: null,
@@ -193,6 +196,39 @@ describe('ide/init_gitlab_web_ide', () => {
});
});
+ describe('when URL has lineNumber in hash', () => {
+ beforeEach(() => {
+ setWindowLocation(`https://example.com/-/ide/path/to/file#L42`);
+
+ createSubject();
+ });
+
+ it('includes line number', () => {
+ expect(start).toHaveBeenCalledWith(
+ findRootElement(),
+ expect.objectContaining({
+ lineRange: TEST_LINE_NUMBER,
+ }),
+ );
+ });
+ });
+
+ describe('when URL has lineRange in hash', () => {
+ beforeEach(() => {
+ setWindowLocation(`https://example.com/-/ide/path/to/file#L42-46`);
+
+ createSubject();
+ });
+ it('includes line range', () => {
+ expect(start).toHaveBeenCalledWith(
+ findRootElement(),
+ expect.objectContaining({
+ lineRange: TEST_LINE_RANGE,
+ }),
+ );
+ });
+ });
+
describe('when forkInfo is in dataset', () => {
beforeEach(() => {
findRootElement().dataset.forkInfo = JSON.stringify(TEST_FORK_INFO);
diff --git a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
index a1355706566..ffe3953a405 100644
--- a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
@@ -1,4 +1,4 @@
-import { GlBadge, GlButton, GlModal } from '@gitlab/ui';
+import { GlBadge, GlButton } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
@@ -15,7 +15,7 @@ describe('ProviderRepoTableRow', () => {
const cancelImport = jest.fn();
const setImportTarget = jest.fn();
const groupImportTarget = {
- targetNamespace: 'target',
+ targetNamespace: null,
newName: 'newName',
};
@@ -45,7 +45,8 @@ describe('ProviderRepoTableRow', () => {
const findImportStatus = () => wrapper.findComponent(ImportStatus);
const findProviderLink = () => wrapper.findByTestId('provider-link');
const findMembershipsWarning = () => wrapper.findByTestId('memberships-warning');
- const findGlModal = () => wrapper.findComponent(GlModal);
+ const findMembershipsWarningModal = () => wrapper.findByTestId('memberships-warning-modal');
+ const findNamespaceRequiredWarning = () => wrapper.findByTestId('namespace-required-warning');
const findCancelButton = () => {
const buttons = wrapper
@@ -97,12 +98,37 @@ describe('ProviderRepoTableRow', () => {
expect(findImportTargetDropdown().exists()).toBe(true);
});
+ it('renders the dropdown without a default selected', () => {
+ expect(findImportTargetDropdown().props('selected')).toBe(null);
+ expect(findImportTargetDropdown().props().toggleText).toBe('Select namespace');
+ });
+
+ describe('when no namespace is selected', () => {
+ it('shows namespace required warning when import button is clicked', async () => {
+ findImportButton().vm.$emit('click');
+ await nextTick();
+
+ expect(findNamespaceRequiredWarning().text()).toBe('Select a destination namespace.');
+ });
+
+ it('does not trigger import when clicking import button', async () => {
+ findImportButton().vm.$emit('click');
+ await nextTick();
+
+ expect(fetchImport).not.toHaveBeenCalled();
+ });
+ });
+
describe('when user namespace is selected as import target', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mountComponent(
{ repo },
{ storeOptions: { importTarget: { targetNamespace: userNamespace } } },
);
+
+ const dropdown = findImportTargetDropdown();
+ dropdown.vm.$emit('select', userNamespace);
+ await nextTick();
});
it('shows memberships warning', () => {
@@ -113,7 +139,7 @@ describe('ProviderRepoTableRow', () => {
findImportButton().vm.$emit('click');
await nextTick();
- const modal = findGlModal();
+ const modal = findMembershipsWarningModal();
expect(modal.props('title')).toBe(
'Are you sure you want to import the project to a personal namespace?',
);
@@ -122,11 +148,15 @@ describe('ProviderRepoTableRow', () => {
);
});
+ it('does not show `missing namespace` warning', () => {
+ expect(findNamespaceRequiredWarning().exists()).toBe(false);
+ });
+
it('triggers import when clicking modal primary button', async () => {
findImportButton().vm.$emit('click');
await nextTick();
- findGlModal().vm.$emit('primary');
+ findMembershipsWarningModal().vm.$emit('primary');
expect(fetchImport).toHaveBeenCalledWith(expect.anything(), {
repoId: repo.importSource.id,
@@ -136,53 +166,68 @@ describe('ProviderRepoTableRow', () => {
});
describe('when group namespace is selected as import target', () => {
+ beforeEach(async () => {
+ const dropdown = findImportTargetDropdown();
+ dropdown.vm.$emit('select', 'target');
+ await nextTick();
+ });
+
it('does not show memberships warning', () => {
expect(findMembershipsWarning().isVisible()).toBe(false);
});
- it('does not show modal when import button is clicked', async () => {
+ it('does not show memberships modal when import button is clicked', async () => {
findImportButton().vm.$emit('click');
await nextTick();
- expect(findGlModal().exists()).toBe(false);
- });
- });
-
- it('renders import button', () => {
- expect(findImportButton().exists()).toBe(true);
- });
-
- it('imports repo when clicking import button', async () => {
- findImportButton().vm.$emit('click');
-
- await nextTick();
-
- expect(fetchImport).toHaveBeenCalledWith(expect.anything(), {
- repoId: repo.importSource.id,
- optionalStages: {},
- });
- });
-
- it('includes optionalStages to import', async () => {
- const OPTIONAL_STAGES = { stage1: true, stage2: false };
-
- mountComponent({
- repo,
- optionalStages: OPTIONAL_STAGES,
+ expect(findMembershipsWarningModal().exists()).toBe(false);
});
- findImportButton().vm.$emit('click');
-
- await nextTick();
-
- expect(fetchImport).toHaveBeenCalledWith(expect.anything(), {
- repoId: repo.importSource.id,
- optionalStages: OPTIONAL_STAGES,
+ it('does not show `missing namespace` warning when import button is clicked', async () => {
+ findImportButton().vm.$emit('click');
+ await nextTick();
+ expect(findNamespaceRequiredWarning().exists()).toBe(false);
});
- });
- it('does not render re-import button', () => {
- expect(findReimportButton().exists()).toBe(false);
+ it('renders import button', () => {
+ expect(findImportButton().exists()).toBe(true);
+ });
+
+ it('imports repo when clicking import button', async () => {
+ findImportButton().vm.$emit('click');
+
+ await nextTick();
+
+ expect(fetchImport).toHaveBeenCalledWith(expect.anything(), {
+ repoId: repo.importSource.id,
+ optionalStages: {},
+ });
+ });
+
+ it('includes optionalStages to import', async () => {
+ const OPTIONAL_STAGES = { stage1: true, stage2: false };
+
+ mountComponent({
+ repo,
+ optionalStages: OPTIONAL_STAGES,
+ });
+
+ const dropdown = findImportTargetDropdown();
+ dropdown.vm.$emit('select', 'target');
+ await nextTick();
+
+ findImportButton().vm.$emit('click');
+ await nextTick();
+
+ expect(fetchImport).toHaveBeenCalledWith(expect.anything(), {
+ repoId: repo.importSource.id,
+ optionalStages: OPTIONAL_STAGES,
+ });
+ });
+
+ it('does not render re-import button', () => {
+ expect(findReimportButton().exists()).toBe(false);
+ });
});
});
@@ -290,7 +335,12 @@ describe('ProviderRepoTableRow', () => {
await nextTick();
+ findImportTargetDropdown().vm.$emit('select', 'some-namespace');
+ await nextTick();
+
findReimportButton().vm.$emit('click');
+ await nextTick();
+ expect(findNamespaceRequiredWarning().exists()).toBe(false);
expect(fetchImport).toHaveBeenCalledWith(expect.anything(), {
repoId: repo.importSource.id,
diff --git a/spec/frontend/import_entities/import_projects/store/actions_spec.js b/spec/frontend/import_entities/import_projects/store/actions_spec.js
index 918821dfa59..aa57ae9f041 100644
--- a/spec/frontend/import_entities/import_projects/store/actions_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/actions_spec.js
@@ -55,7 +55,7 @@ describe('import_projects store actions', () => {
let localState;
const importRepoId = 1;
const otherImportRepoId = 2;
- const defaultTargetNamespace = 'default';
+ const defaultTargetNamespace = null;
const sanitizedName = 'sanitizedName';
const defaultImportTarget = { newName: sanitizedName, targetNamespace: defaultTargetNamespace };
diff --git a/spec/frontend/import_entities/import_projects/store/getters_spec.js b/spec/frontend/import_entities/import_projects/store/getters_spec.js
index fced5670f25..e25a6e09928 100644
--- a/spec/frontend/import_entities/import_projects/store/getters_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/getters_spec.js
@@ -99,7 +99,7 @@ describe('import_projects store getters', () => {
expect(getImportTarget(localState)(IMPORTABLE_REPO.importSource.id)).toStrictEqual({
newName: IMPORTABLE_REPO.importSource.sanitizedName,
- targetNamespace: localState.defaultTargetNamespace,
+ targetNamespace: null,
});
});
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index d883800018a..36cf2d8aa80 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -2,7 +2,11 @@ import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import * as urlUtils from '~/lib/utils/url_utility';
import { setGlobalAlerts } from '~/lib/utils/global_alerts';
-import { setLocationHash } from '~/lib/utils/url_utility';
+import {
+ appendLineRangeHashToUrl,
+ getLineRangeFromHash,
+ setLocationHash,
+} from '~/lib/utils/url_utility';
import { validURLs, invalidURLs } from './mock_data';
jest.mock('~/lib/utils/global_alerts', () => ({
@@ -476,6 +480,61 @@ describe('URL utility', () => {
});
});
+ describe('getLineRangeFromHash', () => {
+ beforeEach(() => {
+ window.location.hash = '';
+ });
+
+ it('returns null when no line number hash is present', () => {
+ expect(getLineRangeFromHash()).toBe(null);
+ });
+
+ it('returns line number when hash is present in URL', () => {
+ window.location.hash = '#L42';
+ expect(getLineRangeFromHash()).toStrictEqual({ beginning: 42, end: 42 });
+ });
+
+ it('returns line number range when hash is present in URL', () => {
+ window.location.hash = '#L10-42';
+ expect(getLineRangeFromHash()).toStrictEqual({ beginning: 10, end: 42 });
+ });
+
+ it('returns null when hash is not a line number', () => {
+ window.location.hash = '#L42InvalidHash123';
+ expect(getLineRangeFromHash()).toBe(null);
+ });
+ });
+
+ describe('appendLineRangeHashToUrl', () => {
+ beforeEach(() => {
+ window.location.hash = '';
+ });
+
+ it('appends line number hash when present in URL', () => {
+ window.location.hash = '#L42';
+ expect(appendLineRangeHashToUrl('https://example.com/ide')).toBe(
+ 'https://example.com/ide#L42',
+ );
+ });
+
+ it('appends line number range hash when present in URL', () => {
+ window.location.hash = '#L10-42';
+ expect(appendLineRangeHashToUrl('https://example.com/ide')).toBe(
+ 'https://example.com/ide#L10-42',
+ );
+ });
+
+ it('returns original URL when no line number hash is present', () => {
+ window.location.hash = '#something-else';
+ expect(appendLineRangeHashToUrl('https://example.com/ide')).toBe('https://example.com/ide');
+ });
+
+ it('returns original URL when hash is not a line number', () => {
+ window.location.hash = '#L42InvalidHash123';
+ expect(appendLineRangeHashToUrl('https://example.com/ide')).toBe('https://example.com/ide');
+ });
+ });
+
describe('visitUrl', () => {
let originalLocation;
const mockUrl = 'http://example.com/page';
diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js
index 5e4a45b083b..0d408908f00 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -16,7 +16,7 @@ import {
extendedWrapper,
} from 'helpers/vue_test_utils_helper';
-import { visitUrl } from '~/lib/utils/url_utility';
+import { visitUrl, appendLineRangeHashToUrl } from '~/lib/utils/url_utility';
import getWritableForksQuery from '~/vue_shared/components/web_ide/get_writable_forks.query.graphql';
jest.mock('~/lib/utils/url_utility');
@@ -299,9 +299,12 @@ describe('vue_shared/components/web_ide_link', () => {
});
it('when web ide button is clicked it opens in a new tab', async () => {
+ const transformation = 'foo';
+ appendLineRangeHashToUrl.mockReturnValueOnce(transformation);
findDisclosureDropdownItems().at(1).props().item.handle();
await nextTick();
- expect(visitUrl).toHaveBeenCalledWith(TEST_WEB_IDE_URL, true);
+ expect(appendLineRangeHashToUrl).toHaveBeenCalledWith(TEST_WEB_IDE_URL);
+ expect(visitUrl).toHaveBeenCalledWith(transformation, true);
});
});
diff --git a/spec/support_specs/matchers/internal_events_matchers_spec.rb b/spec/support_specs/matchers/internal_events_matchers_spec.rb
index 0a07e5092eb..4d87b222ffe 100644
--- a/spec/support_specs/matchers/internal_events_matchers_spec.rb
+++ b/spec/support_specs/matchers/internal_events_matchers_spec.rb
@@ -498,6 +498,200 @@ RSpec.describe 'Internal Events matchers', :clean_gitlab_redis_shared_state, fea
.and change { user_1.reload.updated_at }
.and not_change { User.count }
end
+
+ describe 'instrumentation and testing contexts' do
+ shared_examples 'works correctly' do
+ specify do
+ expect { subject }
+ .to trigger_internal_events('g_edit_by_sfe')
+ .and increment_usage_metrics('redis_hll_counters.ide_edit.g_edit_by_sfe_weekly')
+ end
+ end
+
+ subject { track_event }
+
+ context 'with retries', retry: 2 do
+ context "with independent failure after the event" do
+ subject do
+ track_event
+
+ raise 'Fail first attempt, causing a retry' if RSpec.current_example.attempts == 0
+ end
+
+ it_behaves_like "works correctly"
+ end
+
+ context "with independent failure before the event" do
+ subject do
+ raise 'Fail first attempt, causing a retry' if RSpec.current_example.attempts == 0
+
+ track_event
+ end
+
+ it_behaves_like "works correctly"
+ end
+
+ context "with criteria met only on retry" do
+ subject do
+ track_event if RSpec.current_example.attempts > 0
+ end
+
+ it_behaves_like "works correctly"
+ end
+ end
+
+ context 'without clearing Redis state between examples', clean_gitlab_redis_shared_state: false do
+ it_behaves_like "works correctly"
+ end
+
+ context 'with snowplow not stubbed by default', :do_not_stub_snowplow_by_default do
+ it_behaves_like "works correctly"
+ end
+
+ context 'with aggregate_failures', :aggregate_failures do
+ context "with no failures" do
+ it_behaves_like "works correctly"
+ end
+
+ context "with failures" do
+ it "fails" do
+ pending('This example should always fail. Protects against false positives.')
+
+ # we can't test the error message because :aggregate_failures rescues errors
+ expect { nil }.to(trigger_internal_events('g_edit_by_sfe'))
+ expect { nil }.to(increment_usage_metrics('redis_hll_counters.ide_edit.g_edit_by_sfe_weekly'))
+ end
+ end
+ end
+
+ context 'when triggered from controllers' do
+ context 'when using controller specs', type: :controller do
+ controller(BaseActionController) do
+ include ProductAnalyticsTracking
+
+ track_internal_event :index, name: 'g_edit_by_sfe'
+
+ def index
+ render html: 'index page'
+ end
+
+ private
+
+ def tracking_project_source; end
+
+ def tracking_namespace_source; end
+ end
+
+ subject do
+ sign_in user_1
+ get :index
+ end
+
+ it_behaves_like "works correctly"
+ end
+
+ context 'when triggered from request specs', type: :request do
+ # ideally, this spec should be completed with a new controller generated inside the test
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user, maintainer_of: project) }
+
+ subject do
+ sign_in(user)
+ post namespace_project_create_blob_path(
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 'master'
+ ), params: {
+ branch_name: 'master',
+ file_name: 'docs/EXAMPLE_FILE',
+ content: 'Added changes',
+ commit_message: 'Create CHANGELOG'
+ }
+ end
+
+ it_behaves_like "works correctly"
+ end
+
+ context 'when triggered from rails model lifecycle hooks' do
+ let(:test_model) do
+ Class.new(ApplicationRecord) do
+ self.table_name = :merge_requests
+
+ after_initialize do
+ Gitlab::InternalEvents.track_event(
+ 'create_merge_request',
+ user: user,
+ project: project
+ )
+ end
+
+ attr_accessor :user, :project
+ end
+ end
+
+ it 'works correctly' do
+ expect { test_model.new(user: user_1, project: project_1) }
+ .to trigger_internal_events('create_merge_request')
+ .with(user: user_1, project: project_1)
+ .and increment_usage_metrics('counts.merge_request_create')
+ end
+ end
+
+ context 'when triggered from sidekiq worker', :sidekiq_inline do
+ let(:test_worker) do
+ Class.new do
+ # `include ApplicationWorker` raises errors for unnamed classes
+ def self.name
+ 'Gitlab::TestUsageWorker'
+ end
+
+ include ApplicationWorker
+
+ def perform(event, user_id, namespace_id)
+ Gitlab::InternalEvents.track_event(
+ event,
+ user: User.find(user_id),
+ namespace: Group.find(namespace_id)
+ )
+ end
+ end
+ end
+
+ subject do
+ stub_const('Gitlab::TestUsageWorker', test_worker)
+ test_worker.perform_async(
+ 'g_edit_by_sfe',
+ user_1.id,
+ group_1.id
+ )
+ end
+
+ it_behaves_like "works correctly"
+ end
+
+ context 'when triggered from migrations' do
+ let(:migration) do
+ Class.new(Gitlab::Database::Migration[Gitlab::Database::Migration.current_version]) do
+ milestone '18.0'
+
+ @_defining_file = 'db/migrate/00000000000000_example.rb'
+
+ def up
+ Gitlab::InternalEvents.track_event(
+ 'g_edit_by_sfe',
+ user: User.first,
+ namespace: Group.first
+ )
+ end
+ end
+ end
+
+ subject { migration.new.up }
+
+ it_behaves_like "works correctly"
+ end
+ end
+ end
end
context "when using the 'internal event tracking' shared example" do
diff --git a/storybook/config/webpack.config.js b/storybook/config/webpack.config.js
index d2cd42a71c6..44c87d9a544 100644
--- a/storybook/config/webpack.config.js
+++ b/storybook/config/webpack.config.js
@@ -153,7 +153,6 @@ module.exports = function storybookWebpackConfig({ config }) {
);
config.resolve.alias = {
...config.resolve.alias,
- gridstack: require.resolve('gridstack/dist/es5/gridstack.js'),
'@cubejs-client/core': require.resolve('@cubejs-client/core/dist/cubejs-client-core.js'),
uuid: require.resolve('uuid'),
'/assets': path.resolve(__dirname, '../../app/assets'),
diff --git a/yarn.lock b/yarn.lock
index 623e3fde527..bae0e7d979f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2591,51 +2591,51 @@
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
-"@sentry-internal/browser-utils@9.19.0":
- version "9.19.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.19.0.tgz#b7df4f1de6e467e62f255967e441de68b5db5c2f"
- integrity sha512-DlEHX4eIHe5yIuh/cFu9OiaFuk1CTnFK95zj61I7Q2fxmN43dIwC3xAAGJ/Hy+GDQi7kU+BiS2sudSHSTq81BA==
+"@sentry-internal/browser-utils@9.22.0":
+ version "9.22.0"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.22.0.tgz#dd05ac2ed925b70a0ca9bdec472f2566761dad91"
+ integrity sha512-Ou1tBnVxFAIn8i9gvrWzRotNJQYiu3awNXpsFCw6qFwmiKAVPa6b13vCdolhXnrIiuR77jY1LQnKh9hXpoRzsg==
dependencies:
- "@sentry/core" "9.19.0"
+ "@sentry/core" "9.22.0"
-"@sentry-internal/feedback@9.19.0":
- version "9.19.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.19.0.tgz#a3bf7a7a14d615eb3b8130a97b2c077767a54b27"
- integrity sha512-yixRrv4NfpjhFW56AuUTjVwZlignB9FWAXXyrmRP3SsFeJCFrAsSD8HOxV9RXNr9ePYl7MEU0Agi43YWhJsiAw==
+"@sentry-internal/feedback@9.22.0":
+ version "9.22.0"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.22.0.tgz#760f86903fde91a840bc77fc104b39a873301983"
+ integrity sha512-zgMVkoC61fgi41zLcSZA59vOtKxcLrKBo1ECYhPD1hxEaneNqY5fhXDwlQBw96P5l2yqkgfX6YZtSdU4ejI9yA==
dependencies:
- "@sentry/core" "9.19.0"
+ "@sentry/core" "9.22.0"
-"@sentry-internal/replay-canvas@9.19.0":
- version "9.19.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.19.0.tgz#0f830ebadb7c49a3fb04457f1510aa7972445780"
- integrity sha512-YC8yrOjuKSfQgGniJnzkdbFsWEPTlNpzeeYPTxS4ouH1FwfGrSkPmcddjor2YHaLfiuHHqQ/Vvq70n+zruJH7A==
+"@sentry-internal/replay-canvas@9.22.0":
+ version "9.22.0"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.22.0.tgz#431e289a5db247265e640a1e7aecf9ff2dba6a70"
+ integrity sha512-EcG9IMSEalFe49kowBTJObWjof/iHteDwpyuAszsFDdQUYATrVUtwpwN7o52vDYWJud4arhjrQnMamIGxa79eQ==
dependencies:
- "@sentry-internal/replay" "9.19.0"
- "@sentry/core" "9.19.0"
+ "@sentry-internal/replay" "9.22.0"
+ "@sentry/core" "9.22.0"
-"@sentry-internal/replay@9.19.0":
- version "9.19.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.19.0.tgz#a1068ba5ba8eee9cd2459081be6e11bd3469d401"
- integrity sha512-i/X9brRchbAF25yjxLTI7E8eoESRPBgIyQOWoWRXXt2n51iBRTjLXSaEfGvjdN+qrMq/yd6nC1/UqJVxXHeIhA==
+"@sentry-internal/replay@9.22.0":
+ version "9.22.0"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.22.0.tgz#e67ed6cee4cf481d428986e4419ee099669df9ef"
+ integrity sha512-9GOycoKbrclcRXfcbNV8svbmAsOS5R4wXBQmKF4pFLkmFA/lJv9kdZSNYkRvkrxdNfbMIJXP+DV9EqTZcryXig==
dependencies:
- "@sentry-internal/browser-utils" "9.19.0"
- "@sentry/core" "9.19.0"
+ "@sentry-internal/browser-utils" "9.22.0"
+ "@sentry/core" "9.22.0"
-"@sentry/browser@9.19.0":
- version "9.19.0"
- resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.19.0.tgz#17d5e2ef30e75083c56955cdbe5c2db5deabb542"
- integrity sha512-efKfPQ0yQkdIkC7qJ5TIHxnecLNENGUYl1YD/TC8yyzW2JRf/3OYo5yg1hY2rhsP5RwQShXlT7uA03ABVIkA4A==
+"@sentry/browser@9.22.0":
+ version "9.22.0"
+ resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.22.0.tgz#6eee0c047612b35e7ab4960350a57ffcc51f76f4"
+ integrity sha512-3TeRm74dvX0JdjX0AgkQa+22iUHwHnY+Q6M05NZ+tDeCNHGK/mEBTeqquS1oQX67jWyuvYmG3VV6RJUxtG9Paw==
dependencies:
- "@sentry-internal/browser-utils" "9.19.0"
- "@sentry-internal/feedback" "9.19.0"
- "@sentry-internal/replay" "9.19.0"
- "@sentry-internal/replay-canvas" "9.19.0"
- "@sentry/core" "9.19.0"
+ "@sentry-internal/browser-utils" "9.22.0"
+ "@sentry-internal/feedback" "9.22.0"
+ "@sentry-internal/replay" "9.22.0"
+ "@sentry-internal/replay-canvas" "9.22.0"
+ "@sentry/core" "9.22.0"
-"@sentry/core@9.19.0":
- version "9.19.0"
- resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.19.0.tgz#b0c291494abd4fdefb8f77d200e381c3f7167c3f"
- integrity sha512-I41rKpMJHHZb0z0Nja+Lxto6IkEEmX3uWjnECypF8Z1HIjeJB0+PXl8p/7TeaKYqw2J2GYcRTg7jQZDmvKle1w==
+"@sentry/core@9.22.0":
+ version "9.22.0"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.22.0.tgz#b35113bcba24a6ae437c548c2cbac941302a60ef"
+ integrity sha512-ixvtKmPF42Y6ckGUbFlB54OWI75H2gO5UYHojO6eXFpS7xO3ZGgV/QH6wb40mWK+0w5XZ0233FuU9VpsuE6mKA==
"@sinclair/typebox@^0.27.8":
version "0.27.8"
@@ -8557,10 +8557,10 @@ graphql@16.11.0:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.11.0.tgz#96d17f66370678027fdf59b2d4c20b4efaa8a633"
integrity sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==
-gridstack@^11.1.2:
- version "11.1.2"
- resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-11.1.2.tgz#e72091e2883f7b37cbd150c218d38eebc9fc4f18"
- integrity sha512-6wJ5RffnFchF63/Yhs6tcZcWxRG1EgCnxgejbQsAjQ6Qj8QqKjew73jPq5c1yCAiyEAsXxI2tOJ8lZABOAZxoQ==
+gridstack@^12.1.2:
+ version "12.1.2"
+ resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-12.1.2.tgz#784f6d55873bb48fa9230c1284f769c9fbf785a8"
+ integrity sha512-IC1mkm5xonhAnftwIxsG+B3bawxC61ciKWEvX15ExpVQPbNVN7O9aZZhM7Y/eE4JaIR8PXrdkjd12gMnwNYRLQ==
gzip-size@^6.0.0:
version "6.0.0"