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--.gitlab/ci/as-if-jh.gitlab-ci.yml16
-rw-r--r--.rubocop_todo/rspec/missing_feature_category.yml150
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum1
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/deprecated_notes.js57
-rw-r--r--app/assets/javascripts/incidents/constants.js1
-rw-r--r--app/assets/javascripts/init_deprecated_notes.js4
-rw-r--r--app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue7
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue7
-rw-r--r--app/assets/javascripts/issues/list/constants.js1
-rw-r--r--app/assets/javascripts/issues/list/utils.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js2
-rw-r--r--app/graphql/types/ci/job_type.rb18
-rw-r--r--app/graphql/types/ci/runner_machine_type.rb4
-rw-r--r--app/helpers/notes_helper.rb1
-rw-r--r--app/models/bulk_imports/batch_tracker.rb46
-rw-r--r--app/models/bulk_imports/export.rb1
-rw-r--r--app/models/bulk_imports/export_batch.rb33
-rw-r--r--app/models/bulk_imports/export_upload.rb1
-rw-r--r--app/models/bulk_imports/tracker.rb2
-rw-r--r--app/models/ci/runner_machine_build.rb8
-rw-r--r--app/models/integration.rb3
-rw-r--r--app/models/integrations/squash_tm.rb82
-rw-r--r--app/models/preloaders/runner_machine_policy_preloader.rb23
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/user.rb4
-rw-r--r--app/services/concerns/update_repository_storage_methods.rb9
-rw-r--r--app/services/packages/debian/generate_distribution_service.rb2
-rw-r--r--app/services/projects/update_repository_storage_service.rb13
-rw-r--r--app/services/users/validate_manual_otp_service.rb3
-rw-r--r--app/views/admin/background_migrations/index.html.haml2
-rw-r--r--app/views/dashboard/projects/_blank_state_admin_welcome.html.haml8
-rw-r--r--app/views/dashboard/projects/_blank_state_welcome.html.haml8
-rw-r--r--app/views/groups/_import_group_from_file_panel.html.haml2
-rw-r--r--app/views/groups/settings/_export.html.haml3
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/search/_results_list.html.haml2
-rw-r--r--app/workers/packages/debian/generate_distribution_worker.rb2
-rw-r--r--config/feature_flags/development/search_index_integrity.yml (renamed from config/feature_flags/development/repack_after_shard_migration.yml)10
-rw-r--r--config/gitlab.yml.example10
-rw-r--r--config/initializers/1_settings.rb6
-rw-r--r--config/metrics/counts_all/20230303131933_groups_inheriting_squash_tm_active.yml22
-rw-r--r--config/metrics/counts_all/20230303131936_groups_squash_tm_active.yml22
-rw-r--r--config/metrics/counts_all/20230303132041_instances_squash_tm_active.yml22
-rw-r--r--config/metrics/counts_all/20230303132048_projects_inheriting_squash_tm_active.yml22
-rw-r--r--config/metrics/counts_all/20230303132352_projects_squash_tm_active.yml22
-rw-r--r--config/sidekiq_queues.yml4
-rw-r--r--doc/administration/docs_self_host.md2
-rw-r--r--doc/administration/instance_limits.md2
-rw-r--r--doc/administration/reference_architectures/10k_users.md42
-rw-r--r--doc/administration/reference_architectures/25k_users.md42
-rw-r--r--doc/administration/reference_architectures/50k_users.md42
-rw-r--r--doc/api/graphql/reference/index.md13
-rw-r--r--doc/api/integrations.md37
-rw-r--r--doc/architecture/blueprints/cells/index.md2
-rw-r--r--doc/architecture/blueprints/clickhouse_usage/index.md4
-rw-r--r--doc/development/cicd/cicd_tables.md4
-rw-r--r--doc/development/contributing/merge_request_workflow.md2
-rw-r--r--doc/development/distribution/index.md35
-rw-r--r--doc/integration/jira/connect-app.md26
-rw-r--r--doc/subscriptions/gitlab_dedicated/index.md2
-rw-r--r--doc/user/application_security/dast/browser_based.md2
-rw-r--r--doc/user/enterprise_user/index.md39
-rw-r--r--doc/user/group/epics/manage_epics.md54
-rw-r--r--doc/user/group/saml_sso/index.md2
-rw-r--r--doc/user/infrastructure/iac/terraform_state.md2
-rw-r--r--doc/user/project/code_owners.md4
-rw-r--r--doc/user/project/integrations/index.md5
-rw-r--r--doc/user/project/integrations/squash_tm.md37
-rw-r--r--doc/user/project/merge_requests/index.md1
-rw-r--r--doc/user/project/merge_requests/methods/index.md1
-rw-r--r--lib/api/helpers/integrations_helpers.rb15
-rw-r--r--lib/gitlab/auth/otp/duo_auth.rb13
-rw-r--r--lib/gitlab/auth/otp/strategies/duo_auth/manual_otp.rb46
-rw-r--r--locale/gitlab.pot12
-rw-r--r--package.json2
-rw-r--r--spec/factories/bulk_import/batch_trackers.rb37
-rw-r--r--spec/factories/bulk_import/export_batches.rb23
-rw-r--r--spec/factories/integrations.rb9
-rw-r--r--spec/features/admin/admin_sees_background_migrations_spec.rb2
-rw-r--r--spec/frontend/__helpers__/experimentation_helper.js7
-rw-r--r--spec/frontend/add_context_commits_modal/store/actions_spec.js2
-rw-r--r--spec/frontend/api/alert_management_alerts_api_spec.js3
-rw-r--r--spec/frontend/api/groups_api_spec.js13
-rw-r--r--spec/frontend/api/packages_api_spec.js12
-rw-r--r--spec/frontend/api/projects_api_spec.js3
-rw-r--r--spec/frontend/api/tags_api_spec.js3
-rw-r--r--spec/frontend/api/user_api_spec.js3
-rw-r--r--spec/frontend/api_spec.js23
-rw-r--r--spec/frontend/awards_handler_spec.js5
-rw-r--r--spec/frontend/behaviors/markdown/highlight_current_user_spec.js10
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js4
-rw-r--r--spec/frontend/boards/components/board_content_spec.js1
-rw-r--r--spec/frontend/boards/stores/getters_spec.js8
-rw-r--r--spec/frontend/ci_secure_files/components/secure_files_list_spec.js12
-rw-r--r--spec/frontend/design_management/components/design_notes/design_discussion_spec.js2
-rw-r--r--spec/frontend/design_management/components/design_notes/design_reply_form_spec.js3
-rw-r--r--spec/frontend/design_management/components/design_presentation_spec.js5
-rw-r--r--spec/frontend/design_management/components/design_sidebar_spec.js5
-rw-r--r--spec/frontend/design_management/router_spec.js6
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js11
-rw-r--r--spec/frontend/diffs/components/diff_row_spec.js1
-rw-r--r--spec/frontend/diffs/store/actions_spec.js6
-rw-r--r--spec/frontend/environments/stop_stale_environments_modal_spec.js5
-rw-r--r--spec/frontend/experimentation/components/gitlab_experiment_spec.js5
-rw-r--r--spec/frontend/experimentation/utils_spec.js3
-rw-r--r--spec/frontend/filtered_search/dropdown_user_spec.js4
-rw-r--r--spec/frontend/helpers/startup_css_helper_spec.js7
-rw-r--r--spec/frontend/ide/services/index_spec.js3
-rw-r--r--spec/frontend/ide/services/terminals_spec.js2
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js4
-rw-r--r--spec/frontend/ide/stores/extend_spec.js5
-rw-r--r--spec/frontend/ide/stores/getters_spec.js7
-rw-r--r--spec/frontend/ide/stores/modules/commit/actions_spec.js10
-rw-r--r--spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js243
-rw-r--r--spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js6
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js7
-rw-r--r--spec/frontend/issues/list/utils_spec.js5
-rw-r--r--spec/frontend/issues/show/components/description_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/api_spec.js3
-rw-r--r--spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js5
-rw-r--r--spec/frontend/lib/utils/axios_startup_calls_spec.js7
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js8
-rw-r--r--spec/frontend/lib/utils/datetime/timeago_utility_spec.js10
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js8
-rw-r--r--spec/frontend/members/components/table/role_dropdown_spec.js7
-rw-r--r--spec/frontend/merge_request_tabs_spec.js2
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js5
-rw-r--r--spec/frontend/notes/components/noteable_discussion_spec.js10
-rw-r--r--spec/frontend/notes/deprecated_notes_spec.js1
-rw-r--r--spec/frontend/notes/stores/actions_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/app_spec.js12
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js5
-rw-r--r--spec/frontend/search_autocomplete_spec.js1
-rw-r--r--spec/frontend/sentry/index_spec.js7
-rw-r--r--spec/frontend/sentry/legacy_index_spec.js7
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js6
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js1
-rw-r--r--spec/frontend/snippets/components/edit_spec.js5
-rw-r--r--spec/frontend/snippets/components/snippet_header_spec.js2
-rw-r--r--spec/frontend/syntax_highlight_spec.js6
-rw-r--r--spec/frontend/user_popovers_spec.js11
-rw-r--r--spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js18
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js2
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js5
-rw-r--r--spec/graphql/types/ci/job_type_spec.rb3
-rw-r--r--spec/graphql/types/ci/runner_machine_type_spec.rb4
-rw-r--r--spec/helpers/notes_helper_spec.rb13
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/duo_auth/manual_otp_spec.rb94
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/models/bulk_imports/batch_tracker_spec.rb16
-rw-r--r--spec/models/bulk_imports/export_batch_spec.rb17
-rw-r--r--spec/models/bulk_imports/export_spec.rb3
-rw-r--r--spec/models/ci/runner_machine_build_spec.rb43
-rw-r--r--spec/models/integrations/squash_tm_spec.rb117
-rw-r--r--spec/models/preloaders/runner_machine_policy_preloader_spec.rb38
-rw-r--r--spec/models/user_spec.rb18
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb62
-rw-r--r--spec/requests/api/graphql/mutations/ci/job/cancel_spec.rb (renamed from spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb)4
-rw-r--r--spec/requests/api/graphql/mutations/ci/job/play_spec.rb (renamed from spec/requests/api/graphql/mutations/ci/job_play_spec.rb)4
-rw-r--r--spec/requests/api/graphql/mutations/ci/job/retry_spec.rb (renamed from spec/requests/api/graphql/mutations/ci/job_retry_spec.rb)8
-rw-r--r--spec/requests/api/graphql/mutations/ci/job/unschedule_spec.rb (renamed from spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb)2
-rw-r--r--spec/services/packages/debian/generate_distribution_service_spec.rb28
-rw-r--r--spec/services/users/validate_manual_otp_service_spec.rb33
-rw-r--r--spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb29
-rw-r--r--spec/workers/packages/debian/generate_distribution_worker_spec.rb10
-rw-r--r--workhorse/go.mod2
-rw-r--r--workhorse/go.sum4
-rw-r--r--yarn.lock8
173 files changed, 1510 insertions, 926 deletions
diff --git a/.gitlab/ci/as-if-jh.gitlab-ci.yml b/.gitlab/ci/as-if-jh.gitlab-ci.yml
index 6bd46bee770..2c90112bbf2 100644
--- a/.gitlab/ci/as-if-jh.gitlab-ci.yml
+++ b/.gitlab/ci/as-if-jh.gitlab-ci.yml
@@ -37,11 +37,19 @@ prepare-as-if-jh-branch:
stage: prepare
needs:
- add-jh-files
+ variables:
+ # We can't apply --filter=tree:0 for runner to set up the repository,
+ # so instead we tell runner to not clone anything, and we set up the
+ # repository by ourselves.
+ GIT_STRATEGY: "none"
script:
- # Fetch for the history of the branch so it does not cause the following error:
- # ! [remote rejected] ref -> ref (shallow update not allowed)
- - git fetch --unshallow --filter=tree:0 origin "${CI_COMMIT_SHA}"
- - git checkout -b "${AS_IF_JH_BRANCH}"
+ - git clone --filter=tree:0 "$CI_REPOSITORY_URL" gitlab
+ # We should checkout before moving/changing files
+ - cd gitlab
+ - git checkout -b "${AS_IF_JH_BRANCH}" "${CI_COMMIT_SHA}"
+ - cd ..
+ - mv $JH_FILES_TO_COMMIT gitlab/
+ - cd gitlab
- git add ${JH_FILES_TO_COMMIT}
- git commit -m 'Add JH files' # TODO: Mark which SHA we add
- git push -f "${SANDBOX_REPOSITORY}" "${AS_IF_JH_BRANCH}"
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index a25bfcf6ae5..ada21f3d228 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -1070,7 +1070,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/lib/gitlab/status_page/usage_data_counters/incident_counter_spec.rb'
- 'ee/spec/lib/gitlab/status_page_spec.rb'
- 'ee/spec/lib/gitlab/subscription_portal/client_spec.rb'
- - 'ee/spec/lib/gitlab/subscription_portal/clients/graphql_spec.rb'
- 'ee/spec/lib/gitlab/subscription_portal/clients/rest_spec.rb'
- 'ee/spec/lib/gitlab/template/custom_templates_spec.rb'
- 'ee/spec/lib/gitlab/tracking/snowplow_schema_validation_spec.rb'
@@ -1877,155 +1876,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/views/subscriptions/groups/edit.html.haml_spec.rb'
- 'ee/spec/views/subscriptions/new.html.haml_spec.rb'
- 'ee/spec/views/trial_registrations/new.html.haml_spec.rb'
- - 'ee/spec/workers/active_user_count_threshold_worker_spec.rb'
- - 'ee/spec/workers/adjourned_group_deletion_worker_spec.rb'
- - 'ee/spec/workers/adjourned_project_deletion_worker_spec.rb'
- - 'ee/spec/workers/adjourned_projects_deletion_cron_worker_spec.rb'
- - 'ee/spec/workers/admin_emails_worker_spec.rb'
- - 'ee/spec/workers/analytics/code_review_metrics_worker_spec.rb'
- - 'ee/spec/workers/analytics/cycle_analytics/consistency_worker_spec.rb'
- - 'ee/spec/workers/analytics/cycle_analytics/incremental_worker_spec.rb'
- - 'ee/spec/workers/analytics/cycle_analytics/reaggregation_worker_spec.rb'
- - 'ee/spec/workers/analytics/devops_adoption/create_all_snapshots_worker_spec.rb'
- - 'ee/spec/workers/analytics/devops_adoption/create_snapshot_worker_spec.rb'
- - 'ee/spec/workers/app_sec/dast/scanner_profiles_builds/consistency_worker_spec.rb'
- - 'ee/spec/workers/app_sec/dast/scans/consistency_worker_spec.rb'
- - 'ee/spec/workers/app_sec/dast/site_profiles_builds/consistency_worker_spec.rb'
- - 'ee/spec/workers/approval_rules/external_approval_rule_payload_worker_spec.rb'
- - 'ee/spec/workers/audit_events/user_impersonation_event_create_worker_spec.rb'
- - 'ee/spec/workers/auth/saml_group_sync_worker_spec.rb'
- - 'ee/spec/workers/ci/batch_reset_minutes_worker_spec.rb'
- - 'ee/spec/workers/ci/initial_pipeline_process_worker_spec.rb'
- - 'ee/spec/workers/ci/minutes/refresh_cached_data_worker_spec.rb'
- - 'ee/spec/workers/ci/minutes/update_project_and_namespace_usage_worker_spec.rb'
- - 'ee/spec/workers/ci/runners/stale_group_runners_prune_cron_worker_spec.rb'
- - 'ee/spec/workers/ci/sync_reports_to_report_approval_rules_worker_spec.rb'
- - 'ee/spec/workers/ci/trigger_downstream_subscriptions_worker_spec.rb'
- - 'ee/spec/workers/ci/upstream_projects_subscriptions_cleanup_worker_spec.rb'
- - 'ee/spec/workers/clear_shared_runners_minutes_worker_spec.rb'
- - 'ee/spec/workers/compliance_management/merge_requests/compliance_violations_worker_spec.rb'
- - 'ee/spec/workers/compliance_management/update_default_framework_worker_spec.rb'
- - 'ee/spec/workers/concerns/elastic/indexing_control_spec.rb'
- - 'ee/spec/workers/concerns/elastic/migration_obsolete_spec.rb'
- - 'ee/spec/workers/concerns/elastic/migration_options_spec.rb'
- - 'ee/spec/workers/create_github_webhook_worker_spec.rb'
- - 'ee/spec/workers/deployments/auto_rollback_worker_spec.rb'
- - 'ee/spec/workers/dora/daily_metrics/refresh_worker_spec.rb'
- - 'ee/spec/workers/ee/arkose/blocked_users_report_worker_spec.rb'
- - 'ee/spec/workers/ee/ci/build_finished_worker_spec.rb'
- - 'ee/spec/workers/ee/namespaces/in_product_marketing_emails_worker_spec.rb'
- - 'ee/spec/workers/ee/namespaces/root_statistics_worker_spec.rb'
- - 'ee/spec/workers/ee/projects/inactive_projects_deletion_cron_worker_spec.rb'
- - 'ee/spec/workers/ee/repository_check/batch_worker_spec.rb'
- - 'ee/spec/workers/ee/repository_check/single_repository_worker_spec.rb'
- - 'ee/spec/workers/elastic/project_transfer_worker_spec.rb'
- - 'ee/spec/workers/elastic_association_indexer_worker_spec.rb'
- - 'ee/spec/workers/elastic_cluster_reindexing_cron_worker_spec.rb'
- - 'ee/spec/workers/elastic_full_index_worker_spec.rb'
- - 'ee/spec/workers/elastic_index_initial_bulk_cron_worker_spec.rb'
- - 'ee/spec/workers/elastic_indexing_control_worker_spec.rb'
- - 'ee/spec/workers/elastic_namespace_indexer_worker_spec.rb'
- - 'ee/spec/workers/elastic_namespace_rollout_worker_spec.rb'
- - 'ee/spec/workers/elastic_remove_expired_namespace_subscriptions_from_index_cron_worker_spec.rb'
- - 'ee/spec/workers/epics/new_epic_issue_worker_spec.rb'
- - 'ee/spec/workers/epics/update_cached_metadata_worker_spec.rb'
- - 'ee/spec/workers/geo/batch/project_registry_scheduler_worker_spec.rb'
- - 'ee/spec/workers/geo/batch/project_registry_worker_spec.rb'
- - 'ee/spec/workers/geo/batch_event_create_worker_spec.rb'
- - 'ee/spec/workers/geo/container_repository_sync_worker_spec.rb'
- - 'ee/spec/workers/geo/create_repository_updated_event_worker_spec.rb'
- - 'ee/spec/workers/geo/design_repository_shard_sync_worker_spec.rb'
- - 'ee/spec/workers/geo/design_repository_sync_worker_spec.rb'
- - 'ee/spec/workers/geo/destroy_worker_spec.rb'
- - 'ee/spec/workers/geo/event_worker_spec.rb'
- - 'ee/spec/workers/geo/file_registry_removal_worker_spec.rb'
- - 'ee/spec/workers/geo/metrics_update_worker_spec.rb'
- - 'ee/spec/workers/geo/prune_event_log_worker_spec.rb'
- - 'ee/spec/workers/geo/repositories_clean_up_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_cleanup_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_sync_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_verification/primary/batch_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_verification/primary/shard_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_verification/primary/single_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_verification/secondary/scheduler_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_verification/secondary/shard_worker_spec.rb'
- - 'ee/spec/workers/geo/repository_verification/secondary/single_worker_spec.rb'
- - 'ee/spec/workers/geo/reverification_batch_worker_spec.rb'
- - 'ee/spec/workers/geo/scheduler/per_shard_scheduler_worker_spec.rb'
- - 'ee/spec/workers/geo/scheduler/scheduler_worker_spec.rb'
- - 'ee/spec/workers/geo/secondary_usage_data_cron_worker_spec.rb'
- - 'ee/spec/workers/geo/sidekiq_cron_config_worker_spec.rb'
- - 'ee/spec/workers/geo/sync_timeout_cron_worker_spec.rb'
- - 'ee/spec/workers/geo/verification_batch_worker_spec.rb'
- - 'ee/spec/workers/geo/verification_cron_worker_spec.rb'
- - 'ee/spec/workers/geo/verification_state_backfill_service_spec.rb'
- - 'ee/spec/workers/geo/verification_state_backfill_worker_spec.rb'
- - 'ee/spec/workers/geo/verification_worker_spec.rb'
- - 'ee/spec/workers/geo_repository_destroy_worker_spec.rb'
- - 'ee/spec/workers/gitlab_subscriptions/trials/apply_trial_worker_spec.rb'
- - 'ee/spec/workers/group_saml_group_sync_worker_spec.rb'
- - 'ee/spec/workers/groups/create_event_worker_spec.rb'
- - 'ee/spec/workers/groups/export_memberships_worker_spec.rb'
- - 'ee/spec/workers/groups/schedule_bulk_repository_shard_moves_worker_spec.rb'
- - 'ee/spec/workers/groups/update_repository_storage_worker_spec.rb'
- - 'ee/spec/workers/historical_data_worker_spec.rb'
- - 'ee/spec/workers/import_software_licenses_worker_spec.rb'
- - 'ee/spec/workers/incident_management/apply_incident_sla_exceeded_label_worker_spec.rb'
- - 'ee/spec/workers/incident_management/incident_sla_exceeded_check_worker_spec.rb'
- - 'ee/spec/workers/incident_management/oncall_rotations/persist_all_rotations_shifts_job_spec.rb'
- - 'ee/spec/workers/incident_management/oncall_rotations/persist_shifts_job_spec.rb'
- - 'ee/spec/workers/incident_management/pending_escalations/alert_check_worker_spec.rb'
- - 'ee/spec/workers/incident_management/pending_escalations/alert_create_worker_spec.rb'
- - 'ee/spec/workers/incident_management/pending_escalations/issue_check_worker_spec.rb'
- - 'ee/spec/workers/incident_management/pending_escalations/issue_create_worker_spec.rb'
- - 'ee/spec/workers/incident_management/pending_escalations/schedule_check_cron_worker_spec.rb'
- - 'ee/spec/workers/integrations/slack_event_worker_spec.rb'
- - 'ee/spec/workers/iterations/cadences/create_iterations_worker_spec.rb'
- - 'ee/spec/workers/iterations/cadences/schedule_create_iterations_worker_spec.rb'
- - 'ee/spec/workers/iterations/roll_over_issues_worker_spec.rb'
- - 'ee/spec/workers/iterations_update_status_worker_spec.rb'
- - '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/licenses/reset_submit_license_usage_data_banner_worker_spec.rb'
- - 'ee/spec/workers/merge_request_reset_approvals_worker_spec.rb'
- - 'ee/spec/workers/merge_requests/stream_approval_audit_event_worker_spec.rb'
- - 'ee/spec/workers/merge_requests/sync_code_owner_approval_rules_worker_spec.rb'
- - 'ee/spec/workers/merge_trains/refresh_worker_spec.rb'
- - 'ee/spec/workers/namespaces/sync_namespace_name_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'
- - 'ee/spec/workers/product_analytics/initialize_analytics_worker_spec.rb'
- - 'ee/spec/workers/project_cache_worker_spec.rb'
- - 'ee/spec/workers/project_template_export_worker_spec.rb'
- - 'ee/spec/workers/projects/disable_legacy_open_source_license_for_inactive_projects_worker_spec.rb'
- - 'ee/spec/workers/repository_update_mirror_worker_spec.rb'
- - 'ee/spec/workers/requirements_management/import_requirements_csv_worker_spec.rb'
- - 'ee/spec/workers/requirements_management/process_requirements_reports_worker_spec.rb'
- - 'ee/spec/workers/scan_security_report_secrets_worker_spec.rb'
- - 'ee/spec/workers/security/auto_fix_worker_spec.rb'
- - 'ee/spec/workers/security/create_orchestration_policy_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'
- - 'ee/spec/workers/security/process_scan_result_policy_worker_spec.rb'
- - 'ee/spec/workers/security/scans/purge_by_job_id_worker_spec.rb'
- - 'ee/spec/workers/security/scans/purge_worker_spec.rb'
- - 'ee/spec/workers/security/store_scans_worker_spec.rb'
- - 'ee/spec/workers/security/sync_scan_policies_worker_spec.rb'
- - 'ee/spec/workers/security/track_secure_scans_worker_spec.rb'
- - 'ee/spec/workers/set_user_status_based_on_user_cap_setting_worker_spec.rb'
- - 'ee/spec/workers/status_page/publish_worker_spec.rb'
- - 'ee/spec/workers/store_security_reports_worker_spec.rb'
- - 'ee/spec/workers/sync_seat_link_request_worker_spec.rb'
- - 'ee/spec/workers/sync_seat_link_worker_spec.rb'
- - 'ee/spec/workers/todos_destroyer/confidential_epic_worker_spec.rb'
- - 'ee/spec/workers/update_all_mirrors_worker_spec.rb'
- - 'ee/spec/workers/update_max_seats_used_for_gitlab_com_subscriptions_worker_spec.rb'
- - 'ee/spec/workers/vulnerability_exports/export_deletion_worker_spec.rb'
- - 'ee/spec/workers/vulnerability_exports/export_worker_spec.rb'
- 'spec/benchmarks/banzai_benchmark.rb'
- 'spec/bin/audit_event_type_spec.rb'
- 'spec/bin/diagnostic_reports_uploader_spec.rb'
diff --git a/Gemfile b/Gemfile
index 33ddfe7e2cb..7bb8e5e5f20 100644
--- a/Gemfile
+++ b/Gemfile
@@ -600,3 +600,5 @@ install_if -> { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0.0") } do
# BufferedIO patch
gem 'net-protocol', '~> 0.1.3'
end
+
+gem 'duo_api', '~> 1.3'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 49f9985f258..4fce46bbafa 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -128,6 +128,7 @@
{"name":"dry-logic","version":"1.1.0","platform":"ruby","checksum":"eca4b39084c9d22778144b7e4cf8db20e8bab7de6d89deb220d20a9fde60b69d"},
{"name":"dry-types","version":"1.4.0","platform":"ruby","checksum":"68003bb0db3077fecd0270f4ae486a82ee76bab6d666fdc4e094380a67c9a1df"},
{"name":"dumb_delegator","version":"1.0.0","platform":"ruby","checksum":"ff5e411816d2d8ad8e260b269e712ae3839dddb0f9f8e18d3b1a3fe08f6d2e94"},
+{"name":"duo_api","version":"1.3.0","platform":"ruby","checksum":"87c9830e190fad32fdb086b023f555a3cf5cd4d6708a992f7a32efb2ce206176"},
{"name":"e2mmap","version":"0.1.0","platform":"ruby","checksum":"45ee6bba2d97a7d91ee0885774261feee87e28c598355df31e93b56196ec0f59"},
{"name":"ecma-re-validator","version":"0.3.0","platform":"ruby","checksum":"66a95bd8c2b0641baf1fbf9bd355a0dcf13c82c6883f6f496a722420a8b6e0d7"},
{"name":"ed25519","version":"1.3.0","platform":"java","checksum":"8e5d2f8a5325c7a463d61d1a48406ce54074c610f3dccd889e6532c9527a3894"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 99bed5421de..7e53437a0bb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -416,6 +416,7 @@ GEM
dry-inflector (~> 0.1, >= 0.1.2)
dry-logic (~> 1.0, >= 1.0.2)
dumb_delegator (1.0.0)
+ duo_api (1.3.0)
e2mmap (0.1.0)
ecma-re-validator (0.3.0)
regexp_parser (~> 2.0)
@@ -1689,6 +1690,7 @@ DEPENDENCIES
discordrb-webhooks (~> 3.4)
doorkeeper (~> 5.5)
doorkeeper-openid_connect (~> 1.8)
+ duo_api (~> 1.3)
ed25519 (~> 1.3.0)
elasticsearch-api (= 7.13.3)
elasticsearch-model (~> 7.2)
diff --git a/app/assets/javascripts/deprecated_notes.js b/app/assets/javascripts/deprecated_notes.js
index 5b398623164..32a91506ab4 100644
--- a/app/assets/javascripts/deprecated_notes.js
+++ b/app/assets/javascripts/deprecated_notes.js
@@ -53,9 +53,9 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export default class Notes {
- static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM) {
+ static initialize(notes_url, last_fetched_at, view, enableGFM) {
if (!this.instance) {
- this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
+ this.instance = new Notes(notes_url, last_fetched_at, view, enableGFM);
}
}
@@ -63,7 +63,7 @@ export default class Notes {
return this.instance;
}
- constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = defaultAutocompleteConfig) {
+ constructor(notes_url, last_fetched_at, view, enableGFM = defaultAutocompleteConfig) {
this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this);
this.visibilityChange = this.visibilityChange.bind(this);
@@ -85,9 +85,9 @@ export default class Notes {
this.postComment = this.postComment.bind(this);
this.clearAlertWrapper = this.clearAlert.bind(this);
this.onHashChange = this.onHashChange.bind(this);
+ this.note_ids = [];
this.notes_url = notes_url;
- this.note_ids = note_ids;
this.enableGFM = enableGFM;
// Used to keep track of updated notes while people are editing things
this.updatedNotesTrackingMap = {};
@@ -449,8 +449,6 @@ export default class Notes {
return;
}
- this.note_ids.push(noteEntity.id);
-
if ($notesList.length) {
$notesList.find('.system-note.being-posted').remove();
}
@@ -497,7 +495,6 @@ export default class Notes {
if (!Notes.isNewNote(noteEntity, this.note_ids)) {
return;
}
- this.note_ids.push(noteEntity.id);
const form =
$form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
@@ -745,7 +742,7 @@ export default class Notes {
$noteAvatar.append($targetNoteBadge);
this.revertNoteEditForm($targetNote);
- renderGFM($noteEntityEl.get(0));
+ renderGFM(Notes.getNodeToRender($noteEntityEl));
// Find the note's `li` element by ID and replace it with the updated HTML
const $note_li = $(`.note-row-${noteEntity.id}`);
@@ -1396,8 +1393,28 @@ export default class Notes {
/**
* Check if note does not exist on page
*/
- static isNewNote(noteEntity, noteIds) {
- return $.inArray(noteEntity.id, noteIds) === -1;
+ static isNewNote(noteEntity, note_ids) {
+ if (note_ids.length === 0) {
+ Notes.loadNotesIds(note_ids);
+ }
+ const isNewEntry = $.inArray(noteEntity.id, note_ids) === -1;
+ if (isNewEntry) {
+ note_ids.push(noteEntity.id);
+ }
+ return isNewEntry;
+ }
+
+ /**
+ * Load notes ids
+ */
+ static loadNotesIds(note_ids) {
+ const $notesList = $('.main-notes-list').children();
+ for (const $noteItem of $notesList) {
+ if (Notes.isNodeTypeElement($noteItem)) {
+ const noteId = parseInt($noteItem.id.split('_')[1], 10);
+ note_ids.push(noteId);
+ }
+ }
}
/**
@@ -1422,7 +1439,7 @@ export default class Notes {
const $note = $(noteHtml);
$note.addClass('fade-in-full');
- renderGFM($note.get(0));
+ renderGFM(Notes.getNodeToRender($note));
$notesList.append($note);
return $note;
}
@@ -1431,11 +1448,20 @@ export default class Notes {
const $updatedNote = $(noteHtml);
$updatedNote.addClass('fade-in');
- renderGFM($updatedNote.get(0));
+ renderGFM(Notes.getNodeToRender($updatedNote));
$note.replaceWith($updatedNote);
return $updatedNote;
}
+ static getNodeToRender($note) {
+ for (const $item of $note) {
+ if (Notes.isNodeTypeElement($item)) {
+ return $item;
+ }
+ }
+ return '';
+ }
+
/**
* Get data from Form attributes to use for saving/submitting comment.
*/
@@ -1829,4 +1855,11 @@ export default class Notes {
return $closeBtn.text($closeBtn.data('originalText'));
}
+
+ /**
+ * Function to check if node is element to avoid comment and text
+ */
+ static isNodeTypeElement($node) {
+ return $node.nodeType === Node.ELEMENT_NODE;
+ }
}
diff --git a/app/assets/javascripts/incidents/constants.js b/app/assets/javascripts/incidents/constants.js
index ee3f30de880..dde40ec2983 100644
--- a/app/assets/javascripts/incidents/constants.js
+++ b/app/assets/javascripts/incidents/constants.js
@@ -44,7 +44,6 @@ export const ESCALATION_STATUSES = {
RESOLVED: s__('AlertManagement|Resolved'),
};
-export const DEFAULT_PAGE_SIZE = 20;
export const TH_CREATED_AT_TEST_ID = { 'data-testid': 'incident-management-created-at-sort' };
export const TH_SEVERITY_TEST_ID = { 'data-testid': 'incident-management-severity-sort' };
export const TH_ESCALATION_STATUS_TEST_ID = { 'data-testid': 'incident-management-status-sort' };
diff --git a/app/assets/javascripts/init_deprecated_notes.js b/app/assets/javascripts/init_deprecated_notes.js
index 5f918b0d2f5..8657a1dcb67 100644
--- a/app/assets/javascripts/init_deprecated_notes.js
+++ b/app/assets/javascripts/init_deprecated_notes.js
@@ -2,9 +2,9 @@ import Notes from './deprecated_notes';
export default () => {
const dataEl = document.querySelector('.js-notes-data');
- const { notesUrl, notesIds, now, diffView, enableGFM } = JSON.parse(dataEl.innerHTML);
+ const { notesUrl, now, diffView, enableGFM } = JSON.parse(dataEl.innerHTML);
// Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance
- Notes.initialize(notesUrl, notesIds, now, diffView, enableGFM);
+ Notes.initialize(notesUrl, now, diffView, enableGFM);
};
diff --git a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
index 32aceb0040a..2546bface58 100644
--- a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
+++ b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
@@ -9,7 +9,6 @@ import {
CREATED_DESC,
defaultTypeTokenOptions,
i18n,
- PAGE_SIZE,
PARAM_STATE,
UPDATED_DESC,
urlSortParams,
@@ -49,7 +48,7 @@ import {
TOKEN_TYPE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
-import { IssuableListTabs } from '~/vue_shared/issuable/list/constants';
+import { DEFAULT_PAGE_SIZE, IssuableListTabs } from '~/vue_shared/issuable/list/constants';
import getIssuesCountsQuery from '../queries/get_issues_counts.query.graphql';
import { AutocompleteCache } from '../utils';
@@ -386,14 +385,14 @@ export default {
handleNextPage() {
this.pageParams = {
afterCursor: this.pageInfo.endCursor,
- firstPageSize: PAGE_SIZE,
+ firstPageSize: DEFAULT_PAGE_SIZE,
};
scrollUp();
},
handlePreviousPage() {
this.pageParams = {
beforeCursor: this.pageInfo.startCursor,
- lastPageSize: PAGE_SIZE,
+ lastPageSize: DEFAULT_PAGE_SIZE,
};
scrollUp();
},
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index d8fc5ba8a48..5c4bf8f19e4 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -52,7 +52,7 @@ import {
TOKEN_TYPE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
-import { IssuableListTabs } from '~/vue_shared/issuable/list/constants';
+import { DEFAULT_PAGE_SIZE, IssuableListTabs } from '~/vue_shared/issuable/list/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue';
import {
@@ -62,7 +62,6 @@ import {
i18n,
ISSUE_REFERENCE,
MAX_LIST_SIZE,
- PAGE_SIZE,
PARAM_FIRST_PAGE_SIZE,
PARAM_LAST_PAGE_SIZE,
PARAM_PAGE_AFTER,
@@ -184,7 +183,7 @@ export default {
showBulkEditSidebar: false,
sortKey: CREATED_DESC,
state: STATUS_OPEN,
- pageSize: PAGE_SIZE,
+ pageSize: DEFAULT_PAGE_SIZE,
};
},
apollo: {
@@ -453,7 +452,7 @@ export default {
return this.issues.length > 0 && (this.pageInfo.hasNextPage || this.pageInfo.hasPreviousPage);
},
showPageSizeControls() {
- return this.currentTabCount > PAGE_SIZE;
+ return this.currentTabCount > DEFAULT_PAGE_SIZE;
},
sortOptions() {
return getSortOptions({
diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js
index 31a43c95f5e..99064a50e3f 100644
--- a/app/assets/javascripts/issues/list/constants.js
+++ b/app/assets/javascripts/issues/list/constants.js
@@ -33,7 +33,6 @@ import {
export const ISSUE_REFERENCE = /^#\d+$/;
export const MAX_LIST_SIZE = 10;
-export const PAGE_SIZE = 20;
export const PARAM_ASSIGNEE_ID = 'assignee_id';
export const PARAM_FIRST_PAGE_SIZE = 'first_page_size';
export const PARAM_LAST_PAGE_SIZE = 'last_page_size';
diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js
index 281e48d9aa7..b086640cd12 100644
--- a/app/assets/javascripts/issues/list/utils.js
+++ b/app/assets/javascripts/issues/list/utils.js
@@ -16,6 +16,7 @@ import {
TOKEN_TYPE_HEALTH,
TOKEN_TYPE_LABEL,
} from '~/vue_shared/components/filtered_search_bar/constants';
+import { DEFAULT_PAGE_SIZE } from '~/vue_shared/issuable/list/constants';
import {
ALTERNATIVE_FILTER,
API_PARAM,
@@ -35,7 +36,6 @@ import {
MILESTONE_DUE_ASC,
MILESTONE_DUE_DESC,
NORMAL_FILTER,
- PAGE_SIZE,
PARAM_ASSIGNEE_ID,
POPULARITY_ASC,
POPULARITY_DESC,
@@ -56,7 +56,7 @@ import {
export const getInitialPageParams = (
pageSize,
- firstPageSize = pageSize ?? PAGE_SIZE,
+ firstPageSize = pageSize ?? DEFAULT_PAGE_SIZE,
lastPageSize,
afterCursor,
beforeCursor,
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js
index 7af3fc1c2db..05673215a66 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js
@@ -6,7 +6,6 @@ export const FETCH_PACKAGES_LIST_ERROR_MESSAGE = __(
export const DELETE_PACKAGE_SUCCESS_MESSAGE = __('Package deleted successfully');
export const DEFAULT_PAGE = 1;
-export const DEFAULT_PAGE_SIZE = 20;
export const GROUP_PAGE_TYPE = 'groups';
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
index ecd987e5cf7..122123f49cd 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
@@ -3,11 +3,11 @@ import { createAlert, VARIANT_SUCCESS } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages_and_registries/shared/constants';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
+import { DEFAULT_PAGE_SIZE } from '~/vue_shared/issuable/list/constants';
import {
FETCH_PACKAGES_LIST_ERROR_MESSAGE,
DELETE_PACKAGE_SUCCESS_MESSAGE,
DEFAULT_PAGE,
- DEFAULT_PAGE_SIZE,
MISSING_DELETE_PATH_ERROR,
TERRAFORM_SEARCH_TYPE,
} from '../constants';
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index a97e9cee4b1..d0830aff5c4 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -25,6 +25,9 @@ module Types
description: 'References to builds that must complete before the jobs run.'
field :pipeline, Types::Ci::PipelineType, null: true,
description: 'Pipeline the job belongs to.'
+ field :runner_machine, ::Types::Ci::RunnerMachineType, null: true,
+ description: 'Runner machine assigned to the job.',
+ alpha: { milestone: '15.11' }
field :stage, Types::Ci::StageType, null: true,
description: 'Stage of the job.'
field :status,
@@ -157,6 +160,21 @@ module Types
::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Stage, object.stage_id).find
end
+ def runner_machine
+ BatchLoader::GraphQL.for(object.id).batch(key: :runner_machines) do |build_ids, loader|
+ plucked_build_to_machine_ids = ::Ci::RunnerMachineBuild.for_build(build_ids).pluck_build_id_and_runner_machine_id
+ runner_machines = ::Ci::RunnerMachine.id_in(plucked_build_to_machine_ids.values.uniq)
+ Preloaders::RunnerMachinePolicyPreloader.new(runner_machines, current_user).execute
+ runner_machines_by_id = runner_machines.index_by(&:id)
+
+ build_ids.each do |build_id|
+ runner_machine_id = plucked_build_to_machine_ids[build_id]
+
+ loader.call(build_id, runner_machines_by_id[runner_machine_id])
+ end
+ end
+ end
+
# This class is a secret union!
# TODO: turn this into an actual union, so that fields can be referenced safely!
def id
diff --git a/app/graphql/types/ci/runner_machine_type.rb b/app/graphql/types/ci/runner_machine_type.rb
index db0ff722f4e..8e6656288d9 100644
--- a/app/graphql/types/ci/runner_machine_type.rb
+++ b/app/graphql/types/ci/runner_machine_type.rb
@@ -35,6 +35,10 @@ module Types
Types::Ci::RunnerStatusEnum,
null: false,
description: 'Status of the runner machine.'
+ field :system_id, GraphQL::Types::String,
+ null: false,
+ description: 'System ID associated with the runner machine.',
+ method: :system_xid
field :version, GraphQL::Types::String, null: true, description: 'Version of the runner.'
def executor_name
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 3df9d68b03e..3e8872dc199 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -153,7 +153,6 @@ module NotesHelper
def initial_notes_data(autocomplete)
{
notesUrl: notes_url,
- notesIds: @noteable.notes.pluck(:id), # rubocop: disable CodeReuse/ActiveRecord
now: Time.now.to_i,
diffView: diff_view,
enableGFM: {
diff --git a/app/models/bulk_imports/batch_tracker.rb b/app/models/bulk_imports/batch_tracker.rb
new file mode 100644
index 00000000000..df1fab89ee6
--- /dev/null
+++ b/app/models/bulk_imports/batch_tracker.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module BulkImports
+ class BatchTracker < ApplicationRecord
+ self.table_name = 'bulk_import_batch_trackers'
+
+ belongs_to :tracker, class_name: 'BulkImports::Tracker'
+
+ validates :batch_number, presence: true, uniqueness: { scope: :tracker_id }
+
+ state_machine :status, initial: :created do
+ state :created, value: 0
+ state :started, value: 1
+ state :finished, value: 2
+ state :timeout, value: 3
+ state :failed, value: -1
+ state :skipped, value: -2
+
+ event :start do
+ transition created: :started
+ end
+
+ event :retry do
+ transition started: :created
+ end
+
+ event :finish do
+ transition started: :finished
+ transition failed: :failed
+ transition skipped: :skipped
+ end
+
+ event :skip do
+ transition any => :skipped
+ end
+
+ event :fail_op do
+ transition any => :failed
+ end
+
+ event :cleanup_stale do
+ transition [:created, :started] => :timeout
+ end
+ end
+ end
+end
diff --git a/app/models/bulk_imports/export.rb b/app/models/bulk_imports/export.rb
index 8d4d31ee92d..1ea317a100a 100644
--- a/app/models/bulk_imports/export.rb
+++ b/app/models/bulk_imports/export.rb
@@ -14,6 +14,7 @@ module BulkImports
belongs_to :group, optional: true
has_one :upload, class_name: 'BulkImports::ExportUpload'
+ has_many :batches, class_name: 'BulkImports::ExportBatch'
validates :project, presence: true, unless: :group
validates :group, presence: true, unless: :project
diff --git a/app/models/bulk_imports/export_batch.rb b/app/models/bulk_imports/export_batch.rb
new file mode 100644
index 00000000000..9d34dae12d0
--- /dev/null
+++ b/app/models/bulk_imports/export_batch.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module BulkImports
+ class ExportBatch < ApplicationRecord
+ self.table_name = 'bulk_import_export_batches'
+
+ BATCH_SIZE = 1000
+
+ belongs_to :export, class_name: 'BulkImports::Export'
+ has_one :upload, class_name: 'BulkImports::ExportUpload', foreign_key: :batch_id, inverse_of: :batch
+
+ validates :batch_number, presence: true, uniqueness: { scope: :export_id }
+
+ state_machine :status, initial: :started do
+ state :started, value: 0
+ state :finished, value: 1
+ state :failed, value: -1
+
+ event :start do
+ transition any => :started
+ end
+
+ event :finish do
+ transition started: :finished
+ transition failed: :failed
+ end
+
+ event :fail_op do
+ transition any => :failed
+ end
+ end
+ end
+end
diff --git a/app/models/bulk_imports/export_upload.rb b/app/models/bulk_imports/export_upload.rb
index 4304032b28c..00f8e8f1304 100644
--- a/app/models/bulk_imports/export_upload.rb
+++ b/app/models/bulk_imports/export_upload.rb
@@ -7,6 +7,7 @@ module BulkImports
self.table_name = 'bulk_import_export_uploads'
belongs_to :export, class_name: 'BulkImports::Export'
+ belongs_to :batch, class_name: 'BulkImports::ExportBatch', optional: true
mount_uploader :export_file, ExportUploader
diff --git a/app/models/bulk_imports/tracker.rb b/app/models/bulk_imports/tracker.rb
index 701f1a9e49e..55502721a76 100644
--- a/app/models/bulk_imports/tracker.rb
+++ b/app/models/bulk_imports/tracker.rb
@@ -11,6 +11,8 @@ class BulkImports::Tracker < ApplicationRecord
foreign_key: :bulk_import_entity_id,
optional: false
+ has_many :batches, class_name: 'BulkImports::BatchTracker', inverse_of: :tracker
+
validates :relation,
presence: true,
uniqueness: { scope: :bulk_import_entity_id }
diff --git a/app/models/ci/runner_machine_build.rb b/app/models/ci/runner_machine_build.rb
index 95418db3619..d4f2c403337 100644
--- a/app/models/ci/runner_machine_build.rb
+++ b/app/models/ci/runner_machine_build.rb
@@ -14,5 +14,13 @@ module Ci
validates :build, presence: true
validates :runner_machine, presence: true
+
+ scope :for_build, ->(build_id) { where(build_id: build_id) }
+
+ def self.pluck_build_id_and_runner_machine_id
+ select(:build_id, :runner_machine_id)
+ .pluck(:build_id, :runner_machine_id)
+ .to_h
+ end
end
end
diff --git a/app/models/integration.rb b/app/models/integration.rb
index 15fae97895e..860739fe5aa 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -21,7 +21,8 @@ class Integration < ApplicationRecord
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
drone_ci emails_on_push ewm external_wiki hangouts_chat harbor irker jira
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
- pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao
+ pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands squash_tm teamcity
+ unify_circuit webex_teams youtrack zentao
].freeze
# TODO Shimo is temporary disabled on group and instance-levels.
diff --git a/app/models/integrations/squash_tm.rb b/app/models/integrations/squash_tm.rb
new file mode 100644
index 00000000000..e0a63b5ae6a
--- /dev/null
+++ b/app/models/integrations/squash_tm.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module Integrations
+ class SquashTm < Integration
+ include HasWebHook
+
+ field :url,
+ placeholder: 'https://your-instance.squashcloud.io/squash/plugin/xsquash4gitlab/webhook/issue',
+ title: -> { s_('SquashTmIntegration|Squash TM webhook URL') },
+ exposes_secrets: true,
+ required: true
+
+ field :token,
+ type: 'password',
+ title: -> { s_('SquashTmIntegration|Secret token (optional)') },
+ non_empty_password_title: -> { s_('ProjectService|Enter new token') },
+ non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
+ required: false
+
+ with_options if: :activated? do
+ validates :url, presence: true, public_url: true
+ validates :token, length: { maximum: 255 }, allow_blank: true
+ end
+
+ def title
+ 'Squash TM'
+ end
+
+ def description
+ s_("SquashTmIntegration|Update Squash TM requirements when GitLab issues are modified.")
+ end
+
+ def help
+ docs_link = ActionController::Base.helpers.link_to(
+ _('Learn more.'),
+ Rails.application.routes.url_helpers.help_page_url('user/project/integrations/squash_tm'),
+ target: '_blank',
+ rel: 'noopener noreferrer'
+ )
+
+ Kernel.format(
+ s_('SquashTmIntegration|Update Squash TM requirements when GitLab issues are modified. %{docs_link}'),
+ { docs_link: docs_link.html_safe }
+ ).html_safe
+ end
+
+ def self.supported_events
+ %w[issue confidential_issue]
+ end
+
+ def self.to_param
+ 'squash_tm'
+ end
+
+ def self.default_test_event
+ 'issue'
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ execute_web_hook!(data, "#{data[:object_kind]} Hook")
+ end
+
+ def test(data)
+ result = execute_web_hook!(data, "Test Configuration Hook")
+
+ { success: result.payload[:http_status] == 200, result: result.message }
+ rescue StandardError => error
+ { success: false, result: error.message }
+ end
+
+ override :hook_url
+ def hook_url
+ format("#{url}%s", ('?token={token}' unless token.blank?))
+ end
+
+ def url_variables
+ { 'token' => token }.compact
+ end
+ end
+end
diff --git a/app/models/preloaders/runner_machine_policy_preloader.rb b/app/models/preloaders/runner_machine_policy_preloader.rb
new file mode 100644
index 00000000000..52864eeba8d
--- /dev/null
+++ b/app/models/preloaders/runner_machine_policy_preloader.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Preloaders
+ class RunnerMachinePolicyPreloader
+ def initialize(runner_machines, current_user)
+ @runner_machines = runner_machines
+ @current_user = current_user
+ end
+
+ def execute
+ return if runner_machines.is_a?(ActiveRecord::NullRelation)
+
+ ActiveRecord::Associations::Preloader.new(
+ records: runner_machines,
+ associations: [:runner]
+ ).call
+ end
+
+ private
+
+ attr_reader :runner_machines, :current_user
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 738484338dc..cb218c0a49f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -214,6 +214,7 @@ class Project < ApplicationRecord
has_one :shimo_integration, class_name: 'Integrations::Shimo'
has_one :slack_integration, class_name: 'Integrations::Slack'
has_one :slack_slash_commands_integration, class_name: 'Integrations::SlackSlashCommands'
+ has_one :squash_tm_integration, class_name: 'Integrations::SquashTm'
has_one :teamcity_integration, class_name: 'Integrations::Teamcity'
has_one :unify_circuit_integration, class_name: 'Integrations::UnifyCircuit'
has_one :webex_teams_integration, class_name: 'Integrations::WebexTeams'
diff --git a/app/models/user.rb b/app/models/user.rb
index ed001dadcbb..3bd8a035357 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -28,6 +28,7 @@ class User < ApplicationRecord
include UpdateHighestRole
include HasUserType
include Gitlab::Auth::Otp::Fortinet
+ include Gitlab::Auth::Otp::DuoAuth
include RestrictedSignup
include StripAttribute
include EachBatch
@@ -1068,7 +1069,8 @@ class User < ApplicationRecord
def two_factor_otp_enabled?
otp_required_for_login? ||
forti_authenticator_enabled?(self) ||
- forti_token_cloud_enabled?(self)
+ forti_token_cloud_enabled?(self) ||
+ duo_auth_enabled?(self)
end
def two_factor_webauthn_enabled?
diff --git a/app/services/concerns/update_repository_storage_methods.rb b/app/services/concerns/update_repository_storage_methods.rb
index b21d05f4178..a0b4040cff7 100644
--- a/app/services/concerns/update_repository_storage_methods.rb
+++ b/app/services/concerns/update_repository_storage_methods.rb
@@ -28,10 +28,7 @@ module UpdateRepositoryStorageMethods
track_repository(destination_storage_name)
end
- unless same_filesystem?
- remove_old_paths
- enqueue_housekeeping
- end
+ remove_old_paths unless same_filesystem?
repository_storage_move.finish_cleanup!
@@ -95,10 +92,6 @@ module UpdateRepositoryStorageMethods
end
end
- def enqueue_housekeeping
- # no-op
- end
-
def wait_for_pushes(type)
reference_counter = container.reference_counter(type: type)
diff --git a/app/services/packages/debian/generate_distribution_service.rb b/app/services/packages/debian/generate_distribution_service.rb
index 2ced2e5f275..ee43fe208c9 100644
--- a/app/services/packages/debian/generate_distribution_service.rb
+++ b/app/services/packages/debian/generate_distribution_service.rb
@@ -269,7 +269,7 @@ module Packages
# used by ExclusiveLeaseGuard
def lease_key
- "packages:debian:generate_distribution_service:distribution:#{@distribution.id}"
+ "packages:debian:generate_distribution_service:#{@distribution.class.container_type}_distribution:#{@distribution.id}"
end
# used by ExclusiveLeaseGuard
diff --git a/app/services/projects/update_repository_storage_service.rb b/app/services/projects/update_repository_storage_service.rb
index 7c63216af5e..cadf3012131 100644
--- a/app/services/projects/update_repository_storage_service.rb
+++ b/app/services/projects/update_repository_storage_service.rb
@@ -25,19 +25,6 @@ module Projects
end
end
- # The underlying FetchInternalRemote call uses a `git fetch` to move data
- # to the new repository, which leaves it in a less-well-packed state,
- # lacking bitmaps and commit graphs. Housekeeping will boost performance
- # significantly.
- def enqueue_housekeeping
- return unless Gitlab::CurrentSettings.housekeeping_enabled?
- return unless Feature.enabled?(:repack_after_shard_migration, project)
-
- Repositories::HousekeepingService.new(project, :gc).execute
- rescue Repositories::HousekeepingService::LeaseTaken
- # No action required
- end
-
def remove_old_paths
super
diff --git a/app/services/users/validate_manual_otp_service.rb b/app/services/users/validate_manual_otp_service.rb
index 96a827db13c..8ba76f5f593 100644
--- a/app/services/users/validate_manual_otp_service.rb
+++ b/app/services/users/validate_manual_otp_service.rb
@@ -3,6 +3,7 @@
module Users
class ValidateManualOtpService < BaseService
include ::Gitlab::Auth::Otp::Fortinet
+ include ::Gitlab::Auth::Otp::DuoAuth
def initialize(current_user)
@current_user = current_user
@@ -10,6 +11,8 @@ module Users
::Gitlab::Auth::Otp::Strategies::FortiAuthenticator::ManualOtp.new(current_user)
elsif forti_token_cloud_enabled?(current_user)
::Gitlab::Auth::Otp::Strategies::FortiTokenCloud.new(current_user)
+ elsif duo_auth_enabled?(current_user)
+ ::Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp.new(current_user)
else
::Gitlab::Auth::Otp::Strategies::Devise.new(current_user)
end
diff --git a/app/views/admin/background_migrations/index.html.haml b/app/views/admin/background_migrations/index.html.haml
index 0f76fdce416..00859bf6b66 100644
--- a/app/views/admin/background_migrations/index.html.haml
+++ b/app/views/admin/background_migrations/index.html.haml
@@ -5,7 +5,7 @@
.gl-flex-grow-1
%h3= s_('BackgroundMigrations|Background Migrations')
%p.light.gl-mb-0
- - learnmore_link = help_page_path('user/admin_area/monitoring/background_migrations')
+ - learnmore_link = help_page_path('update/background_migrations')
- learnmore_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: learnmore_link }
= html_escape(s_('BackgroundMigrations|Background migrations are used to perform data migrations whenever a migration exceeds the time limits in our guidelines. %{linkStart}Learn more%{linkEnd}')) % { linkStart: learnmore_link_start, linkEnd: '</a>'.html_safe }
diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
index eba5e7c6e9b..855177fd836 100644
--- a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
+++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
@@ -7,7 +7,7 @@
= link_to new_project_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_project", size: 50)
- .blank-state-body.gl-sm-pl-0.gl-pl-6
+ .blank-state-body.gl-sm-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Create a project')
%p
@@ -17,7 +17,7 @@
= link_to new_group_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_group", size: 50)
- .blank-state-body.gl-sm-pl-0.gl-pl-6
+ .blank-state-body.gl-sm-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Create a group')
%p
@@ -26,7 +26,7 @@
= link_to new_admin_user_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_user", size: 50)
- .blank-state-body.gl-sm-pl-0.gl-pl-6
+ .blank-state-body.gl-sm-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Add people')
%p
@@ -35,7 +35,7 @@
= link_to admin_root_path, class: link_classes do
.blank-state-icon
= custom_icon("configure_server", size: 50)
- .blank-state-body.gl-sm-pl-0.gl-pl-6
+ .blank-state-body.gl-sm-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Configure GitLab')
%p
diff --git a/app/views/dashboard/projects/_blank_state_welcome.html.haml b/app/views/dashboard/projects/_blank_state_welcome.html.haml
index a9a34af3f96..c5fdc31a775 100644
--- a/app/views/dashboard/projects/_blank_state_welcome.html.haml
+++ b/app/views/dashboard/projects/_blank_state_welcome.html.haml
@@ -5,7 +5,7 @@
= link_to new_project_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_project", size: 50)
- .blank-state-body.gl-sm-pl-0.gl-pl-6
+ .blank-state-body.gl-sm-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Create a project')
%p
@@ -19,7 +19,7 @@
= link_to new_group_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_group", size: 50)
- .blank-state-body.gl-sm-pl-0.gl-pl-6
+ .blank-state-body.gl-sm-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Create a group')
%p
@@ -28,7 +28,7 @@
= link_to trending_explore_projects_path, class: link_classes do
.blank-state-icon
= custom_icon("globe", size: 50)
- .blank-state-body.gl-sm-pl-0.gl-pl-6
+ .blank-state-body.gl-sm-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Explore public projects')
%p
@@ -37,7 +37,7 @@
= link_to Gitlab::Saas::doc_url, class: link_classes do
.blank-state-icon
= custom_icon("lightbulb", size: 50)
- .blank-state-body.gl-sm-pl-0.gl-pl-6
+ .blank-state-body.gl-sm-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Learn more about GitLab')
%p
diff --git a/app/views/groups/_import_group_from_file_panel.html.haml b/app/views/groups/_import_group_from_file_panel.html.haml
index 94ad9dc6da9..775b9c79817 100644
--- a/app/views/groups/_import_group_from_file_panel.html.haml
+++ b/app/views/groups/_import_group_from_file_panel.html.haml
@@ -17,7 +17,7 @@
.form-group
= f.label :file, s_('GroupsNew|Upload file')
.gl-font-weight-normal
- - import_export_link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: help_page_path('user/group/settings/import_export') }
+ - import_export_link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: help_page_path('user/group/import/index') }
= s_('GroupsNew|To import a group, navigate to the group settings for the GitLab source instance, %{link_start}generate an export file%{link_end}, and upload it here.').html_safe % { link_start: import_export_link_start, link_end: '</a>'.html_safe }
.gl-mt-3
= render 'shared/file_picker_button', f: f, field: :file, help_text: nil, classes: 'gl-button btn-confirm-secondary gl-mr-2'
diff --git a/app/views/groups/settings/_export.html.haml b/app/views/groups/settings/_export.html.haml
index b60eb134a9c..6b505755727 100644
--- a/app/views/groups/settings/_export.html.haml
+++ b/app/views/groups/settings/_export.html.haml
@@ -5,13 +5,12 @@
%p= _('Export this group with all related data.')
= render Pajamas::AlertComponent.new(variant: :warning, dismissible: false, alert_options: { class: 'gl-mb-4' }) do |c|
= c.body do
- - docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/import/index.md', anchor: 'migrate-groups-by-direct-transfer-recommended') }
+ - docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/import/index', anchor: 'migrate-groups-by-direct-transfer-recommended') }
- docs_link_end = '</a>'.html_safe
= s_('GroupsNew|This feature is deprecated and replaced by group migration by direct transfer. %{docs_link_start}Learn more%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: docs_link_end }
%p
- export_information = _('After the export is complete, download the data file from a notification email or from this page. You can then import the data file from the %{strong_text_start}Create new group%{strong_text_end} page of another GitLab instance.') % { strong_text_start: '<strong>'.html_safe, strong_text_end: '</strong>'.html_safe}
= export_information.html_safe
- = link_to _('Learn more.'), help_page_path('user/group/settings/import_export.md'), target: '_blank', rel: 'noopener noreferrer'
= render Pajamas::AlertComponent.new(dismissible: false, alert_options: { class: 'gl-mb-5' }) do |c|
= c.body do
%p.gl-mb-0
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index fee943042f9..3280dcf2cd4 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -5,5 +5,5 @@
.results.gl-md-display-flex.gl-mt-0
#js-search-sidebar{ class: search_bar_classes, data: { navigation_json: search_navigation_json } }
.gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
- = render partial: 'search/results_status' unless @search_objects.to_a.empty?
+ = render partial: 'search/results_status' if @search_objects.present?
= render partial: 'search/results_list'
diff --git a/app/views/search/_results_list.html.haml b/app/views/search/_results_list.html.haml
index ce4dd02b41d..c36acaf9ea8 100644
--- a/app/views/search/_results_list.html.haml
+++ b/app/views/search/_results_list.html.haml
@@ -2,7 +2,7 @@
= render partial: "search/results/timeout"
- elsif @search_results.respond_to?(:failed?) && @search_results.failed?
= render partial: "search/results/error"
-- elsif @search_objects.to_a.empty?
+- elsif @search_objects.blank?
= render partial: "search/results/empty"
- else
.gl-md-pl-5
diff --git a/app/workers/packages/debian/generate_distribution_worker.rb b/app/workers/packages/debian/generate_distribution_worker.rb
index 1eff3ea02dd..f0c753c3a9b 100644
--- a/app/workers/packages/debian/generate_distribution_worker.rb
+++ b/app/workers/packages/debian/generate_distribution_worker.rb
@@ -20,7 +20,7 @@ module Packages
loggable_arguments 0
def perform(container_type, distribution_id)
- @container_type = container_type
+ @container_type = container_type.to_sym
@distribution_id = distribution_id
return unless distribution
diff --git a/config/feature_flags/development/repack_after_shard_migration.yml b/config/feature_flags/development/search_index_integrity.yml
index 15b7a3e67b0..84e1e4b65c8 100644
--- a/config/feature_flags/development/repack_after_shard_migration.yml
+++ b/config/feature_flags/development/search_index_integrity.yml
@@ -1,8 +1,8 @@
---
-name: repack_after_shard_migration
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21502
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/195597
-milestone: '12.6'
+name: search_index_integrity
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112369
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/392981
+milestone: '15.10'
type: development
-group: group::source code
+group: group::global search
default_enabled: false
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 9c4c33d0e25..e8a88628a8a 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -1167,6 +1167,16 @@ production: &base
# client_id: 'YOUR_FORTI_TOKEN_CLOUD_CLIENT_ID'
# client_secret: 'YOUR_FORTI_TOKEN_CLOUD_CLIENT_SECRET'
+ # Duo Auth settings
+ duo_auth:
+ # Allow using Duo as an OTP provider
+ enabled: false
+
+ # Client ID and Secret to access Duo's API
+ # integration_key: 'YOUR_DUO_INTEGRATION_KEY'
+ # secret_key: 'YOUR_DUO_SECRET_KEY'
+ # hostname: 'YOUR_DUO_API_FQDN'
+
# Shared file storage settings
shared:
# path: /mnt/gitlab # Default: shared
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 22de00b99bf..9cb1be45b68 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -1016,6 +1016,12 @@ Settings['forti_token_cloud'] ||= Settingslogic.new({})
Settings.forti_token_cloud['enabled'] = false if Settings.forti_token_cloud['enabled'].nil?
#
+# DuoAuth
+#
+Settings['duo_auth'] ||= Settingslogic.new({})
+Settings.duo_auth['enabled'] = false if Settings.duo_auth['enabled'].nil?
+
+#
# Extra customization
#
Settings['extra'] ||= Settingslogic.new({})
diff --git a/config/metrics/counts_all/20230303131933_groups_inheriting_squash_tm_active.yml b/config/metrics/counts_all/20230303131933_groups_inheriting_squash_tm_active.yml
new file mode 100644
index 00000000000..294c044affb
--- /dev/null
+++ b/config/metrics/counts_all/20230303131933_groups_inheriting_squash_tm_active.yml
@@ -0,0 +1,22 @@
+---
+key_path: counts.groups_inheriting_squash_tm_active
+description: Count of active groups inheriting integrations for Squash TM
+product_section: dev
+product_stage: manage
+product_group: integrations
+product_category: integrations
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110909
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20230303131936_groups_squash_tm_active.yml b/config/metrics/counts_all/20230303131936_groups_squash_tm_active.yml
new file mode 100644
index 00000000000..0ccadb78bf0
--- /dev/null
+++ b/config/metrics/counts_all/20230303131936_groups_squash_tm_active.yml
@@ -0,0 +1,22 @@
+---
+key_path: counts.groups_squash_tm_active
+description: Count of groups with active integrations for Squash TM
+product_section: dev
+product_stage: manage
+product_group: integrations
+product_category: integrations
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110909
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20230303132041_instances_squash_tm_active.yml b/config/metrics/counts_all/20230303132041_instances_squash_tm_active.yml
new file mode 100644
index 00000000000..9f35216ddaf
--- /dev/null
+++ b/config/metrics/counts_all/20230303132041_instances_squash_tm_active.yml
@@ -0,0 +1,22 @@
+---
+key_path: counts.instances_squash_tm_active
+description: Count of instance-level integrations for Squash TM
+product_section: dev
+product_stage: manage
+product_group: integrations
+product_category: integrations
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110909
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20230303132048_projects_inheriting_squash_tm_active.yml b/config/metrics/counts_all/20230303132048_projects_inheriting_squash_tm_active.yml
new file mode 100644
index 00000000000..25944bfe05a
--- /dev/null
+++ b/config/metrics/counts_all/20230303132048_projects_inheriting_squash_tm_active.yml
@@ -0,0 +1,22 @@
+---
+key_path: counts.projects_inheriting_squash_tm_active
+description: Count of active projects inheriting integrations for Squash TM
+product_section: dev
+product_stage: manage
+product_group: integrations
+product_category: integrations
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110909
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20230303132352_projects_squash_tm_active.yml b/config/metrics/counts_all/20230303132352_projects_squash_tm_active.yml
new file mode 100644
index 00000000000..fedc0a801ba
--- /dev/null
+++ b/config/metrics/counts_all/20230303132352_projects_squash_tm_active.yml
@@ -0,0 +1,22 @@
+---
+key_path: counts.projects_squash_tm_active
+description: Count of projects with active integrations for Squash TM
+product_section: dev
+product_stage: manage
+product_group: integrations
+product_category: integrations
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110909
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 05273158ec5..c9b80dab03a 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -485,6 +485,10 @@
- 1
- - sbom_reports
- 1
+- - search_namespace_index_integrity
+ - 1
+- - search_project_index_integrity
+ - 1
- - security_auto_fix
- 1
- - security_orchestration_policy_rule_schedule_namespace
diff --git a/doc/administration/docs_self_host.md b/doc/administration/docs_self_host.md
index 095baafab70..932ffb87288 100644
--- a/doc/administration/docs_self_host.md
+++ b/doc/administration/docs_self_host.md
@@ -74,7 +74,7 @@ To run the GitLab product documentation website in a Docker container:
docker-compose up -d
```
-1. Visit `http://0.0.0.0:4000` to view the documentation website and verify that
+1. Visit `http://0.0.0.0:4000` to view the documentation website and verify that
it works.
1. [Redirect the help links to the new Docs site](#redirect-the-help-links-to-the-new-docs-site).
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index 7d10fdf770f..2b2eefdb17c 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -290,7 +290,7 @@ Plan.default.actual_limits.update!(group_hooks: 100)
Set the limit to `0` to disable it.
-The default maximum number of webhooks is `100` per project and `50` per group. Webhooks in a child group do not count towards the webhook limit of their parent group.
+The default maximum number of webhooks is `100` per project and `50` per group. Webhooks in a child group do not count towards the webhook limit of their parent group.
For GitLab.com, see the [webhook limits for GitLab.com](../user/gitlab_com/index.md#webhooks).
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index 5944b41dea4..1934ca6bff0 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -1792,7 +1792,7 @@ Updates to example must be made at:
# Redis
## Redis connection details
- ## First cluster that will host the cache
+ ## First cluster that will host the cache data
gitlab_rails['redis_cache_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_FIRST_CLUSTER>@gitlab-redis-cache'
gitlab_rails['redis_cache_sentinels'] = [
@@ -1801,22 +1801,11 @@ Updates to example must be made at:
{host: '10.6.0.53', port: 26379},
]
- ## Second cluster that will host the persistent queues, shared state, and actioncable
- gitlab_rails['redis_queues_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_shared_state_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_actioncable_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
+ ## Second cluster that hosts all other persistent data
+ redis['master_name'] = 'gitlab-redis-persistent'
+ redis['master_password'] = '<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>'
- gitlab_rails['redis_queues_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_shared_state_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_actioncable_sentinels'] = [
+ gitlab_rails['redis_sentinels'] = [
{host: '10.6.0.61', port: 26379},
{host: '10.6.0.62', port: 26379},
{host: '10.6.0.63', port: 26379},
@@ -1977,7 +1966,7 @@ On each node perform the following:
gitlab_rails['auto_migrate'] = false
## Redis connection details
- ## First cluster that will host the cache
+ ## First cluster that will host the cache data
gitlab_rails['redis_cache_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_FIRST_CLUSTER>@gitlab-redis-cache'
gitlab_rails['redis_cache_sentinels'] = [
@@ -1986,22 +1975,11 @@ On each node perform the following:
{host: '10.6.0.53', port: 26379},
]
- ## Second cluster that will host the persistent queues, shared state, and actioncable
- gitlab_rails['redis_queues_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_shared_state_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_actioncable_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
+ ## Second cluster that hosts all other persistent data
+ redis['master_name'] = 'gitlab-redis-persistent'
+ redis['master_password'] = '<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>'
- gitlab_rails['redis_queues_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_shared_state_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_actioncable_sentinels'] = [
+ gitlab_rails['redis_sentinels'] = [
{host: '10.6.0.61', port: 26379},
{host: '10.6.0.62', port: 26379},
{host: '10.6.0.63', port: 26379},
diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md
index 8f89a6c8238..08cb6e2cdff 100644
--- a/doc/administration/reference_architectures/25k_users.md
+++ b/doc/administration/reference_architectures/25k_users.md
@@ -1809,7 +1809,7 @@ Updates to example must be made at:
# Redis
## Redis connection details
- ## First cluster that will host the cache
+ ## First cluster that will host the cache data
gitlab_rails['redis_cache_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_FIRST_CLUSTER>@gitlab-redis-cache'
gitlab_rails['redis_cache_sentinels'] = [
@@ -1818,22 +1818,11 @@ Updates to example must be made at:
{host: '10.6.0.53', port: 26379},
]
- ## Second cluster that will host the persistent queues, shared state, and actioncable
- gitlab_rails['redis_queues_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_shared_state_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_actioncable_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
+ ## Second cluster that hosts all other persistent data
+ redis['master_name'] = 'gitlab-redis-persistent'
+ redis['master_password'] = '<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>'
- gitlab_rails['redis_queues_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_shared_state_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_actioncable_sentinels'] = [
+ gitlab_rails['redis_sentinels'] = [
{host: '10.6.0.61', port: 26379},
{host: '10.6.0.62', port: 26379},
{host: '10.6.0.63', port: 26379},
@@ -1996,7 +1985,7 @@ On each node perform the following:
gitlab_rails['auto_migrate'] = false
## Redis connection details
- ## First cluster that will host the cache
+ ## First cluster that will host the cache data
gitlab_rails['redis_cache_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_FIRST_CLUSTER>@gitlab-redis-cache'
gitlab_rails['redis_cache_sentinels'] = [
@@ -2005,22 +1994,11 @@ On each node perform the following:
{host: '10.6.0.53', port: 26379},
]
- ## Second cluster that will host the queues, shared state, and actionable
- gitlab_rails['redis_queues_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_shared_state_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_actioncable_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
+ ## Second cluster that hosts all other persistent data
+ redis['master_name'] = 'gitlab-redis-persistent'
+ redis['master_password'] = '<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>'
- gitlab_rails['redis_queues_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_shared_state_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_actioncable_sentinels'] = [
+ gitlab_rails['redis_sentinels'] = [
{host: '10.6.0.61', port: 26379},
{host: '10.6.0.62', port: 26379},
{host: '10.6.0.63', port: 26379},
diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md
index abdc1d28312..3f2771dda29 100644
--- a/doc/administration/reference_architectures/50k_users.md
+++ b/doc/administration/reference_architectures/50k_users.md
@@ -1805,7 +1805,7 @@ Updates to example must be made at:
# Redis
## Redis connection details
- ## First cluster that will host the cache
+ ## First cluster that will host the cache data
gitlab_rails['redis_cache_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_FIRST_CLUSTER>@gitlab-redis-cache'
gitlab_rails['redis_cache_sentinels'] = [
@@ -1814,22 +1814,11 @@ Updates to example must be made at:
{host: '10.6.0.53', port: 26379},
]
- ## Second cluster that will host the persistent queues, shared state, and actioncable
- gitlab_rails['redis_queues_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_shared_state_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_actioncable_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
+ ## Second cluster that hosts all other persistent data
+ redis['master_name'] = 'gitlab-redis-persistent'
+ redis['master_password'] = '<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>'
- gitlab_rails['redis_queues_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_shared_state_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_actioncable_sentinels'] = [
+ gitlab_rails['redis_sentinels'] = [
{host: '10.6.0.61', port: 26379},
{host: '10.6.0.62', port: 26379},
{host: '10.6.0.63', port: 26379},
@@ -1999,7 +1988,7 @@ On each node perform the following:
gitlab_rails['auto_migrate'] = false
## Redis connection details
- ## First cluster that will host the cache
+ ## First cluster that will host the cache data
gitlab_rails['redis_cache_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_FIRST_CLUSTER>@gitlab-redis-cache'
gitlab_rails['redis_cache_sentinels'] = [
@@ -2008,22 +1997,11 @@ On each node perform the following:
{host: '10.6.0.53', port: 26379},
]
- ## Second cluster that will host the persistent queues, shared state, and actionable
- gitlab_rails['redis_queues_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_shared_state_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
- gitlab_rails['redis_actioncable_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
+ ## Second cluster that hosts all other persistent data
+ redis['master_name'] = 'gitlab-redis-persistent'
+ redis['master_password'] = '<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>'
- gitlab_rails['redis_queues_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_shared_state_sentinels'] = [
- {host: '10.6.0.61', port: 26379},
- {host: '10.6.0.62', port: 26379},
- {host: '10.6.0.63', port: 26379},
- ]
- gitlab_rails['redis_actioncable_sentinels'] = [
+ gitlab_rails['redis_sentinels'] = [
{host: '10.6.0.61', port: 26379},
{host: '10.6.0.62', port: 26379},
{host: '10.6.0.63', port: 26379},
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6e4213f78a1..6c7a8bcdee1 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -11722,6 +11722,7 @@ CI/CD variables for a GitLab instance.
| <a id="cijobrefpath"></a>`refPath` | [`String`](#string) | Path to the ref. |
| <a id="cijobretried"></a>`retried` | [`Boolean`](#boolean) | Indicates that the job has been retried. |
| <a id="cijobretryable"></a>`retryable` | [`Boolean!`](#boolean) | Indicates the job can be retried. |
+| <a id="cijobrunnermachine"></a>`runnerMachine` **{warning-solid}** | [`CiRunnerMachine`](#cirunnermachine) | **Introduced** in 15.11. This feature is in Alpha. It can be changed or removed at any time. Runner machine assigned to the job. |
| <a id="cijobscheduledat"></a>`scheduledAt` | [`Time`](#time) | Schedule for the build. |
| <a id="cijobschedulingtype"></a>`schedulingType` | [`String`](#string) | Type of job scheduling. Value is `dag` if the job uses the `needs` keyword, and `stage` otherwise. |
| <a id="cijobshortsha"></a>`shortSha` | [`String!`](#string) | Short SHA1 ID of the commit. |
@@ -11946,6 +11947,7 @@ Returns [`CiRunnerStatus!`](#cirunnerstatus).
| <a id="cirunnermachinerevision"></a>`revision` | [`String`](#string) | Revision of the runner. |
| <a id="cirunnermachinerunner"></a>`runner` | [`CiRunner`](#cirunner) | Runner configuration for the runner machine. |
| <a id="cirunnermachinestatus"></a>`status` | [`CiRunnerStatus!`](#cirunnerstatus) | Status of the runner machine. |
+| <a id="cirunnermachinesystemid"></a>`systemId` | [`String!`](#string) | System ID associated with the runner machine. |
| <a id="cirunnermachineversion"></a>`version` | [`String`](#string) | Version of the runner. |
### `CiSecureFileRegistry`
@@ -22787,6 +22789,15 @@ Mode of a commit action.
| <a id="commitencodingbase64"></a>`BASE64` | Base64 encoding. |
| <a id="commitencodingtext"></a>`TEXT` | Text encoding. |
+### `ComplianceFrameworkPresenceFilter`
+
+ComplianceFramework of a project for filtering.
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="complianceframeworkpresencefilterany"></a>`ANY` | Any compliance framework is assigned. |
+| <a id="complianceframeworkpresencefilternone"></a>`NONE` | No compliance framework is assigned. |
+
### `ComplianceViolationReason`
Reason for the compliance violation.
@@ -24181,6 +24192,7 @@ State of a Sentry error.
| <a id="servicetypeshimo_service"></a>`SHIMO_SERVICE` | ShimoService type. |
| <a id="servicetypeslack_service"></a>`SLACK_SERVICE` | SlackService type. |
| <a id="servicetypeslack_slash_commands_service"></a>`SLACK_SLASH_COMMANDS_SERVICE` | SlackSlashCommandsService type. |
+| <a id="servicetypesquash_tm_service"></a>`SQUASH_TM_SERVICE` | SquashTmService type. |
| <a id="servicetypeteamcity_service"></a>`TEAMCITY_SERVICE` | TeamcityService type. |
| <a id="servicetypeunify_circuit_service"></a>`UNIFY_CIRCUIT_SERVICE` | UnifyCircuitService type. |
| <a id="servicetypewebex_teams_service"></a>`WEBEX_TEAMS_SERVICE` | WebexTeamsService type. |
@@ -26102,6 +26114,7 @@ Attributes for defining a CI/CD variable.
| ---- | ---- | ----------- |
| <a id="complianceframeworkfiltersid"></a>`id` | [`ComplianceManagementFrameworkID`](#compliancemanagementframeworkid) | ID of the compliance framework. |
| <a id="complianceframeworkfiltersnot"></a>`not` | [`NegatedComplianceFrameworkFilters`](#negatedcomplianceframeworkfilters) | Negated compliance framework filter input. |
+| <a id="complianceframeworkfilterspresencefilter"></a>`presenceFilter` | [`ComplianceFrameworkPresenceFilter`](#complianceframeworkpresencefilter) | Checks presence of compliance framework of the project, "none" and "any" values are supported. |
### `ComplianceFrameworkInput`
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index c4b9613d3d7..e25753b892e 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -1596,6 +1596,43 @@ Get MockCI integration settings for a project.
GET /projects/:id/integrations/mock-ci
```
+## Squash TM
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/337855) in GitLab 15.10.
+
+Update [Squash TM](https://www.squashtest.com/product-squash-tm?lang=en) requirements when GitLab issues are modified.
+
+### Create/Edit Squash TM integration
+
+Set Squash TM integration settings for a project.
+
+```plaintext
+PUT /projects/:id/integrations/squash-tm
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+|-------------------------|--------|----------|-------------------------------|
+| `url` | string | yes | URL of the Squash TM webhook. |
+| `token` | string | no | Optional token |
+
+### Disable Squash TM integration
+
+Disable the Squash TM integration for a project. Integration settings are preserved.
+
+```plaintext
+DELETE /projects/:id/integrations/squash-tm
+```
+
+### Get Squash TM integration settings
+
+Get Squash TM integration settings for a project.
+
+```plaintext
+GET /projects/:id/integrations/squash-tm
+```
+
## YouTrack
YouTrack issue tracker
diff --git a/doc/architecture/blueprints/cells/index.md b/doc/architecture/blueprints/cells/index.md
index 099c1dfe0ec..54244265b30 100644
--- a/doc/architecture/blueprints/cells/index.md
+++ b/doc/architecture/blueprints/cells/index.md
@@ -179,7 +179,7 @@ Cells is a fundamental architecture change that impacts other sections and stage
Based on discussions with other groups the net impact of introducing Cells and a new entity called organizations is mostly neutral. It may slow down development in some areas. We did not discover major blockers for other teams.
1. We need to resolve naming conflicts (proposal is TBD)
-1. Cells requires introducing Organizations. Organizations are a new entity **above** top-level groups. Because this is a new entity, it may impact the ability to consolidate settings for Group::Organization and influence their decision on [how to approach introducing a an organization](https://gitlab.com/gitlab-org/gitlab/-/issues/376285#approach-2-workspace-is-built-on-top-of-top-level-groups)
+1. Cells requires introducing Organizations. Organizations are a new entity **above** top-level groups. Because this is a new entity, it may impact the ability to consolidate settings for Group::Organization and influence their decision on [how to approach introducing a an organization](https://gitlab.com/gitlab-org/gitlab/-/issues/376285#approach-2-organization-is-built-on-top-of-top-level-groups)
1. Organizations may make it slightly easier for Fulfillment to realize their billing plans.
### Impact on Group::Organization
diff --git a/doc/architecture/blueprints/clickhouse_usage/index.md b/doc/architecture/blueprints/clickhouse_usage/index.md
index 390097a4cca..2781ea15a55 100644
--- a/doc/architecture/blueprints/clickhouse_usage/index.md
+++ b/doc/architecture/blueprints/clickhouse_usage/index.md
@@ -34,11 +34,11 @@ As ClickHouse has already been selected for use at GitLab, our main goal now is
The following are links to proposals in the form of blueprints that address technical challenges to using ClickHouse across a wide variety of features.
1. Scalable data ingestion pipeline.
- - How do we ingest large volumes of data from GitLab into ClickHouse either directly or by replicating existing data?
+ - How do we ingest large volumes of data from GitLab into ClickHouse either directly or by replicating existing data?
1. Supporting ClickHouse for self-managed installations.
- For which use-cases and scales does it make sense to run ClickHouse for self-managed and what are the associated costs?
- How can we best support self-managed installation of ClickHouse for different types/sizes of environments?
- - Consider using the [Opstrace ClickHouse operator](https://gitlab.com/gitlab-org/opstrace/opstrace/-/tree/main/clickhouse-operator) as the basis for a canonical distribution.
+ - Consider using the [Opstrace ClickHouse operator](https://gitlab.com/gitlab-org/opstrace/opstrace/-/tree/main/clickhouse-operator) as the basis for a canonical distribution.
- Consider exposing Clickhouse backend as [GitLab Plus](https://gitlab.com/groups/gitlab-org/-/epics/308) to combine benefits of using self-managed instance and GitLab-managed database.
- Should we develop abstractions for querying and data ingestion to avoid requiring ClickHouse for small-scale installations?
1. Abstraction layer for features to leverage both ClickHouse or PostreSQL.
diff --git a/doc/development/cicd/cicd_tables.md b/doc/development/cicd/cicd_tables.md
index c0f5f9c75a4..c86540c10f0 100644
--- a/doc/development/cicd/cicd_tables.md
+++ b/doc/development/cicd/cicd_tables.md
@@ -27,7 +27,7 @@ so the best solution is to create the tables using raw SQL:
id bigint NOT NULL,
partition_id bigint NOT NULL,
build_id bigint NOT NULL,
- PRIMARY KEY (partition_id, id),
+ PRIMARY KEY (id, partition_id),
CONSTRAINT fk_bb490f12fe_p FOREIGN KEY (partition_id, build_id)
REFERENCES ci_builds(partition_id, id)
ON UPDATE CASCADE ON DELETE CASCADE
@@ -51,6 +51,8 @@ When creating the routing table:
- Each new table needs a `partition_id` column and its value must equal
the value from the related association. In this example, that is `ci_builds`. All resources
belonging to a pipeline share the same `partition_id` value.
+- The primary key must have the columns ordered this way to allow efficient
+ search only by `id`.
- The foreign key constraint must include the `ON UPDATE CASCADE` option because
the `partition_id` value should be able to update it for re-balancing the
partitions.
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index e384b41f6f2..f39d93a39bc 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -201,7 +201,7 @@ Example commit message template that can be used on your machine that embodies t
To make sure that your merge request can be approved, please ensure that it meets
the contribution acceptance criteria below:
-1. The change is as small as possible.
+1. The change is as small as possible.
1. If the merge request contains more than 500 changes:
- Explain the reason
- Mention a maintainer
diff --git a/doc/development/distribution/index.md b/doc/development/distribution/index.md
new file mode 100644
index 00000000000..168e3854eca
--- /dev/null
+++ b/doc/development/distribution/index.md
@@ -0,0 +1,35 @@
+---
+stage: Systems
+group: Distribution
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+description: "GitLab's development guidelines for Distribution"
+---
+
+# Contribute to GitLab Distribution
+
+Learn how to add new components and services to the GitLab application.
+
+## Support all package methods
+
+Additions must support both Omnibus GitLab and Cloud Native GitLab. Changes
+to one must be made to the other to retain feature parity.
+
+## Contributing
+
+The primary projects handled by Distribution are listed below. For more
+information, visit the [Distribution team engineering handbook page](https://about.gitlab.com/handbook/engineering/development/enablement/systems/distribution/)
+or select one of the subsections in the navigation bar.
+
+### GitLab application
+
+- [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab)
+- [Cloud Native GitLab (CNG)](https://gitlab.com/gitlab-org/build/CNG)
+- [GitLab Operator](https://gitlab.com/gitlab-org/cloud-native/gitlab-operator)
+- [GitLab Chart](https://gitlab.com/gitlab-org/charts/gitlab)
+
+### Components and tools
+
+- [Omnibus GitLab Builder](https://gitlab.com/gitlab-org/gitlab-omnibus-builder)
+- [Omnibus Fork](https://gitlab.com/gitlab-org/omnibus)
+- [GitLab Logger](https://gitlab.com/gitlab-org/cloud-native/gitlab-logger)
+- [Issue Bot](https://gitlab.com/gitlab-org/distribution/issue-bot)
diff --git a/doc/integration/jira/connect-app.md b/doc/integration/jira/connect-app.md
index 9f4fac5f212..8bbac021849 100644
--- a/doc/integration/jira/connect-app.md
+++ b/doc/integration/jira/connect-app.md
@@ -285,3 +285,29 @@ To resolve this issue on GitLab self-managed, follow one of the solutions below,
- Contact the [Jira Software Cloud support](https://support.atlassian.com/jira-software-cloud/) and ask to trigger a new installed lifecycle event for the GitLab for Jira Cloud app in your namespace.
- In all GitLab versions:
- Re-install the GitLab for Jira Cloud app. This might remove all already synced development panel data.
+
+## Security considerations
+
+The GitLab for Jira Cloud app connects GitLab and Jira, as data must be shared between the two applications and access must be granted in both directions.
+
+## Access to GitLab through OAuth
+
+GitLab does not share an access token with Jira. However, users must authenticate via OAuth to configure the app.
+
+An access token is retrieved via [PKCE](https://www.rfc-editor.org/rfc/rfc7636) OAuth flow, and stored only on the client side.
+The app-frontend that initializes the OAuth flow is a JavaScript application, which is loaded from GitLab through an iframe on Jira.
+
+The OAuth application requires the `api` scope that grants complete read/write access to the API, including to all groups and projects, the container registry, and the package registry.
+However, the GitLab for Jira Cloud app only uses this access to:
+
+- Display namespaces to be linked.
+- Link namespaces.
+
+Access through OAuth is only needed for the time a user configures the GitLab for Jira Cloud app. For more information, see [Access token expiration](../oauth_provider.md#access-token-expiration).
+
+## Access to Jira through access token
+
+Jira shares an access token with GitLab to authenticate and authorize data pushes to Jira.
+As part of the app installation process, Jira sends a handshake request to GitLab containing the access token.
+The handshake is signed with an [asymmetric JWT](https://developer.atlassian.com/cloud/jira/platform/understanding-jwt-for-connect-apps/)
+and the access token is stored encrypted with `AES256-GCM` on GitLab.
diff --git a/doc/subscriptions/gitlab_dedicated/index.md b/doc/subscriptions/gitlab_dedicated/index.md
index b196aae549a..0c2b6375d7a 100644
--- a/doc/subscriptions/gitlab_dedicated/index.md
+++ b/doc/subscriptions/gitlab_dedicated/index.md
@@ -62,7 +62,7 @@ During onboarding, you can specify an AWS KMS encryption key stored in your own
GitLab Dedicated offers the following [compliance certifications](https://about.gitlab.com/security/):
-- SOC 2 Type 1 Report (Security and Confidentiality criteria)
+- SOC 2 Type 1 Report (Security and Confidentiality criteria)
- ISO/IEC 27001:2013
- ISO/IEC 27017:2015
- ISO/IEC 27018:2019
diff --git a/doc/user/application_security/dast/browser_based.md b/doc/user/application_security/dast/browser_based.md
index d9938aaa94a..88be88ad00e 100644
--- a/doc/user/application_security/dast/browser_based.md
+++ b/doc/user/application_security/dast/browser_based.md
@@ -172,7 +172,7 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
| `DAST_BROWSER_ACTION_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to complete an action. |
| `DAST_BROWSER_ALLOWED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. Headers set using `DAST_REQUEST_HEADERS` are added to every request made to these hostnames. |
| `DAST_BROWSER_COOKIES` | dictionary | `abtesting_group:3,region:locked` | A cookie name and value to be added to every request. |
-| `DAST_BROWSER_CRAWL_GRAPH` | boolean | `true` | Set to `true` to generate an SVG graph of navigation paths visited during crawl phase of the scan. You must also define `gl-dast-crawl-graph.svg` as a CI job artifact to be able to access the generated graph. |
+| `DAST_BROWSER_CRAWL_GRAPH` | boolean | `true` | Set to `true` to generate an SVG graph of navigation paths visited during crawl phase of the scan. You must also define `gl-dast-crawl-graph.svg` as a CI job artifact to be able to access the generated graph. |
| `DAST_BROWSER_DEVTOOLS_LOG` | string | `Default:messageAndBody,truncate:2000` | Set to log protocol messages between DAST and the Chromium browser. | |
| `DAST_BROWSER_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `600ms` | The maximum amount of time to wait for an element before determining it is ready for analysis. |
| `DAST_BROWSER_EXCLUDED_ELEMENTS` | selector | `a[href='2.html'],css:.no-follow` | Comma-separated list of selectors that are ignored when scanning. |
diff --git a/doc/user/enterprise_user/index.md b/doc/user/enterprise_user/index.md
index 3daee460956..b6a823c656f 100644
--- a/doc/user/enterprise_user/index.md
+++ b/doc/user/enterprise_user/index.md
@@ -31,6 +31,45 @@ Although a user can be a member of more than one group, each user account can be
provisioned by only one group. As a result, a user is considered an enterprise
user under one top-level group only.
+## Verified domains for groups
+
+The following automated processes use [verified domains](../project/pages/custom_domains_ssl_tls_certification/index.md) to run:
+
+- [Bypass email confirmation for provisioned users](#bypass-email-confirmation-for-provisioned-users).
+
+### Set up a verified domain
+
+Prerequisites:
+
+- A project with [GitLab Pages](../project/pages/index.md), served under the default Pages domain `*.gitlab.io`.
+- A custom domain name `example.com` or subdomain `subdomain.example.com`.
+- Access to your domain's server control panel to set up a DNS `TXT` record to verify your domain's ownership.
+
+Setting up a verified domain is similar to [setting up a custom domain on GitLab Pages](../project/pages/custom_domains_ssl_tls_certification/index.md). However, you must:
+
+- Only configure the DNS `TXT` record to verify the domain's ownership.
+- Ignore instructions for the `A`, `CNAME`, and `ALIAS` records.
+
+1. [Add a custom domain](../project/pages/custom_domains_ssl_tls_certification/index.md#1-add-a-custom-domain) for the matching email domain.
+ - The domain must match the email domain exactly. For example, if your email is `username@example.com`, verify the `example.com` domain.
+1. [Get a verification code](../project/pages/custom_domains_ssl_tls_certification/index.md#2-get-the-verification-code).
+1. [Set up the DNS `TXT`](../project/pages/custom_domains_ssl_tls_certification/index.md#3-set-up-dns-records) for your custom domain.
+1. [Verify the domain's ownership](../project/pages/custom_domains_ssl_tls_certification/index.md#4-verify-the-domains-ownership).
+1. Optional. [Add more domain aliases](../project/pages/custom_domains_ssl_tls_certification/index.md#add-more-domain-aliases).
+
+### View domains in group
+
+To view all configured domains in your group:
+
+1. On the top bar, select **Main menu > Groups** and find your top-level group.
+1. On the left sidebar, select **Settings > Domain Verification**.
+
+You then see:
+
+- A list of added domains.
+- The domains' status of **Verified** or **Unverified**.
+- The project where the domain has been configured.
+
## Manage enterprise users in a namespace
A top-level Owner of a namespace on a paid plan can retrieve information about and
diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md
index 0fd4c018bd4..3a0fc86e803 100644
--- a/doc/user/group/epics/manage_epics.md
+++ b/doc/user/group/epics/manage_epics.md
@@ -41,8 +41,6 @@ The newly created epic opens.
### Start and due date inheritance
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7332) in GitLab 12.5 to replace **From milestones**.
-
If you select **Inherited**:
- For the **start date**: GitLab scans all child epics and issues assigned to the epic,
@@ -52,7 +50,7 @@ If you select **Inherited**:
and sets the due date to match the latest due date found in the child epics or the milestone
assigned to the issues.
-These are dynamic dates and recalculated if any of the following occur:
+These dates are dynamic and recalculated if any of the following occur:
- A child epic's dates change.
- Milestones are reassigned to an issue.
@@ -123,8 +121,6 @@ To reorder list items, when viewing an epic:
## Bulk edit epics
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7250) in GitLab 12.2.
-
Users with at least the Reporter role can manage epics.
When bulk editing epics in a group, you can edit their labels.
@@ -233,7 +229,6 @@ than 1000. The cached value is rounded to thousands or millions and updated ever
## Filter the list of epics
-> - Filtering by epics was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195704) in GitLab 12.9.
> - Filtering by child epics was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9029) in GitLab 13.0.
> - Filtering by the user's reaction emoji [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325630) in GitLab 13.11.
> - Sorting by epic titles [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331625) in GitLab 14.1.
@@ -341,7 +336,7 @@ in relation to epics.
### View issues assigned to an epic
-On the **Epics and Issues** tab, you can see epics and issues assigned to this epic.
+On the **Child issues and epics** section, you can see epics and issues assigned to this epic.
Only epics and issues that you can access show on the list.
You can always view the issues assigned to the epic if they are in the group's child project.
@@ -350,7 +345,7 @@ of its parent group.
### View count of issues in an epic
-On the **Epics and Issues** tab, under each epic name, hover over the total counts.
+On the **Child issues and epics** section, under each epic name, hover over the total counts.
The number indicates all epics associated with the project, including issues
you might not have permission to.
@@ -365,7 +360,7 @@ automatically added to the epic.
> Minimum required role for the project [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382506) from Reporter to Guest in GitLab 15.8.
You can add existing issues to an epic, including issues in a project from a [different group hierarchy](index.md#child-issues-from-different-group-hierarchies).
-Newly added issues appear at the top of the list of issues in the **Epics and Issues** tab.
+Newly added issues appear at the top of the list of issues in the **Child issues and epics** section.
An epic contains a list of issues and an issue can be associated with at most one epic.
When you add a new issue that's already linked to an epic, the issue is automatically unlinked from its
@@ -377,13 +372,13 @@ Prerequisites:
To add an existing issue to an epic:
-1. On the epic's page, under **Epics and Issues**, select **Add**.
+1. On the epic's page, under **Child issues and epics**, select **Add**.
1. Select **Add an existing issue**.
1. Identify the issue to be added, using either of the following methods:
- Paste the link of the issue.
- Search for the desired issue by entering part of the issue's title, then selecting the desired
- match ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9126) in GitLab 12.5). Issues
- from different group hierarchies do not appear in search results. To add such an issue, enter its full URL.
+ match. Issues from different group hierarchies do not appear in search results.
+ To add such an issue, enter its full URL.
If there are multiple issues to be added, press <kbd>Space</kbd> and repeat this step.
1. Select **Add**.
@@ -401,7 +396,7 @@ Prerequisites:
To create an issue from an epic:
-1. On the epic's page, under **Epics and Issues**, select **Add**.
+1. On the epic's page, under **Child issues and epics**, select **Add**.
1. Select **Add a new issue**.
1. Under **Title**, enter the title for the new issue.
1. From the **Project** dropdown list, select the project in which the issue should be created.
@@ -430,10 +425,9 @@ To remove an issue from an epic:
### Reorder issues assigned to an epic
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9367) in GitLab 12.5.
-> - Minimum required role for the project [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382506) from Reporter to Guest in GitLab 15.8.
+> Minimum required role for the project [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382506) from Reporter to Guest in GitLab 15.8.
-New issues appear at the top of the list in the **Epics and Issues** tab.
+New issues appear at the top of the list in the **Child issues and epics** section.
You can reorder the list of issues by dragging them.
Prerequisites:
@@ -442,7 +436,7 @@ Prerequisites:
To reorder issues assigned to an epic:
-1. Go to the **Epics and Issues** tab.
+1. Go to the **Child issues and epics** section.
1. Drag issues into the desired order.
### Move issues between epics **(ULTIMATE)**
@@ -450,7 +444,7 @@ To reorder issues assigned to an epic:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33039) in GitLab 13.0.
> - Minimum required role for the project [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382506) from Reporter to Guest in GitLab 15.8.
-New issues appear at the top of the list in the **Epics and Issues**
+New issues appear at the top of the list in the **Child issues and epics**
tab. You can move issues from one epic to another.
Prerequisites:
@@ -459,13 +453,12 @@ Prerequisites:
To move an issue to another epic:
-1. Go to the **Epics and Issues** tab.
+1. Go to the **Child issues and epics** section.
1. Drag issues into the desired parent epic in the visible hierarchy.
### Promote an issue to an epic
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3777) in GitLab 11.6.
-> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/37081) from GitLab Ultimate to GitLab Premium in 12.8.
+> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/37081) from GitLab Ultimate to GitLab Premium in 12.8.
Prerequisites:
@@ -509,7 +502,7 @@ For more on epic templates, see [Epic Templates - Repeatable sets of issues](htt
## Multi-level child epics **(ULTIMATE)**
You can add any epic that belongs to a group or subgroup of the parent epic's group.
-New child epics appear at the top of the list of epics in the **Epics and Issues** tab.
+New child epics appear at the top of the list of epics in the **Child issues and epics** section.
When you add an epic that's already linked to a parent epic, the link to its current parent is removed.
@@ -522,7 +515,7 @@ The maximum number of direct child epics is 100.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/8502) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `child_epics_from_different_hierarchies`. Disabled by default.
> - Minimum required role for the group [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382503) from Reporter to Guest in GitLab 15.7.
> - Cross-group child epics [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/375622) in GitLab 15.9. Enabled by default.
-> - [Feature flag `child_epics_from_different_hierarchies`](https://gitlab.com/gitlab-org/gitlab/-/issues/382719)) removed in GitLab 15.10.
+> - [Feature flag `child_epics_from_different_hierarchies`](https://gitlab.com/gitlab-org/gitlab/-/issues/382719) removed in GitLab 15.10.
You can add a child epic that belongs to a group that is different from the parent epic's group.
@@ -544,7 +537,7 @@ Prerequisites:
To add a new epic as child epic:
1. In an epic, in the **Child issues and epics** section, select **Add > Add a new epic**.
-1. Select a group from the dropdown. The epic's group is selected by default.
+1. Select a group from the dropdown list. The epic's group is selected by default.
1. Enter a title for the new epic.
1. Select **Create epic**.
@@ -553,7 +546,7 @@ To add an existing epic as child epic:
1. In an epic, in the **Child issues and epics** section, select **Add > Add an existing epic**.
1. Identify the epic to be added, using either of the following methods:
- Paste the link of the epic.
- - Search for the desired issue by entering part of the epic's title, then selecting the desired match. This search is only available for epics within the same group hierarchy.
+ - Search for the desired issue by entering part of the epic's title, then selecting the desired match. This search is only available for epics in the same group hierarchy.
If there are multiple epics to be added, press <kbd>Space</kbd> and repeat this step.
1. Select **Add**.
@@ -563,7 +556,7 @@ To add an existing epic as child epic:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33039) in GitLab 13.0.
> - Minimum required role for the group [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382503) from Reporter to Guest in GitLab 15.7.
-New child epics appear at the top of the list in the **Epics and Issues** tab.
+New child epics appear at the top of the list in the **Child issues and epics** section.
You can move child epics from one epic to another.
When you add a new epic that's already linked to a parent epic, the link to its current parent is removed.
Issues and child epics cannot be intermingled.
@@ -574,15 +567,14 @@ Prerequisites:
To move child epics to another epic:
-1. Go to the **Epics and Issues** tab.
+1. Go to the **Child issues and epics** section.
1. Drag epics into the desired parent epic.
### Reorder child epics assigned to an epic
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9367) in GitLab 12.5.
-> - Minimum required role for the group [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382503) from Reporter to Guest in GitLab 15.7.
+> Minimum required role for the group [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382503) from Reporter to Guest in GitLab 15.7.
-New child epics appear at the top of the list in the **Epics and Issues** tab.
+New child epics appear at the top of the list in the **Child issues and epics** section.
You can reorder the list of child epics.
Prerequisites:
@@ -591,7 +583,7 @@ Prerequisites:
To reorder child epics assigned to an epic:
-1. Go to the **Epics and Issues** tab.
+1. Go to the **Child issues and epics** section.
1. Drag epics into the desired order.
### Remove a child epic from a parent epic
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 430a00a839a..eb43f0636f2 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -435,7 +435,7 @@ convert the information to XML. An example SAML response is shown here.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/238461) in GitLab 15.4.
By default, users provisioned with SAML or SCIM are sent a verification email to verify their identity. Instead, you can
-[configure GitLab with a custom domain](../../project/pages/custom_domains_ssl_tls_certification/index.md) and GitLab
+[configure GitLab with a custom domain](../../enterprise_user/index.md#set-up-a-verified-domain) and GitLab
automatically confirms user accounts. Users still receive an
[enterprise user](../../enterprise_user/index.md) welcome email. Confirmation is
bypassed for users:
diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md
index ad4863c226f..62772c5b379 100644
--- a/doc/user/infrastructure/iac/terraform_state.md
+++ b/doc/user/infrastructure/iac/terraform_state.md
@@ -42,7 +42,7 @@ For self-managed GitLab, before you can use GitLab for your Terraform state file
- An administrator must [set up Terraform state storage](../../../administration/terraform_state.md).
- You must enable the **Infrastructure** menu for your project. Go to **Settings > General**,
- expand **Visibility, project features, permissions**, and under **Operations**, turn on the toggle.
+ expand **Visibility, project features, permissions**, and under **Infrastructure**, turn on the toggle.
## Initialize a Terraform state as a backend by using GitLab CI/CD
diff --git a/doc/user/project/code_owners.md b/doc/user/project/code_owners.md
index c42e4304e3b..3b70c0ef185 100644
--- a/doc/user/project/code_owners.md
+++ b/doc/user/project/code_owners.md
@@ -385,6 +385,10 @@ model/db @database
README.md @docs
```
+## Technical Resources
+
+[Code Owners development guidelines](../../../ee/development/code_owners/index.md)
+
## Troubleshooting
### Approvals shown as optional
diff --git a/doc/user/project/integrations/index.md b/doc/user/project/integrations/index.md
index 57947e21736..9ff6ad2a237 100644
--- a/doc/user/project/integrations/index.md
+++ b/doc/user/project/integrations/index.md
@@ -72,13 +72,14 @@ You can configure the following integrations.
| [Pipelines emails](pipeline_status_emails.md) | Send the pipeline status to a list of recipients by email. | **{dotted-circle}** No |
| [Pivotal Tracker](pivotal_tracker.md) | Add commit messages as comments to Pivotal Tracker stories. | **{dotted-circle}** No |
| [Prometheus](prometheus.md) | Monitor application metrics. | **{dotted-circle}** No |
-| [Pumble](pumble.md) | Send event notifications to a Pumble channel. | **{dotted-circle}** No |
+| [Pumble](pumble.md) | Send event notifications to a Pumble channel. | **{dotted-circle}** No |
| Pushover | Get real-time notifications on your device. | **{dotted-circle}** No |
| [Redmine](redmine.md) | Use Redmine as the issue tracker. | **{dotted-circle}** No |
-| [Shimo Workspace](shimo.md) | Use Shimo instead of the GitLab Wiki. | **{dotted-circle}** No |
+| [Shimo Workspace](shimo.md) | Use Shimo instead of the GitLab Wiki. | **{dotted-circle}** No |
| [GitLab for Slack app](gitlab_slack_application.md) | Use Slack's official GitLab application. | **{dotted-circle}** No |
| [Slack notifications](slack.md) | Send notifications about project events to Slack. | **{dotted-circle}** No |
| [Slack slash commands](slack_slash_commands.md) | Enable slash commands in a workspace. | **{dotted-circle}** No |
+| [Squash TM](squash_tm.md) | Update Squash TM requirements when GitLab issues are modified. | **{check-circle}** Yes |
| [Unify Circuit](unify_circuit.md) | Send notifications about project events to Unify Circuit. | **{dotted-circle}** No |
| [Webex Teams](webex_teams.md) | Receive events notifications. | **{dotted-circle}** No |
| [YouTrack](youtrack.md) | Use YouTrack as the issue tracker. | **{dotted-circle}** No |
diff --git a/doc/user/project/integrations/squash_tm.md b/doc/user/project/integrations/squash_tm.md
new file mode 100644
index 00000000000..0f63b4a48db
--- /dev/null
+++ b/doc/user/project/integrations/squash_tm.md
@@ -0,0 +1,37 @@
+---
+stage: Ecosystem
+group: Integrations
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Squash TM integration **(FREE SELF)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/337855) in GitLab 15.10.
+
+When [Squash TM](https://www.squashtest.com/squash-gitlab-integration?lang=en) (Test Management)
+integration is enabled and configured in GitLab, issues (typically user stories) created in GitLab
+are synchronized as requirements in Squash TM and test progress is reported in GitLab issues.
+
+## Configure Squash TM
+
+1. Optional. Ask your system administrator to [configure a token in the properties file](https://tm-en.doc.squashtest.com/latest/redirect/gitlab-integration-token.html).
+1. Follow the [Squash TM documentation](https://tm-en.doc.squashtest.com/latest/redirect/gitlab-integration-configuration.html) to:
+ 1. Create a GitLab server.
+ 1. Enable the `Xsquash4GitLab` plugin
+ 1. Configure a synchronization.
+ 1. From the **Real-time synchronization** panel, copy the following fields to use later in GitLab:
+
+ - **Webhook URL**.
+ - **Secret token** if your Squash TM system administrator configured one at step 1.
+
+## Configure GitLab
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > Integrations**.
+1. Select **Squash TM**.
+1. Ensure that the **Active** toggle is enabled.
+1. In the **Trigger** section, indicate which type of issue is concerned by the real-time synchronization.
+1. Complete the fields:
+
+ - Enter the **Squash TM webhook URL**,
+ - Enter the **secret token** if your Squash TM system administrator configured it earlier.
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 9e19b3a69ff..f760b1b730a 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -304,7 +304,6 @@ For a web developer writing a webpage for your company's website:
- [GitLab keyboard shortcuts](../../shortcuts.md)
- [Comments and threads](../../discussions/index.md)
- [Suggest code changes](reviews/suggestions.md)
-- [Commits](commits.md)
- [CI/CD pipelines](../../../ci/index.md)
- [Push options](../push_options.md) for merge requests
diff --git a/doc/user/project/merge_requests/methods/index.md b/doc/user/project/merge_requests/methods/index.md
index 1f7e15ee982..02bd4ed0502 100644
--- a/doc/user/project/merge_requests/methods/index.md
+++ b/doc/user/project/merge_requests/methods/index.md
@@ -228,5 +228,4 @@ workflow that requires frequent rebases.
## Related topics
-- [Commits history](../commits.md)
- [Squash and merge](../squash_and_merge.md)
diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb
index 60cb61ef02f..c85871d4b8c 100644
--- a/lib/api/helpers/integrations_helpers.rb
+++ b/lib/api/helpers/integrations_helpers.rb
@@ -917,6 +917,20 @@ module API
type: String,
desc: 'The product ID of ZenTao project'
}
+ ],
+ 'squash-tm' => [
+ {
+ required: true,
+ name: :url,
+ type: String,
+ desc: 'The Squash TM webhook URL'
+ },
+ {
+ required: false,
+ name: :token,
+ type: String,
+ desc: 'The secret token'
+ }
]
}
end
@@ -955,6 +969,7 @@ module API
::Integrations::Redmine,
::Integrations::Slack,
::Integrations::SlackSlashCommands,
+ ::Integrations::SquashTm,
::Integrations::Teamcity,
::Integrations::Youtrack,
::Integrations::Zentao
diff --git a/lib/gitlab/auth/otp/duo_auth.rb b/lib/gitlab/auth/otp/duo_auth.rb
new file mode 100644
index 00000000000..eeae04bc08b
--- /dev/null
+++ b/lib/gitlab/auth/otp/duo_auth.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Auth
+ module Otp
+ module DuoAuth
+ def duo_auth_enabled?(_user)
+ ::Gitlab.config.duo_auth.enabled
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/otp/strategies/duo_auth/manual_otp.rb b/lib/gitlab/auth/otp/strategies/duo_auth/manual_otp.rb
new file mode 100644
index 00000000000..57bc88de175
--- /dev/null
+++ b/lib/gitlab/auth/otp/strategies/duo_auth/manual_otp.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Auth
+ module Otp
+ module Strategies
+ module DuoAuth
+ class ManualOtp < Base
+ include Gitlab::Utils::StrongMemoize
+
+ def validate(otp_code)
+ params = { username: user.username, factor: "passcode", passcode: otp_code.to_i }
+ response = duo_client.request('POST', "/auth/v2/auth", params)
+ approve_or_deny(parse_response(response))
+ rescue StandardError => e
+ Gitlab::AppLogger.error(e)
+ error(e.message)
+ end
+
+ private
+
+ def duo_client
+ DuoApi.new(::Gitlab.config.duo_auth.integration_key,
+ ::Gitlab.config.duo_auth.secret_key,
+ ::Gitlab.config.duo_auth.hostname)
+ end
+ strong_memoize_attr :duo_client
+
+ def parse_response(response)
+ Gitlab::Json.parse(response.body)
+ end
+
+ def approve_or_deny(parsed_response)
+ result_key = parsed_response.dig('response', 'result')
+ if result_key.to_s == "allow"
+ success
+ else
+ error(message: parsed_response.dig('response', 'status_msg').to_s)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7617e278c6e..9169cdb4f7c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -41552,6 +41552,18 @@ msgstr ""
msgid "Squash commits when merge request is accepted."
msgstr ""
+msgid "SquashTmIntegration|Secret token (optional)"
+msgstr ""
+
+msgid "SquashTmIntegration|Squash TM webhook URL"
+msgstr ""
+
+msgid "SquashTmIntegration|Update Squash TM requirements when GitLab issues are modified."
+msgstr ""
+
+msgid "SquashTmIntegration|Update Squash TM requirements when GitLab issues are modified. %{docs_link}"
+msgstr ""
+
msgid "Stack trace"
msgstr ""
diff --git a/package.json b/package.json
index 5f2c81e591d..e633367acab 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.2.0",
"@gitlab/svgs": "3.24.0",
- "@gitlab/ui": "56.4.1",
+ "@gitlab/ui": "57.0.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230223005157",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
diff --git a/spec/factories/bulk_import/batch_trackers.rb b/spec/factories/bulk_import/batch_trackers.rb
new file mode 100644
index 00000000000..427eefc5f3e
--- /dev/null
+++ b/spec/factories/bulk_import/batch_trackers.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :bulk_import_batch_tracker, class: 'BulkImports::BatchTracker' do
+ association :tracker, factory: :bulk_import_tracker
+
+ status { 0 }
+ fetched_objects_count { 1000 }
+ imported_objects_count { 1000 }
+
+ sequence(:batch_number) { |n| n }
+
+ trait :created do
+ status { 0 }
+ end
+
+ trait :started do
+ status { 1 }
+ end
+
+ trait :finished do
+ status { 2 }
+ end
+
+ trait :timeout do
+ status { 3 }
+ end
+
+ trait :failed do
+ status { -1 }
+ end
+
+ trait :skipped do
+ status { -2 }
+ end
+ end
+end
diff --git a/spec/factories/bulk_import/export_batches.rb b/spec/factories/bulk_import/export_batches.rb
new file mode 100644
index 00000000000..4339b02d27e
--- /dev/null
+++ b/spec/factories/bulk_import/export_batches.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :bulk_import_export_batch, class: 'BulkImports::ExportBatch' do
+ association :export, factory: :bulk_import_export
+
+ upload { association(:bulk_import_export_upload) }
+
+ status { 0 }
+
+ trait :started do
+ status { 0 }
+ end
+
+ trait :finished do
+ status { 1 }
+ end
+
+ trait :failed do
+ status { -1 }
+ end
+ end
+end
diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb
index 5a666e57ad1..caeac6e3b92 100644
--- a/spec/factories/integrations.rb
+++ b/spec/factories/integrations.rb
@@ -274,6 +274,15 @@ FactoryBot.define do
service_account_key { File.read('spec/fixtures/service_account.json') }
end
+ factory :squash_tm_integration, class: 'Integrations::SquashTm' do
+ project
+ active { true }
+ type { 'Integrations::SquashTm' }
+
+ url { 'https://url-to-squash.com' }
+ token { 'squash_tm_token' }
+ end
+
# this is for testing storing values inside properties, which is deprecated and will be removed in
# https://gitlab.com/gitlab-org/gitlab/issues/29404
trait :without_properties_callback do
diff --git a/spec/features/admin/admin_sees_background_migrations_spec.rb b/spec/features/admin/admin_sees_background_migrations_spec.rb
index cad1bf74d2e..77266e65e4c 100644
--- a/spec/features/admin/admin_sees_background_migrations_spec.rb
+++ b/spec/features/admin/admin_sees_background_migrations_spec.rb
@@ -191,7 +191,7 @@ RSpec.describe "Admin > Admin sees background migrations", feature_category: :da
visit admin_background_migrations_path
within '#content-body' do
- expect(page).to have_link('Learn more', href: help_page_path('user/admin_area/monitoring/background_migrations'))
+ expect(page).to have_link('Learn more', href: help_page_path('update/background_migrations'))
end
end
diff --git a/spec/frontend/__helpers__/experimentation_helper.js b/spec/frontend/__helpers__/experimentation_helper.js
index d5044be88d7..7e8dd463d28 100644
--- a/spec/frontend/__helpers__/experimentation_helper.js
+++ b/spec/frontend/__helpers__/experimentation_helper.js
@@ -2,16 +2,9 @@ import { merge } from 'lodash';
// This helper is for specs that use `gitlab/experimentation` module
export function withGonExperiment(experimentKey, value = true) {
- let origGon;
-
beforeEach(() => {
- origGon = window.gon;
window.gon = merge({}, window.gon || {}, { experiments: { [experimentKey]: value } });
});
-
- afterEach(() => {
- window.gon = origGon;
- });
}
// The following helper is for specs that use `gitlab-experiment` utilities,
diff --git a/spec/frontend/add_context_commits_modal/store/actions_spec.js b/spec/frontend/add_context_commits_modal/store/actions_spec.js
index 27c8d760a96..3863eee3795 100644
--- a/spec/frontend/add_context_commits_modal/store/actions_spec.js
+++ b/spec/frontend/add_context_commits_modal/store/actions_spec.js
@@ -31,10 +31,10 @@ describe('AddContextCommitsModalStoreActions', () => {
short_id: 'abcdef',
committed_date: '2020-06-12',
};
- gon.api_version = 'v4';
let mock;
beforeEach(() => {
+ gon.api_version = 'v4';
mock = new MockAdapter(axios);
});
diff --git a/spec/frontend/api/alert_management_alerts_api_spec.js b/spec/frontend/api/alert_management_alerts_api_spec.js
index 507f659a170..86052a05b76 100644
--- a/spec/frontend/api/alert_management_alerts_api_spec.js
+++ b/spec/frontend/api/alert_management_alerts_api_spec.js
@@ -9,7 +9,6 @@ import {
describe('~/api/alert_management_alerts_api.js', () => {
let mock;
- let originalGon;
const projectId = 1;
const alertIid = 2;
@@ -19,13 +18,11 @@ describe('~/api/alert_management_alerts_api.js', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = { api_version: 'v4' };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('fetchAlertMetricImages', () => {
diff --git a/spec/frontend/api/groups_api_spec.js b/spec/frontend/api/groups_api_spec.js
index 0315db02cf2..642edb33624 100644
--- a/spec/frontend/api/groups_api_spec.js
+++ b/spec/frontend/api/groups_api_spec.js
@@ -10,23 +10,18 @@ const mockUrlRoot = '/gitlab';
const mockGroupId = '99';
describe('GroupsApi', () => {
- let originalGon;
let mock;
- const dummyGon = {
- api_version: mockApiVersion,
- relative_url_root: mockUrlRoot,
- };
-
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: mockApiVersion,
+ relative_url_root: mockUrlRoot,
+ };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('updateGroup', () => {
diff --git a/spec/frontend/api/packages_api_spec.js b/spec/frontend/api/packages_api_spec.js
index 5f517bcf358..37c4b926ec2 100644
--- a/spec/frontend/api/packages_api_spec.js
+++ b/spec/frontend/api/packages_api_spec.js
@@ -6,22 +6,18 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('Api', () => {
const dummyApiVersion = 'v3000';
const dummyUrlRoot = '/gitlab';
- const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
- };
- let originalGon;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('packages', () => {
diff --git a/spec/frontend/api/projects_api_spec.js b/spec/frontend/api/projects_api_spec.js
index 2d4ed39dad0..2de56fae0c2 100644
--- a/spec/frontend/api/projects_api_spec.js
+++ b/spec/frontend/api/projects_api_spec.js
@@ -7,7 +7,6 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('~/api/projects_api.js', () => {
let mock;
- let originalGon;
const projectId = 1;
const setfullPathProjectSearch = (value) => {
@@ -17,13 +16,11 @@ describe('~/api/projects_api.js', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = { api_version: 'v7', features: { fullPathProjectSearch: true } };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('getProjects', () => {
diff --git a/spec/frontend/api/tags_api_spec.js b/spec/frontend/api/tags_api_spec.js
index af3533f52b7..0a1177d4f60 100644
--- a/spec/frontend/api/tags_api_spec.js
+++ b/spec/frontend/api/tags_api_spec.js
@@ -5,20 +5,17 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('~/api/tags_api.js', () => {
let mock;
- let originalGon;
const projectId = 1;
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = { api_version: 'v7' };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('getTag', () => {
diff --git a/spec/frontend/api/user_api_spec.js b/spec/frontend/api/user_api_spec.js
index 4d0252aad23..6636d77a09b 100644
--- a/spec/frontend/api/user_api_spec.js
+++ b/spec/frontend/api/user_api_spec.js
@@ -12,19 +12,16 @@ import { timeRanges } from '~/vue_shared/constants';
describe('~/api/user_api', () => {
let axiosMock;
- let originalGon;
beforeEach(() => {
axiosMock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = { api_version: 'v4' };
});
afterEach(() => {
axiosMock.restore();
axiosMock.resetHistory();
- window.gon = originalGon;
});
describe('followUser', () => {
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 27385d00717..4ef37311e51 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -13,22 +13,19 @@ import {
describe('Api', () => {
const dummyApiVersion = 'v3000';
const dummyUrlRoot = '/gitlab';
- const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
- };
- let originalGon;
+
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('buildUrl', () => {
@@ -1421,7 +1418,7 @@ describe('Api', () => {
describe('when service data increment counter is called with feature flag disabled', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: false };
+ gon.features = { usageDataApi: false };
});
it('returns null', () => {
@@ -1435,7 +1432,7 @@ describe('Api', () => {
describe('when service data increment counter is called', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: true };
+ gon.features = { usageDataApi: true };
});
it('resolves the Promise', () => {
@@ -1466,7 +1463,7 @@ describe('Api', () => {
describe('when service data increment unique users is called with feature flag disabled', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: false };
+ gon.features = { usageDataApi: false };
});
it('returns null and does not call the endpoint', () => {
@@ -1481,7 +1478,7 @@ describe('Api', () => {
describe('when service data increment unique users is called', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: true };
+ gon.features = { usageDataApi: true };
});
it('resolves the Promise', () => {
@@ -1498,7 +1495,7 @@ describe('Api', () => {
describe('when user is not set and feature flag enabled', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: true };
+ gon.features = { usageDataApi: true };
});
it('returns null and does not call the endpoint', () => {
diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index 1a54b9909ba..35a1603d375 100644
--- a/spec/frontend/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -6,10 +6,8 @@ import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_fra
import loadAwardsHandler from '~/awards_handler';
window.gl = window.gl || {};
-window.gon = window.gon || {};
let awardsHandler = null;
-const urlRoot = gon.relative_url_root;
describe('AwardsHandler', () => {
useFakeRequestAnimationFrame();
@@ -95,9 +93,6 @@ describe('AwardsHandler', () => {
});
afterEach(() => {
- // restore original url root value
- gon.relative_url_root = urlRoot;
-
clearEmojiMock();
// Undo what we did to the shared <body>
diff --git a/spec/frontend/behaviors/markdown/highlight_current_user_spec.js b/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
index 38d19ac3808..ad70efdf7c3 100644
--- a/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
+++ b/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
@@ -22,14 +22,9 @@ describe('highlightCurrentUser', () => {
describe('without current user', () => {
beforeEach(() => {
- window.gon = window.gon || {};
window.gon.current_user_id = null;
});
- afterEach(() => {
- delete window.gon.current_user_id;
- });
-
it('does not highlight the user', () => {
const initialHtml = rootElement.outerHTML;
@@ -41,14 +36,9 @@ describe('highlightCurrentUser', () => {
describe('with current user', () => {
beforeEach(() => {
- window.gon = window.gon || {};
window.gon.current_user_id = 2;
});
- afterEach(() => {
- delete window.gon.current_user_id;
- });
-
it('highlights current user', () => {
highlightCurrentUser(elements);
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index 9e544bade5b..a612e863d46 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -312,10 +312,6 @@ describe('Board card component', () => {
});
});
- afterEach(() => {
- global.gon.default_avatar_url = null;
- });
-
it('displays defaults avatar if users avatar is null', () => {
expect(wrapper.find('.board-card-assignee img').exists()).toBe(true);
expect(wrapper.find('.board-card-assignee img').attributes('src')).toBe(
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index db9d2f73686..33351bf8efd 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -26,7 +26,6 @@ const actions = {
describe('BoardContent', () => {
let wrapper;
let fakeApollo;
- window.gon = {};
const defaultState = {
isShowingEpicsSwimlanes: false,
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index c86a256bd96..944a7493504 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -31,10 +31,6 @@ describe('Boards - Getters', () => {
});
describe('isSwimlanesOn', () => {
- afterEach(() => {
- window.gon = { features: {} };
- });
-
it('returns false', () => {
expect(getters.isSwimlanesOn()).toBe(false);
});
@@ -171,10 +167,6 @@ describe('Boards - Getters', () => {
});
describe('isEpicBoard', () => {
- afterEach(() => {
- window.gon = { features: {} };
- });
-
it('returns false', () => {
expect(getters.isEpicBoard()).toBe(false);
});
diff --git a/spec/frontend/ci_secure_files/components/secure_files_list_spec.js b/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
index 637f4989f84..17b5fdc4dde 100644
--- a/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
+++ b/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
@@ -15,11 +15,6 @@ const dummyApiVersion = 'v3000';
const dummyProjectId = 1;
const fileSizeLimit = 5;
const dummyUrlRoot = '/gitlab';
-const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
-};
-let originalGon;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${dummyProjectId}/secure_files`;
describe('SecureFilesList', () => {
@@ -28,15 +23,16 @@ describe('SecureFilesList', () => {
let trackingSpy;
beforeEach(() => {
- originalGon = window.gon;
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
});
afterEach(() => {
mock.restore();
unmockTracking();
- window.gon = originalGon;
});
const createWrapper = (admin = true, props = {}) => {
diff --git a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
index 5483112f270..56bf0fa60a7 100644
--- a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
@@ -27,7 +27,6 @@ const defaultMockDiscussion = {
const DEFAULT_TODO_COUNT = 2;
describe('Design discussions component', () => {
- const originalGon = window.gon;
let wrapper;
const findDesignNotes = () => wrapper.findAllComponents(DesignNote);
@@ -97,7 +96,6 @@ describe('Design discussions component', () => {
});
afterEach(() => {
- window.gon = originalGon;
confirmAction.mockReset();
});
diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
index da682d3562d..db1cfb4f504 100644
--- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
@@ -22,7 +22,6 @@ jest.mock('~/autosave');
describe('Design reply form component', () => {
let wrapper;
- let originalGon;
const findTextarea = () => wrapper.find('textarea');
const findSubmitButton = () => wrapper.findComponent({ ref: 'submitButton' });
@@ -82,12 +81,10 @@ describe('Design reply form component', () => {
}
beforeEach(() => {
- originalGon = window.gon;
window.gon.current_user_id = 1;
});
afterEach(() => {
- window.gon = originalGon;
confirmAction.mockReset();
});
diff --git a/spec/frontend/design_management/components/design_presentation_spec.js b/spec/frontend/design_management/components/design_presentation_spec.js
index 2f67d9e24d2..fdcea6d88c0 100644
--- a/spec/frontend/design_management/components/design_presentation_spec.js
+++ b/spec/frontend/design_management/components/design_presentation_spec.js
@@ -15,7 +15,6 @@ const mockOverlayData = {
};
describe('Design management design presentation component', () => {
- const originalGon = window.gon;
let wrapper;
function createComponent(
@@ -114,10 +113,6 @@ describe('Design management design presentation component', () => {
window.gon = { current_user_id: 1 };
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('renders image and overlay when image provided', async () => {
createComponent(
{
diff --git a/spec/frontend/design_management/components/design_sidebar_spec.js b/spec/frontend/design_management/components/design_sidebar_spec.js
index 1416801f1a7..90424175417 100644
--- a/spec/frontend/design_management/components/design_sidebar_spec.js
+++ b/spec/frontend/design_management/components/design_sidebar_spec.js
@@ -29,7 +29,6 @@ const $route = {
const mutate = jest.fn().mockResolvedValue();
describe('Design management design sidebar component', () => {
- const originalGon = window.gon;
let wrapper;
const findDiscussions = () => wrapper.findAllComponents(DesignDiscussion);
@@ -67,10 +66,6 @@ describe('Design management design sidebar component', () => {
window.gon = { current_user_id: 1 };
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('renders participants', () => {
createComponent();
diff --git a/spec/frontend/design_management/router_spec.js b/spec/frontend/design_management/router_spec.js
index b9edde559c8..3503725f741 100644
--- a/spec/frontend/design_management/router_spec.js
+++ b/spec/frontend/design_management/router_spec.js
@@ -11,8 +11,6 @@ import '~/commons/bootstrap';
function factory(routeArg) {
Vue.use(VueRouter);
- window.gon = { sprite_icons: '' };
-
const router = createRouter('/');
if (routeArg !== undefined) {
router.push(routeArg);
@@ -36,10 +34,6 @@ function factory(routeArg) {
}
describe('Design management router', () => {
- afterEach(() => {
- window.location.hash = '';
- });
-
describe.each([['/'], [{ name: DESIGNS_ROUTE_NAME }]])('root route', (routeArg) => {
it('pushes home component', () => {
const wrapper = factory(routeArg);
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index 318427de1ae..93698396450 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -220,21 +220,10 @@ describe('DiffFile', () => {
describe('computed', () => {
describe('showLocalFileReviews', () => {
- let gon;
-
function setLoggedIn(bool) {
window.gon.current_user_id = bool;
}
- beforeAll(() => {
- gon = window.gon;
- window.gon = {};
- });
-
- afterEach(() => {
- window.gon = gon;
- });
-
it.each`
loggedIn | bool
${true} | ${true}
diff --git a/spec/frontend/diffs/components/diff_row_spec.js b/spec/frontend/diffs/components/diff_row_spec.js
index f01c86f781e..356c7ef925a 100644
--- a/spec/frontend/diffs/components/diff_row_spec.js
+++ b/spec/frontend/diffs/components/diff_row_spec.js
@@ -89,7 +89,6 @@ describe('DiffRow', () => {
};
afterEach(() => {
- window.gon = {};
showCommentForm.mockReset();
enterdragging.mockReset();
stopdragging.mockReset();
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index f5036226b1e..b00076504e3 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -1015,20 +1015,14 @@ describe('DiffsStoreActions', () => {
describe('setShowWhitespace', () => {
const endpointUpdateUser = 'user/prefs';
let putSpy;
- let gon;
beforeEach(() => {
putSpy = jest.spyOn(axios, 'put');
- gon = window.gon;
mock.onPut(endpointUpdateUser).reply(HTTP_STATUS_OK, {});
jest.spyOn(eventHub, '$emit').mockImplementation();
});
- afterEach(() => {
- window.gon = gon;
- });
-
it('commits SET_SHOW_WHITESPACE', () => {
return testAction(
diffActions.setShowWhitespace,
diff --git a/spec/frontend/environments/stop_stale_environments_modal_spec.js b/spec/frontend/environments/stop_stale_environments_modal_spec.js
index eddab696439..ddf6670db12 100644
--- a/spec/frontend/environments/stop_stale_environments_modal_spec.js
+++ b/spec/frontend/environments/stop_stale_environments_modal_spec.js
@@ -18,7 +18,6 @@ describe('~/environments/components/stop_stale_environments_modal.vue', () => {
let wrapper;
let mock;
let before;
- let originalGon;
const createWrapper = (opts = {}) =>
shallowMount(StopStaleEnvironmentsModal, {
@@ -28,8 +27,7 @@ describe('~/environments/components/stop_stale_environments_modal.vue', () => {
});
beforeEach(() => {
- originalGon = window.gon;
- window.gon = { api_version: 'v4' };
+ window.gon.api_version = 'v4';
mock = new MockAdapter(axios);
jest.spyOn(axios, 'post');
@@ -40,7 +38,6 @@ describe('~/environments/components/stop_stale_environments_modal.vue', () => {
afterEach(() => {
mock.restore();
jest.resetAllMocks();
- window.gon = originalGon;
});
it('sets the correct min and max dates', async () => {
diff --git a/spec/frontend/experimentation/components/gitlab_experiment_spec.js b/spec/frontend/experimentation/components/gitlab_experiment_spec.js
index 54289acc2dc..73db4b9503c 100644
--- a/spec/frontend/experimentation/components/gitlab_experiment_spec.js
+++ b/spec/frontend/experimentation/components/gitlab_experiment_spec.js
@@ -9,7 +9,6 @@ const defaultSlots = {
};
describe('ExperimentComponent', () => {
- const oldGon = window.gon;
let wrapper;
const createComponent = (propsData = defaultProps, slots = defaultSlots) => {
@@ -20,10 +19,6 @@ describe('ExperimentComponent', () => {
window.gon = { experiment: { experiment_name: { variant: expectedVariant } } };
};
- afterEach(() => {
- window.gon = oldGon;
- });
-
describe('when variant and experiment is set', () => {
it('renders control when it is the active variant', () => {
mockVariant('control');
diff --git a/spec/frontend/experimentation/utils_spec.js b/spec/frontend/experimentation/utils_spec.js
index 0d663fd055e..6d9c9dfe65a 100644
--- a/spec/frontend/experimentation/utils_spec.js
+++ b/spec/frontend/experimentation/utils_spec.js
@@ -10,18 +10,15 @@ describe('experiment Utilities', () => {
const ABC_KEY = 'abc';
const DEF_KEY = 'def';
- let origGon;
let origGl;
beforeEach(() => {
- origGon = window.gon;
origGl = window.gl;
window.gon.experiment = {};
window.gl.experiments = {};
});
afterEach(() => {
- window.gon = origGon;
window.gl = origGl;
});
diff --git a/spec/frontend/filtered_search/dropdown_user_spec.js b/spec/frontend/filtered_search/dropdown_user_spec.js
index 26f12673f68..02ef813883f 100644
--- a/spec/frontend/filtered_search/dropdown_user_spec.js
+++ b/spec/frontend/filtered_search/dropdown_user_spec.js
@@ -68,10 +68,6 @@ describe('Dropdown User', () => {
'/gitlab_directory/-/autocomplete/users.json',
);
});
-
- afterEach(() => {
- window.gon = {};
- });
});
describe('hideCurrentUser', () => {
diff --git a/spec/frontend/helpers/startup_css_helper_spec.js b/spec/frontend/helpers/startup_css_helper_spec.js
index 05161437c22..28c742386cc 100644
--- a/spec/frontend/helpers/startup_css_helper_spec.js
+++ b/spec/frontend/helpers/startup_css_helper_spec.js
@@ -21,17 +21,10 @@ describe('waitForCSSLoaded', () => {
});
describe('when gon features is not provided', () => {
- let originalGon;
-
beforeEach(() => {
- originalGon = window.gon;
window.gon = null;
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('should invoke the action right away', async () => {
const events = waitForCSSLoaded(mockedCallback);
await events;
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 623dee387e5..cd099e60070 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -252,12 +252,10 @@ describe('IDE services', () => {
describe('pingUsage', () => {
let mock;
- let relativeUrlRoot;
const TEST_RELATIVE_URL_ROOT = 'blah-blah';
beforeEach(() => {
jest.spyOn(axios, 'post');
- relativeUrlRoot = gon.relative_url_root;
gon.relative_url_root = TEST_RELATIVE_URL_ROOT;
mock = new MockAdapter(axios);
@@ -265,7 +263,6 @@ describe('IDE services', () => {
afterEach(() => {
mock.restore();
- gon.relative_url_root = relativeUrlRoot;
});
it('posts to usage endpoint', () => {
diff --git a/spec/frontend/ide/services/terminals_spec.js b/spec/frontend/ide/services/terminals_spec.js
index 5f752197e13..5b6b60a250c 100644
--- a/spec/frontend/ide/services/terminals_spec.js
+++ b/spec/frontend/ide/services/terminals_spec.js
@@ -9,7 +9,6 @@ const TEST_BRANCH = 'ref';
describe('~/ide/services/terminals', () => {
let axiosSpy;
let mock;
- const prevRelativeUrlRoot = gon.relative_url_root;
beforeEach(() => {
axiosSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, {}]);
@@ -19,7 +18,6 @@ describe('~/ide/services/terminals', () => {
});
afterEach(() => {
- gon.relative_url_root = prevRelativeUrlRoot;
mock.restore();
});
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index 90ca8526698..7f4e1cf761d 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -16,7 +16,6 @@ const RELATIVE_URL_ROOT = '/gitlab';
describe('IDE store file actions', () => {
let mock;
- let originalGon;
let store;
let router;
@@ -24,9 +23,7 @@ describe('IDE store file actions', () => {
stubPerformanceWebAPI();
mock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = {
- ...window.gon,
relative_url_root: RELATIVE_URL_ROOT,
};
@@ -44,7 +41,6 @@ describe('IDE store file actions', () => {
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('closeFile', () => {
diff --git a/spec/frontend/ide/stores/extend_spec.js b/spec/frontend/ide/stores/extend_spec.js
index ffb00f9ef5b..88909999c82 100644
--- a/spec/frontend/ide/stores/extend_spec.js
+++ b/spec/frontend/ide/stores/extend_spec.js
@@ -6,12 +6,10 @@ jest.mock('~/ide/stores/plugins/terminal', () => jest.fn());
jest.mock('~/ide/stores/plugins/terminal_sync', () => jest.fn());
describe('ide/stores/extend', () => {
- let prevGon;
let store;
let el;
beforeEach(() => {
- prevGon = global.gon;
store = {};
el = {};
@@ -23,13 +21,12 @@ describe('ide/stores/extend', () => {
});
afterEach(() => {
- global.gon = prevGon;
terminalPlugin.mockClear();
terminalSyncPlugin.mockClear();
});
const withGonFeatures = (features) => {
- global.gon = { ...global.gon, features };
+ global.gon.features = features;
};
describe('terminalPlugin', () => {
diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js
index d4166a3bd6d..0fe6a16c676 100644
--- a/spec/frontend/ide/stores/getters_spec.js
+++ b/spec/frontend/ide/stores/getters_spec.js
@@ -24,11 +24,8 @@ const TEST_FORK_PATH = '/test/fork/path';
describe('IDE store getters', () => {
let localState;
let localStore;
- let origGon;
beforeEach(() => {
- origGon = window.gon;
-
// Feature flag is defaulted to on in prod
window.gon = { features: { rejectUnsignedCommitsByGitlab: true } };
@@ -36,10 +33,6 @@ describe('IDE store getters', () => {
localState = localStore.state;
});
- afterEach(() => {
- window.gon = origGon;
- });
-
describe('activeFile', () => {
it('returns the current active file', () => {
localState.openFiles.push(file());
diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js
index 685935954bd..872aa9b6e6b 100644
--- a/spec/frontend/ide/stores/modules/commit/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js
@@ -57,7 +57,6 @@ describe('IDE commit module actions', () => {
afterEach(() => {
unmockTracking();
- delete gon.api_version;
mock.restore();
});
@@ -85,19 +84,12 @@ describe('IDE commit module actions', () => {
});
describe('updateBranchName', () => {
- let originalGon;
-
beforeEach(() => {
- originalGon = window.gon;
- window.gon = { current_username: 'johndoe' };
+ window.gon.current_username = 'johndoe';
store.state.currentBranchId = 'main';
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('updates store with new branch name', async () => {
await store.dispatch('commit/updateBranchName', 'branch-name');
diff --git a/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js b/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js
index 72fcab63ba7..f8e47bc0a4b 100644
--- a/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js
+++ b/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js
@@ -1,9 +1,9 @@
-import { GlFormGroup } from '@gitlab/ui';
+import { GlButton, GlFormGroup, GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import AddIssuableForm from '~/related_issues/components/add_issuable_form.vue';
import IssueToken from '~/related_issues/components/issue_token.vue';
+import RelatedIssuableInput from '~/related_issues/components/related_issuable_input.vue';
import { linkedIssueTypesMap, PathIdSeparator } from '~/related_issues/constants';
const issuable1 = {
@@ -26,71 +26,60 @@ const issuable2 = {
const pathIdSeparator = PathIdSeparator.Issue;
-const findFormInput = (wrapper) => wrapper.find('input').element;
-
-const findRadioInput = (inputs, value) =>
- inputs.filter((input) => input.element.value === value)[0];
-
-const findRadioInputs = (wrapper) => wrapper.findAll('[name="linked-issue-type-radio"]');
-
-const constructWrapper = (props) => {
- return shallowMount(AddIssuableForm, {
- propsData: {
- inputValue: '',
- pendingReferences: [],
- pathIdSeparator,
- ...props,
- },
- });
-};
-
describe('AddIssuableForm', () => {
let wrapper;
- afterEach(() => {
- // Jest doesn't blur an item even if it is destroyed,
- // so blur the input manually after each test
- const input = findFormInput(wrapper);
- if (input) input.blur();
+ const createComponent = (props = {}, mountFn = shallowMount) => {
+ wrapper = mountFn(AddIssuableForm, {
+ propsData: {
+ inputValue: '',
+ pendingReferences: [],
+ pathIdSeparator,
+ ...props,
+ },
+ stubs: {
+ RelatedIssuableInput,
+ },
+ });
+ };
- if (wrapper) {
- wrapper.destroy();
- wrapper = null;
- }
- });
+ const findAddIssuableForm = () => wrapper.find('form');
+ const findFormInput = () => wrapper.find('input').element;
+ const findRadioInput = (inputs, value) =>
+ inputs.filter((input) => input.element.value === value)[0];
+ const findAllIssueTokens = () => wrapper.findAllComponents(IssueToken);
+ const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
+ const findRadioInputs = () => wrapper.findAllComponents(GlFormRadio);
+
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findFormButtons = () => wrapper.findAllComponents(GlButton);
+ const findSubmitButton = () => findFormButtons().at(0);
+ const findRelatedIssuableInput = () => wrapper.findComponent(RelatedIssuableInput);
describe('with data', () => {
describe('without references', () => {
describe('without any input text', () => {
beforeEach(() => {
- wrapper = shallowMount(AddIssuableForm, {
- propsData: {
- inputValue: '',
- pendingReferences: [],
- pathIdSeparator,
- },
- });
+ createComponent();
});
it('should have disabled submit button', () => {
- expect(wrapper.vm.$refs.addButton.disabled).toBe(true);
- expect(wrapper.vm.$refs.loadingIcon).toBeUndefined();
+ expect(findSubmitButton().props('disabled')).toBe(true);
+ expect(findSubmitButton().props('loading')).toBe(false);
});
});
describe('with input text', () => {
beforeEach(() => {
- wrapper = shallowMount(AddIssuableForm, {
- propsData: {
- inputValue: 'foo',
- pendingReferences: [],
- pathIdSeparator,
- },
+ createComponent({
+ inputValue: 'foo',
+ pendingReferences: [],
+ pathIdSeparator,
});
});
it('should not have disabled submit button', () => {
- expect(wrapper.vm.$refs.addButton.disabled).toBe(false);
+ expect(findSubmitButton().props('disabled')).toBe(false);
});
});
});
@@ -99,59 +88,56 @@ describe('AddIssuableForm', () => {
const inputValue = 'foo #123';
beforeEach(() => {
- wrapper = mount(AddIssuableForm, {
- propsData: {
- inputValue,
- pendingReferences: [issuable1.reference, issuable2.reference],
- pathIdSeparator,
- },
+ createComponent({
+ inputValue,
+ pendingReferences: [issuable1.reference, issuable2.reference],
+ pathIdSeparator,
});
- });
+ }, mount);
it('should put input value in place', () => {
expect(findFormInput(wrapper).value).toBe(inputValue);
});
it('should render pending issuables items', () => {
- expect(wrapper.findAllComponents(IssueToken)).toHaveLength(2);
+ expect(findAllIssueTokens()).toHaveLength(2);
});
it('should not have disabled submit button', () => {
- expect(wrapper.vm.$refs.addButton.disabled).toBe(false);
+ expect(findSubmitButton().props('disabled')).toBe(false);
});
});
describe('when issuable type is "issue"', () => {
beforeEach(() => {
- wrapper = mount(AddIssuableForm, {
- propsData: {
+ createComponent(
+ {
inputValue: '',
issuableType: TYPE_ISSUE,
pathIdSeparator,
pendingReferences: [],
},
- });
+ mount,
+ );
});
it('does not show radio inputs', () => {
- expect(findRadioInputs(wrapper).length).toBe(0);
+ expect(findRadioInputs()).toHaveLength(0);
});
});
describe('when issuable type is "epic"', () => {
beforeEach(() => {
- wrapper = shallowMount(AddIssuableForm, {
- propsData: {
- inputValue: '',
- issuableType: TYPE_EPIC,
- pathIdSeparator,
- pendingReferences: [],
- },
+ createComponent({
+ inputValue: '',
+ issuableType: TYPE_EPIC,
+ pathIdSeparator,
+ pendingReferences: [],
});
});
it('does not show radio inputs', () => {
- expect(findRadioInputs(wrapper).length).toBe(0);
+ expect(findRadioInputs()).toHaveLength(0);
});
});
@@ -163,17 +149,15 @@ describe('AddIssuableForm', () => {
`(
'show header text as "$contextHeader" and footer text as "$contextFooter" issuableType is set to $issuableType',
({ issuableType, contextHeader, contextFooter }) => {
- wrapper = shallowMount(AddIssuableForm, {
- propsData: {
- issuableType,
- inputValue: '',
- showCategorizedIssues: true,
- pathIdSeparator,
- pendingReferences: [],
- },
+ createComponent({
+ issuableType,
+ inputValue: '',
+ showCategorizedIssues: true,
+ pathIdSeparator,
+ pendingReferences: [],
});
- expect(wrapper.findComponent(GlFormGroup).attributes('label')).toBe(contextHeader);
+ expect(findFormGroup().attributes('label')).toBe(contextHeader);
expect(wrapper.find('p.bold').text()).toContain(contextFooter);
},
);
@@ -181,26 +165,24 @@ describe('AddIssuableForm', () => {
describe('when it is a Linked Issues form', () => {
beforeEach(() => {
- wrapper = mount(AddIssuableForm, {
- propsData: {
- inputValue: '',
- showCategorizedIssues: true,
- issuableType: TYPE_ISSUE,
- pathIdSeparator,
- pendingReferences: [],
- },
+ createComponent({
+ inputValue: '',
+ showCategorizedIssues: true,
+ issuableType: TYPE_ISSUE,
+ pathIdSeparator,
+ pendingReferences: [],
});
});
it('shows radio inputs to allow categorisation of blocking issues', () => {
- expect(findRadioInputs(wrapper).length).toBeGreaterThan(0);
+ expect(findRadioGroup().props('options').length).toBeGreaterThan(0);
});
describe('form radio buttons', () => {
let radioInputs;
beforeEach(() => {
- radioInputs = findRadioInputs(wrapper);
+ radioInputs = findRadioInputs();
});
it('shows "relates to" option', () => {
@@ -216,58 +198,59 @@ describe('AddIssuableForm', () => {
});
it('shows 3 options in total', () => {
- expect(radioInputs.length).toBe(3);
+ expect(findRadioGroup().props('options')).toHaveLength(3);
});
});
describe('when the form is submitted', () => {
- it('emits an event with a "relates_to" link type when the "relates to" radio input selected', async () => {
- jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
-
- wrapper.vm.linkedIssueType = linkedIssueTypesMap.RELATES_TO;
- wrapper.vm.onFormSubmit();
-
- await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.RELATES_TO,
- });
+ it('emits an event with a "relates_to" link type when the "relates to" radio input selected', () => {
+ findAddIssuableForm().trigger('submit');
+
+ expect(wrapper.emitted('addIssuableFormSubmit')).toEqual([
+ [
+ {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.RELATES_TO,
+ },
+ ],
+ ]);
});
- it('emits an event with a "blocks" link type when the "blocks" radio input selected', async () => {
- jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
-
- wrapper.vm.linkedIssueType = linkedIssueTypesMap.BLOCKS;
- wrapper.vm.onFormSubmit();
-
- await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.BLOCKS,
- });
+ it('emits an event with a "blocks" link type when the "blocks" radio input selected', () => {
+ findRadioGroup().vm.$emit('input', linkedIssueTypesMap.BLOCKS);
+ findAddIssuableForm().trigger('submit');
+
+ expect(wrapper.emitted('addIssuableFormSubmit')).toEqual([
+ [
+ {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.BLOCKS,
+ },
+ ],
+ ]);
});
it('emits an event with a "is_blocked_by" link type when the "is blocked by" radio input selected', async () => {
- jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
-
- wrapper.vm.linkedIssueType = linkedIssueTypesMap.IS_BLOCKED_BY;
- wrapper.vm.onFormSubmit();
-
- await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.IS_BLOCKED_BY,
- });
+ findRadioGroup().vm.$emit('input', linkedIssueTypesMap.IS_BLOCKED_BY);
+ findAddIssuableForm().trigger('submit');
+
+ expect(wrapper.emitted('addIssuableFormSubmit')).toEqual([
+ [
+ {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.IS_BLOCKED_BY,
+ },
+ ],
+ ]);
});
- it('shows error message when error is present', async () => {
+ it('shows error message when error is present', () => {
const itemAddFailureMessage = 'Something went wrong while submitting.';
- wrapper.setProps({
+ createComponent({
hasError: true,
itemAddFailureMessage,
});
- await nextTick();
expect(wrapper.find('.gl-field-error').exists()).toBe(true);
expect(wrapper.find('.gl-field-error').text()).toContain(itemAddFailureMessage);
});
@@ -283,27 +266,31 @@ describe('AddIssuableForm', () => {
};
it('returns autocomplete object', () => {
- wrapper = constructWrapper({
+ createComponent({
autoCompleteSources,
});
- expect(wrapper.vm.transformedAutocompleteSources).toBe(autoCompleteSources);
+ expect(findRelatedIssuableInput().props('autoCompleteSources')).toEqual(
+ autoCompleteSources,
+ );
- wrapper = constructWrapper({
+ createComponent({
autoCompleteSources,
confidential: false,
});
- expect(wrapper.vm.transformedAutocompleteSources).toBe(autoCompleteSources);
+ expect(findRelatedIssuableInput().props('autoCompleteSources')).toEqual(
+ autoCompleteSources,
+ );
});
it('returns autocomplete sources with query `confidential_only`, when it is confidential', () => {
- wrapper = constructWrapper({
+ createComponent({
autoCompleteSources,
confidential: true,
});
- const actualSources = wrapper.vm.transformedAutocompleteSources;
+ const actualSources = findRelatedIssuableInput().props('autoCompleteSources');
expect(actualSources.epics).toContain('?confidential_only=true');
expect(actualSources.issues).toContain('?confidential_only=true');
diff --git a/spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js b/spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js
index 9df8cacf81c..ebf4771e97f 100644
--- a/spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js
+++ b/spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js
@@ -337,11 +337,9 @@ describe('IssuesDashboardApp component', () => {
username: 'root',
avatar_url: 'avatar/url',
};
- const originalGon = window.gon;
beforeEach(() => {
window.gon = {
- ...originalGon,
current_user_id: mockCurrentUser.id,
current_user_fullname: mockCurrentUser.name,
current_username: mockCurrentUser.username,
@@ -350,10 +348,6 @@ describe('IssuesDashboardApp component', () => {
mountComponent();
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('renders all tokens alphabetically', () => {
const preloadedUsers = [{ ...mockCurrentUser, id: mockCurrentUser.id }];
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index 5893e5dec2f..b28a08e2fce 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -564,11 +564,8 @@ describe('CE IssuesListApp component', () => {
});
describe('when all tokens are available', () => {
- const originalGon = window.gon;
-
beforeEach(() => {
window.gon = {
- ...originalGon,
current_user_id: mockCurrentUser.id,
current_user_fullname: mockCurrentUser.name,
current_username: mockCurrentUser.username,
@@ -584,10 +581,6 @@ describe('CE IssuesListApp component', () => {
});
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('renders all tokens alphabetically', () => {
const preloadedUsers = [
{ ...mockCurrentUser, id: convertToGraphQLId(TYPENAME_USER, mockCurrentUser.id) },
diff --git a/spec/frontend/issues/list/utils_spec.js b/spec/frontend/issues/list/utils_spec.js
index a281ed1c989..e4ecdc6c29e 100644
--- a/spec/frontend/issues/list/utils_spec.js
+++ b/spec/frontend/issues/list/utils_spec.js
@@ -10,7 +10,7 @@ import {
urlParams,
urlParamsWithSpecialValues,
} from 'jest/issues/list/mock_data';
-import { PAGE_SIZE, urlSortParams } from '~/issues/list/constants';
+import { urlSortParams } from '~/issues/list/constants';
import {
convertToApiParams,
convertToSearchQuery,
@@ -22,10 +22,11 @@ import {
isSortKey,
} from '~/issues/list/utils';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
+import { DEFAULT_PAGE_SIZE } from '~/vue_shared/issuable/list/constants';
describe('getInitialPageParams', () => {
it('returns page params with a default page size when no arguments are given', () => {
- expect(getInitialPageParams()).toEqual({ firstPageSize: PAGE_SIZE });
+ expect(getInitialPageParams()).toEqual({ firstPageSize: DEFAULT_PAGE_SIZE });
});
it('returns page params with the given page size', () => {
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index 9e71d7648cc..740b2f782e4 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -48,7 +48,6 @@ const workItemTypesQueryHandler = jest.fn().mockResolvedValue(projectWorkItemTyp
describe('Description component', () => {
let wrapper;
- let originalGon;
Vue.use(VueApollo);
@@ -87,7 +86,6 @@ describe('Description component', () => {
}
beforeEach(() => {
- originalGon = window.gon;
window.gon = { sprite_icons: mockSpriteIcons };
setWindowLocation(TEST_HOST);
@@ -103,8 +101,6 @@ describe('Description component', () => {
});
afterAll(() => {
- window.gon = originalGon;
-
$('.issuable-meta .flash-container').remove();
});
diff --git a/spec/frontend/jira_connect/subscriptions/api_spec.js b/spec/frontend/jira_connect/subscriptions/api_spec.js
index e2a14a9102f..5a28c6d9789 100644
--- a/spec/frontend/jira_connect/subscriptions/api_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/api_spec.js
@@ -17,7 +17,6 @@ jest.mock('~/jira_connect/subscriptions/utils', () => ({
describe('JiraConnect API', () => {
let axiosMock;
- let originalGon;
let response;
const mockAddPath = 'addPath';
@@ -29,13 +28,11 @@ describe('JiraConnect API', () => {
beforeEach(() => {
axiosMock = new MockAdapter(axiosInstance);
- originalGon = window.gon;
window.gon = { api_version: 'v4' };
});
afterEach(() => {
axiosMock.restore();
- window.gon = originalGon;
response = null;
});
diff --git a/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js b/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js
index 5ac7a7985a8..b8847f0fca3 100644
--- a/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js
+++ b/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js
@@ -6,13 +6,8 @@ import { isNavigatingAway } from '~/lib/utils/is_navigating_away';
jest.mock('~/lib/utils/is_navigating_away');
describe('getSuppressNetworkErrorsDuringNavigationLink', () => {
- const originalGon = window.gon;
let subscription;
- beforeEach(() => {
- window.gon = originalGon;
- });
-
afterEach(() => {
if (subscription) {
subscription.unsubscribe();
diff --git a/spec/frontend/lib/utils/axios_startup_calls_spec.js b/spec/frontend/lib/utils/axios_startup_calls_spec.js
index 4471b781446..3d063ff9b46 100644
--- a/spec/frontend/lib/utils/axios_startup_calls_spec.js
+++ b/spec/frontend/lib/utils/axios_startup_calls_spec.js
@@ -113,17 +113,10 @@ describe('setupAxiosStartupCalls', () => {
});
describe('startup call', () => {
- let oldGon;
-
beforeEach(() => {
- oldGon = window.gon;
window.gon = { gitlab_url: 'https://example.org/gitlab' };
});
- afterEach(() => {
- window.gon = oldGon;
- });
-
it('removes GitLab Base URL from startup call', async () => {
window.gl.startup_calls = {
'/startup': {
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 7b068f7d248..b4ec00ab766 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -534,18 +534,10 @@ describe('common_utils', () => {
});
describe('spriteIcon', () => {
- let beforeGon;
-
beforeEach(() => {
- window.gon = window.gon || {};
- beforeGon = { ...window.gon };
window.gon.sprite_icons = 'icons.svg';
});
- afterEach(() => {
- window.gon = beforeGon;
- });
-
it('should return the svg for a linked icon', () => {
expect(commonUtils.spriteIcon('test')).toEqual(
'<svg ><use xlink:href="icons.svg#test" /></svg>',
diff --git a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js
index 1ef7047d959..c13d55f978e 100644
--- a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js
@@ -3,16 +3,6 @@ import { s__ } from '~/locale';
import '~/commons/bootstrap';
describe('TimeAgo utils', () => {
- let oldGon;
-
- afterEach(() => {
- window.gon = oldGon;
- });
-
- beforeEach(() => {
- oldGon = window.gon;
- });
-
describe('getTimeago', () => {
describe('with User Setting timeDisplayRelative: true', () => {
beforeEach(() => {
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 6afdab455a6..72556e6bbe2 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -45,10 +45,6 @@ describe('URL utility', () => {
});
describe('webIDEUrl', () => {
- afterEach(() => {
- gon.relative_url_root = '';
- });
-
it('escapes special characters', () => {
expect(urlUtils.webIDEUrl('/gitlab-org/gitlab-#-foss/merge_requests/1')).toBe(
'/-/ide/project/gitlab-org/gitlab-%23-foss/merge_requests/1',
@@ -505,10 +501,6 @@ describe('URL utility', () => {
gon.gitlab_url = gitlabUrl;
});
- afterEach(() => {
- gon.gitlab_url = '';
- });
-
it.each`
url | urlType | external
${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false}
diff --git a/spec/frontend/members/components/table/role_dropdown_spec.js b/spec/frontend/members/components/table/role_dropdown_spec.js
index 60790640316..d6e63b1930f 100644
--- a/spec/frontend/members/components/table/role_dropdown_spec.js
+++ b/spec/frontend/members/components/table/role_dropdown_spec.js
@@ -70,17 +70,10 @@ describe('RoleDropdown', () => {
const findDropdownToggle = () => wrapper.find('button[aria-haspopup="menu"]');
const findDropdown = () => wrapper.findComponent(GlDropdown);
- let originalGon;
-
beforeEach(() => {
- originalGon = window.gon;
gon.features = { showOverageOnRolePromotion: true };
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
describe('when dropdown is open', () => {
beforeEach(() => {
guestOverageConfirmAction.mockReturnValue(true);
diff --git a/spec/frontend/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 6d434d7e654..76d1fa3a332 100644
--- a/spec/frontend/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -354,8 +354,6 @@ describe('MergeRequestTabs', () => {
testContext.class.expandSidebar.forEach((el) => {
expect(el.classList.contains('gl-display-none!')).toBe(hides);
});
-
- window.gon = {};
});
describe('when switching tabs', () => {
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index 30675365513..8097857f226 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -177,7 +177,6 @@ describe('Monitoring store actions', () => {
});
it('dispatches when feature metricsDashboardAnnotations is on', () => {
- const origGon = window.gon;
window.gon = { features: { metricsDashboardAnnotations: true } };
return testAction(
@@ -190,9 +189,7 @@ describe('Monitoring store actions', () => {
{ type: 'fetchDashboard' },
{ type: 'fetchAnnotations' },
],
- ).then(() => {
- window.gon = origGon;
- });
+ );
});
});
diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js
index e2d3ba6a0a0..ac0c037fe36 100644
--- a/spec/frontend/notes/components/noteable_discussion_spec.js
+++ b/spec/frontend/notes/components/noteable_discussion_spec.js
@@ -22,7 +22,6 @@ jest.mock('~/behaviors/markdown/render_gfm');
describe('noteable_discussion component', () => {
let store;
let wrapper;
- let originalGon;
beforeEach(() => {
window.mrTabs = {};
@@ -163,15 +162,6 @@ describe('noteable_discussion component', () => {
});
describe('signout widget', () => {
- beforeEach(() => {
- originalGon = { ...window.gon };
- window.gon = window.gon || {};
- });
-
- afterEach(() => {
- window.gon = originalGon;
- });
-
describe('user is logged in', () => {
beforeEach(() => {
window.gon.current_user_id = userDataMock.id;
diff --git a/spec/frontend/notes/deprecated_notes_spec.js b/spec/frontend/notes/deprecated_notes_spec.js
index 6d3bc19bd45..40f10ca901b 100644
--- a/spec/frontend/notes/deprecated_notes_spec.js
+++ b/spec/frontend/notes/deprecated_notes_spec.js
@@ -23,7 +23,6 @@ const fixture = 'snippets/show.html';
let mockAxios;
window.project_uploads_path = `${TEST_HOST}/uploads`;
-window.gon = window.gon || {};
window.gl = window.gl || {};
gl.utils = gl.utils || {};
gl.utils.disableButtonIfEmptyField = () => {};
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 9ac67445837..0d3ebea7af2 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -1453,10 +1453,6 @@ describe('Actions Notes Store', () => {
describe('fetchDiscussions', () => {
const discussion = { notes: [] };
- afterEach(() => {
- window.gon = {};
- });
-
it('updates the discussions and dispatches `updateResolvableDiscussionsCounts`', () => {
axiosMock.onAny().reply(HTTP_STATUS_OK, { discussion });
return testAction(
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
index 1060462327d..c2ae34ce697 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
@@ -31,11 +31,6 @@ import { proxyDetailsQuery, proxyData, pagination, proxyManifests } from './mock
const dummyApiVersion = 'v3000';
const dummyGrouptId = 1;
const dummyUrlRoot = '/gitlab';
-const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
-};
-let originalGon;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${dummyGrouptId}/dependency_proxy/cache`;
Vue.use(VueApollo);
@@ -89,15 +84,16 @@ describe('DependencyProxyApp', () => {
beforeEach(() => {
resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
- originalGon = window.gon;
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
mock = new MockAdapter(axios);
mock.onDelete(expectedUrl).reply(HTTP_STATUS_ACCEPTED, {});
});
afterEach(() => {
- window.gon = originalGon;
mock.restore();
});
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index f19952f58ab..a588251c4bd 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -481,11 +481,6 @@ describe('Blob content viewer component', () => {
repository: { empty },
} = projectMock;
- afterEach(() => {
- delete gon.current_user_id;
- delete gon.current_username;
- });
-
it('renders component', async () => {
window.gon.current_user_id = 1;
window.gon.current_username = 'root';
diff --git a/spec/frontend/search_autocomplete_spec.js b/spec/frontend/search_autocomplete_spec.js
index a3098fb81ea..dfb31eeda78 100644
--- a/spec/frontend/search_autocomplete_spec.js
+++ b/spec/frontend/search_autocomplete_spec.js
@@ -119,7 +119,6 @@ describe('Search autocomplete dropdown', () => {
afterEach(() => {
// Undo what we did to the shared <body>
removeBodyAttributes();
- window.gon = {};
resetHTMLFixture();
});
diff --git a/spec/frontend/sentry/index_spec.js b/spec/frontend/sentry/index_spec.js
index 2dd528a8a1c..83195e9d306 100644
--- a/spec/frontend/sentry/index_spec.js
+++ b/spec/frontend/sentry/index_spec.js
@@ -4,8 +4,6 @@ import LegacySentryConfig from '~/sentry/legacy_sentry_config';
import SentryConfig from '~/sentry/sentry_config';
describe('Sentry init', () => {
- let originalGon;
-
const dsn = 'https://123@sentry.gitlab.test/123';
const environment = 'test';
const currentUserId = '1';
@@ -14,7 +12,6 @@ describe('Sentry init', () => {
const featureCategory = 'my_feature_category';
beforeEach(() => {
- originalGon = window.gon;
window.gon = {
sentry_dsn: dsn,
sentry_environment: environment,
@@ -28,10 +25,6 @@ describe('Sentry init', () => {
jest.spyOn(SentryConfig, 'init').mockImplementation();
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('exports new version of Sentry in the global object', () => {
// eslint-disable-next-line no-underscore-dangle
expect(window._Sentry.SDK_VERSION).not.toMatch(/^5\./);
diff --git a/spec/frontend/sentry/legacy_index_spec.js b/spec/frontend/sentry/legacy_index_spec.js
index 5c336f8392e..493b4dfde67 100644
--- a/spec/frontend/sentry/legacy_index_spec.js
+++ b/spec/frontend/sentry/legacy_index_spec.js
@@ -4,8 +4,6 @@ import LegacySentryConfig from '~/sentry/legacy_sentry_config';
import SentryConfig from '~/sentry/sentry_config';
describe('Sentry init', () => {
- let originalGon;
-
const dsn = 'https://123@sentry.gitlab.test/123';
const environment = 'test';
const currentUserId = '1';
@@ -14,7 +12,6 @@ describe('Sentry init', () => {
const featureCategory = 'my_feature_category';
beforeEach(() => {
- originalGon = window.gon;
window.gon = {
sentry_dsn: dsn,
sentry_environment: environment,
@@ -28,10 +25,6 @@ describe('Sentry init', () => {
jest.spyOn(SentryConfig, 'init').mockImplementation();
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('exports legacy version of Sentry in the global object', () => {
// eslint-disable-next-line no-underscore-dangle
expect(window._Sentry.SDK_VERSION).toMatch(/^5\./);
diff --git a/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js b/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
index b672bc292bf..b6b3dbd5b6b 100644
--- a/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
@@ -7,7 +7,6 @@ const TEST_AVATAR = `${TEST_HOST}/avatar.png`;
const TEST_DEFAULT_AVATAR_URL = `${TEST_HOST}/default/avatar/url.png`;
describe('AssigneeAvatar', () => {
- let origGon;
let wrapper;
function createComponent(props = {}) {
@@ -24,14 +23,9 @@ describe('AssigneeAvatar', () => {
}
beforeEach(() => {
- origGon = window.gon;
window.gon = { default_avatar_url: TEST_DEFAULT_AVATAR_URL };
});
- afterEach(() => {
- window.gon = origGon;
- });
-
const findImg = () => wrapper.find('img');
it('does not show warning icon if assignee can merge', () => {
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
index bec6f9e26ab..9f7c587ca9d 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
@@ -99,7 +99,6 @@ describe('Sidebar assignees widget', () => {
afterEach(() => {
fakeApollo = null;
- delete gon.current_username;
});
describe('with passed initial assignees', () => {
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index 5662892d83e..0d0e78e9179 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -94,7 +94,6 @@ describe('Snippet Edit app', () => {
let mutateSpy;
const relativeUrlRoot = '/foo/';
- const originalRelativeUrlRoot = gon.relative_url_root;
beforeEach(() => {
stubPerformanceWebAPI();
@@ -108,10 +107,6 @@ describe('Snippet Edit app', () => {
jest.spyOn(urlUtils, 'redirectTo').mockImplementation();
});
- afterEach(() => {
- gon.relative_url_root = originalRelativeUrlRoot;
- });
-
const findBlobActions = () => wrapper.findComponent(SnippetBlobActionsEdit);
const findCancelButton = () => wrapper.findByTestId('snippet-cancel-btn');
const clickSubmitBtn = () => wrapper.findByTestId('snippet-edit-form').trigger('submit');
diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js
index 2e518b09073..994cf65c1f5 100644
--- a/spec/frontend/snippets/components/snippet_header_spec.js
+++ b/spec/frontend/snippets/components/snippet_header_spec.js
@@ -34,7 +34,6 @@ describe('Snippet header component', () => {
let mock;
let mockApollo;
- const originalRelativeUrlRoot = gon.relative_url_root;
const reportAbusePath = '/-/snippets/42/mark_as_spam';
const canReportSpam = true;
@@ -129,7 +128,6 @@ describe('Snippet header component', () => {
afterEach(() => {
mockApollo = null;
mock.restore();
- gon.relative_url_root = originalRelativeUrlRoot;
});
it('renders itself', () => {
diff --git a/spec/frontend/syntax_highlight_spec.js b/spec/frontend/syntax_highlight_spec.js
index 1be6c213350..a573c37ca44 100644
--- a/spec/frontend/syntax_highlight_spec.js
+++ b/spec/frontend/syntax_highlight_spec.js
@@ -1,14 +1,10 @@
-/* eslint-disable no-return-assign */
import $ from 'jquery';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import syntaxHighlight from '~/syntax_highlight';
describe('Syntax Highlighter', () => {
const stubUserColorScheme = (value) => {
- if (window.gon == null) {
- window.gon = {};
- }
- return (window.gon.user_color_scheme = value);
+ window.gon.user_color_scheme = value;
};
// We have to bind `document.querySelectorAll` to `document` to not mess up the fn's context
diff --git a/spec/frontend/user_popovers_spec.js b/spec/frontend/user_popovers_spec.js
index 8ce071c075f..3808cc8b0fc 100644
--- a/spec/frontend/user_popovers_spec.js
+++ b/spec/frontend/user_popovers_spec.js
@@ -10,8 +10,6 @@ jest.mock('~/api/user_api', () => ({
}));
describe('User Popovers', () => {
- let origGon;
-
const fixtureTemplate = 'merge_requests/merge_request_with_mentions.html';
const selector = '.js-user-link[data-user], .js-user-link[data-user-id]';
@@ -60,15 +58,6 @@ describe('User Popovers', () => {
});
};
- beforeEach(() => {
- origGon = window.gon;
- window.gon = {};
- });
-
- afterEach(() => {
- window.gon = origGon;
- });
-
describe('when signed out', () => {
beforeEach(() => {
setupTestSubject();
diff --git a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
index b1d9c854bb7..8c6b3cc464c 100644
--- a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
@@ -17,7 +17,6 @@ import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list
Vue.use(VueApollo);
describe('MRWidget approvals summary', () => {
- const originalUserId = gon.current_user_id;
let wrapper;
const createComponent = (data = approvedByCurrentUser) => {
@@ -30,10 +29,6 @@ describe('MRWidget approvals summary', () => {
const findAvatars = () => wrapper.findComponent(UserAvatarList);
- afterEach(() => {
- gon.current_user_id = originalUserId;
- });
-
describe('when approved', () => {
beforeEach(async () => {
createComponent();
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
index 43ce9b77cd3..fad501ee7f5 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
@@ -91,7 +91,6 @@ describe('MrWidgetOptions', () => {
// eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
wrapper.destroy();
gl.mrWidgetData = {};
- gon.features = {};
});
const createComponent = (mrData = mockData, options = {}, data = {}, fullMount = true) => {
@@ -1266,10 +1265,6 @@ describe('MrWidgetOptions', () => {
});
describe('widget container', () => {
- afterEach(() => {
- delete window.gon.features.refactorSecurityExtension;
- });
-
it('should not be displayed when the refactor_security_extension feature flag is turned off', () => {
createComponent();
expect(findWidgetContainer().exists()).toBe(false);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
index 6e63dee843a..dd0ec65c871 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
@@ -165,16 +165,10 @@ describe('Filters actions', () => {
});
describe('fetchAuthors', () => {
- let restoreVersion;
beforeEach(() => {
- restoreVersion = gon.api_version;
gon.api_version = 'v1';
});
- afterEach(() => {
- gon.api_version = restoreVersion;
- });
-
describe('success', () => {
beforeEach(() => {
mock.onAny().replyOnce(HTTP_STATUS_OK, filterUsers);
@@ -305,17 +299,11 @@ describe('Filters actions', () => {
describe('fetchAssignees', () => {
describe('success', () => {
- let restoreVersion;
beforeEach(() => {
mock.onAny().replyOnce(HTTP_STATUS_OK, filterUsers);
- restoreVersion = gon.api_version;
gon.api_version = 'v1';
});
- afterEach(() => {
- gon.api_version = restoreVersion;
- });
-
it('dispatches RECEIVE_ASSIGNEES_SUCCESS with received data and groupEndpoint set', () => {
return testAction(
actions.fetchAssignees,
@@ -350,17 +338,11 @@ describe('Filters actions', () => {
});
describe('error', () => {
- let restoreVersion;
beforeEach(() => {
mock.onAny().replyOnce(HTTP_STATUS_SERVICE_UNAVAILABLE);
- restoreVersion = gon.api_version;
gon.api_version = 'v1';
});
- afterEach(() => {
- gon.api_version = restoreVersion;
- });
-
it('dispatches RECEIVE_ASSIGNEES_ERROR and groupEndpoint set', () => {
return testAction(
actions.fetchAssignees,
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
index a910e85a03e..89003296854 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
@@ -67,7 +67,6 @@ function createComponent(options = {}) {
}
describe('UserToken', () => {
- const originalGon = window.gon;
const currentUserLength = 1;
let mock;
let wrapper;
@@ -79,7 +78,6 @@ describe('UserToken', () => {
});
afterEach(() => {
- window.gon = originalGon;
mock.restore();
});
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
index bb2758bcf8e..45daf0dc34b 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
@@ -39,7 +39,6 @@ describe('IssuableItem', () => {
const mockLabels = mockIssuable.labels.nodes;
const mockAuthor = mockIssuable.author;
- const originalUrl = gon.gitlab_url;
let wrapper;
const findTimestampWrapper = () => wrapper.find('[data-testid="issuable-timestamp"]');
@@ -49,10 +48,6 @@ describe('IssuableItem', () => {
gon.gitlab_url = MOCK_GITLAB_URL;
});
- afterEach(() => {
- gon.gitlab_url = originalUrl;
- });
-
describe('computed', () => {
describe('author', () => {
it('returns `issuable.author` reference', () => {
diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb
index 714eaebfe73..c64144cc521 100644
--- a/spec/graphql/types/ci/job_type_spec.rb
+++ b/spec/graphql/types/ci/job_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Types::Ci::JobType do
+RSpec.describe Types::Ci::JobType, feature_category: :continuous_integration do
include GraphqlHelpers
specify { expect(described_class.graphql_name).to eq('CiJob') }
@@ -40,6 +40,7 @@ RSpec.describe Types::Ci::JobType do
refPath
retryable
retried
+ runnerMachine
scheduledAt
schedulingType
shortSha
diff --git a/spec/graphql/types/ci/runner_machine_type_spec.rb b/spec/graphql/types/ci/runner_machine_type_spec.rb
index 0a473716a96..289cc52e27b 100644
--- a/spec/graphql/types/ci/runner_machine_type_spec.rb
+++ b/spec/graphql/types/ci/runner_machine_type_spec.rb
@@ -10,9 +10,9 @@ RSpec.describe GitlabSchema.types['CiRunnerMachine'], feature_category: :runner_
it 'contains attributes related to a runner machine' do
expected_fields = %w[
architecture_name contacted_at created_at executor_name id ip_address platform_name revision
- runner status version
+ runner status system_id version
]
- expect(described_class).to include_graphql_fields(*expected_fields)
+ expect(described_class).to have_graphql_fields(*expected_fields)
end
end
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 68a6b6293c8..91635ffcdc0 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-RSpec.describe NotesHelper do
+RSpec.describe NotesHelper, feature_category: :team_planning do
include RepoHelpers
let_it_be(:owner) { create(:owner) }
@@ -223,6 +223,17 @@ RSpec.describe NotesHelper do
end
end
+ describe '#initial_notes_data' do
+ it 'return initial notes data for issuable' do
+ autocomplete = '/autocomplete/users'
+ @project = project
+ @noteable = create(:issue, project: @project)
+
+ expect(helper.initial_notes_data(autocomplete).keys).to match_array(%i[notesUrl now diffView enableGFM])
+ expect(helper.initial_notes_data(autocomplete)[:enableGFM].keys).to match(%i[emojis members issues mergeRequests vulnerabilities epics milestones labels])
+ end
+ end
+
describe '#notes_url' do
it 'return snippet notes path for personal snippet' do
@snippet = create(:personal_snippet)
diff --git a/spec/lib/gitlab/auth/otp/strategies/duo_auth/manual_otp_spec.rb b/spec/lib/gitlab/auth/otp/strategies/duo_auth/manual_otp_spec.rb
new file mode 100644
index 00000000000..d04e0ad9fb4
--- /dev/null
+++ b/spec/lib/gitlab/auth/otp/strategies/duo_auth/manual_otp_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp, feature_category: :system_access do
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:otp_code) { 42 }
+
+ let_it_be(:hostname) { 'duo_auth.example.com' }
+ let_it_be(:integration_key) { 'int3gr4t1on' }
+ let_it_be(:secret_key) { 's3cr3t' }
+
+ let_it_be(:duo_response_builder) { Struct.new(:body) }
+
+ let_it_be(:response_status) { 200 }
+
+ let_it_be(:duo_auth_url) { "https://#{hostname}/auth/v2/auth/" }
+ let_it_be(:params) do
+ { username: user.username,
+ factor: "passcode",
+ passcode: otp_code }
+ end
+
+ let_it_be(:manual_otp) { described_class.new(user) }
+
+ subject(:response) { manual_otp.validate(otp_code) }
+
+ before do
+ stub_duo_auth_config(
+ enabled: true,
+ hostname: hostname,
+ secret_key: secret_key,
+ integration_key: integration_key
+ )
+ end
+
+ context 'when successful validation' do
+ before do
+ allow(duo_client).to receive(:request)
+ .with("POST", "/auth/v2/auth", params)
+ .and_return(duo_response_builder.new('{ "response": { "result": "allow" }}'))
+
+ allow(manual_otp).to receive(:duo_client).and_return(duo_client)
+ end
+
+ it 'returns success' do
+ response
+
+ expect(response[:status]).to eq(:success)
+ end
+ end
+
+ context 'when unsuccessful validation' do
+ before do
+ allow(duo_client).to receive(:request)
+ .with("POST", "/auth/v2/auth", params)
+ .and_return(duo_response_builder.new('{ "response": { "result": "deny" }}'))
+
+ allow(manual_otp).to receive(:duo_client).and_return(duo_client)
+ end
+
+ it 'returns error' do
+ response
+
+ expect(response[:status]).to eq(:error)
+ end
+ end
+
+ context 'when unexpected error' do
+ before do
+ allow(duo_client).to receive(:request)
+ .with("POST", "/auth/v2/auth", params)
+ .and_return(duo_response_builder.new('aaa'))
+
+ allow(manual_otp).to receive(:duo_client).and_return(duo_client)
+ end
+
+ it 'returns error' do
+ response
+
+ expect(response[:status]).to eq(:error)
+ expect(response[:message]).to match(/unexpected character/)
+ end
+ end
+
+ def stub_duo_auth_config(duo_auth_settings)
+ allow(::Gitlab.config.duo_auth).to(receive_messages(duo_auth_settings))
+ end
+
+ def duo_client
+ manual_otp.send(:duo_client)
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index ca784ead25a..f6d6a791e8c 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -570,6 +570,7 @@ project:
- external_wiki_integration
- mock_ci_integration
- mock_monitoring_integration
+- squash_tm_integration
- forked_to_members
- forked_from_project
- forks
diff --git a/spec/models/bulk_imports/batch_tracker_spec.rb b/spec/models/bulk_imports/batch_tracker_spec.rb
new file mode 100644
index 00000000000..336943228c7
--- /dev/null
+++ b/spec/models/bulk_imports/batch_tracker_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::BatchTracker, type: :model, feature_category: :importers do
+ describe 'associations' do
+ it { is_expected.to belong_to(:tracker) }
+ end
+
+ describe 'validations' do
+ subject { build(:bulk_import_batch_tracker) }
+
+ it { is_expected.to validate_presence_of(:batch_number) }
+ it { is_expected.to validate_uniqueness_of(:batch_number).scoped_to(:tracker_id) }
+ end
+end
diff --git a/spec/models/bulk_imports/export_batch_spec.rb b/spec/models/bulk_imports/export_batch_spec.rb
new file mode 100644
index 00000000000..43209921b9c
--- /dev/null
+++ b/spec/models/bulk_imports/export_batch_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::ExportBatch, type: :model, feature_category: :importers do
+ describe 'associations' do
+ it { is_expected.to belong_to(:export) }
+ it { is_expected.to have_one(:upload) }
+ end
+
+ describe 'validations' do
+ subject { build(:bulk_import_export_batch) }
+
+ it { is_expected.to validate_presence_of(:batch_number) }
+ it { is_expected.to validate_uniqueness_of(:batch_number).scoped_to(:export_id) }
+ end
+end
diff --git a/spec/models/bulk_imports/export_spec.rb b/spec/models/bulk_imports/export_spec.rb
index d85b77d599b..7173d032bc2 100644
--- a/spec/models/bulk_imports/export_spec.rb
+++ b/spec/models/bulk_imports/export_spec.rb
@@ -2,11 +2,12 @@
require 'spec_helper'
-RSpec.describe BulkImports::Export, type: :model do
+RSpec.describe BulkImports::Export, type: :model, feature_category: :importers do
describe 'associations' do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:upload) }
+ it { is_expected.to have_many(:batches) }
end
describe 'validations' do
diff --git a/spec/models/ci/runner_machine_build_spec.rb b/spec/models/ci/runner_machine_build_spec.rb
index 012baadfb4a..b43ff535477 100644
--- a/spec/models/ci/runner_machine_build_spec.rb
+++ b/spec/models/ci/runner_machine_build_spec.rb
@@ -3,6 +3,10 @@
require 'spec_helper'
RSpec.describe Ci::RunnerMachineBuild, model: true, feature_category: :runner_fleet do
+ let_it_be(:runner) { create(:ci_runner) }
+ let_it_be(:runner_machine) { create(:ci_runner_machine, runner: runner) }
+ let_it_be(:build) { create(:ci_build, runner_machine: runner_machine) }
+
it { is_expected.to belong_to(:build) }
it { is_expected.to belong_to(:runner_machine) }
@@ -54,4 +58,43 @@ RSpec.describe Ci::RunnerMachineBuild, model: true, feature_category: :runner_fl
let!(:model) { create(:ci_runner_machine_build, runner_machine: parent) }
end
end
+
+ describe '.for_build' do
+ subject(:for_build) { described_class.for_build(build_id) }
+
+ context 'with valid build_id' do
+ let(:build_id) { build.id }
+
+ it { is_expected.to contain_exactly(described_class.find_by_build_id(build_id)) }
+ end
+
+ context 'with valid build_ids' do
+ let(:build2) { create(:ci_build, runner_machine: runner_machine) }
+ let(:build_id) { [build, build2] }
+
+ it { is_expected.to eq(described_class.where(build_id: build_id)) }
+ end
+
+ context 'with non-existeng build_id' do
+ let(:build_id) { non_existing_record_id }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '.pluck_runner_machine_id_and_build_id' do
+ subject { scope.pluck_build_id_and_runner_machine_id }
+
+ context 'with default scope' do
+ let(:scope) { described_class }
+
+ it { is_expected.to eq({ build.id => runner_machine.id }) }
+ end
+
+ context 'with scope excluding build' do
+ let(:scope) { described_class.where(build_id: non_existing_record_id) }
+
+ it { is_expected.to be_empty }
+ end
+ end
end
diff --git a/spec/models/integrations/squash_tm_spec.rb b/spec/models/integrations/squash_tm_spec.rb
new file mode 100644
index 00000000000..f12e37dae6d
--- /dev/null
+++ b/spec/models/integrations/squash_tm_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Integrations::SquashTm, feature_category: :integrations do
+ it_behaves_like Integrations::HasWebHook do
+ let_it_be(:project) { create(:project) }
+
+ let(:integration) { build(:squash_tm_integration, project: project) }
+ let(:hook_url) { "#{integration.url}?token={token}" }
+ end
+
+ it_behaves_like Integrations::ResetSecretFields do
+ let(:integration) { subject }
+ end
+
+ describe 'Validations' do
+ context 'when integration is active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:url) }
+ it { is_expected.to allow_value('https://example.com').for(:url) }
+ it { is_expected.not_to allow_value(nil).for(:url) }
+ it { is_expected.not_to allow_value('').for(:url) }
+ it { is_expected.not_to allow_value('foo').for(:url) }
+ it { is_expected.not_to allow_value('example.com').for(:url) }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ it { is_expected.to validate_length_of(:token).is_at_most(255) }
+ it { is_expected.to allow_value(nil).for(:token) }
+ it { is_expected.to allow_value('foo').for(:token) }
+ end
+
+ context 'when integration is inactive' do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:url) }
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
+ describe '#execute' do
+ let(:integration) { build(:squash_tm_integration, project: build(:project)) }
+
+ let(:squash_tm_hook_url) do
+ "#{integration.url}?token=#{integration.token}"
+ end
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:issue) { create(:issue) }
+ let(:data) { issue.to_hook_data(user) }
+
+ before do
+ stub_request(:post, squash_tm_hook_url)
+ end
+
+ it 'calls Squash TM API' do
+ integration.execute(data)
+
+ expect(a_request(:post, squash_tm_hook_url)).to have_been_made.once
+ end
+ end
+
+ describe '#test' do
+ let(:integration) { build(:squash_tm_integration) }
+
+ let(:squash_tm_hook_url) do
+ "#{integration.url}?token=#{integration.token}"
+ end
+
+ subject(:result) { integration.test({}) }
+
+ context 'when server is responding' do
+ let(:body) { 'OK' }
+ let(:status) { 200 }
+
+ before do
+ stub_request(:post, squash_tm_hook_url)
+ .to_return(status: status, body: body)
+ end
+
+ it { is_expected.to eq(success: true, result: 'OK') }
+ end
+
+ context 'when server rejects the request' do
+ let(:body) { 'Unauthorized' }
+ let(:status) { 401 }
+
+ before do
+ stub_request(:post, squash_tm_hook_url)
+ .to_return(status: status, body: body)
+ end
+
+ it { is_expected.to eq(success: false, result: body) }
+ end
+
+ context 'when test request executes with errors' do
+ before do
+ allow(integration).to receive(:execute_web_hook!)
+ .with({}, "Test Configuration Hook")
+ .and_raise(StandardError, 'error message')
+ end
+
+ it { is_expected.to eq(success: false, result: 'error message') }
+ end
+ end
+
+ describe '.default_test_event' do
+ subject { described_class.default_test_event }
+
+ it { is_expected.to eq('issue') }
+ end
+end
diff --git a/spec/models/preloaders/runner_machine_policy_preloader_spec.rb b/spec/models/preloaders/runner_machine_policy_preloader_spec.rb
new file mode 100644
index 00000000000..26fc101d8dc
--- /dev/null
+++ b/spec/models/preloaders/runner_machine_policy_preloader_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Preloaders::RunnerMachinePolicyPreloader, feature_category: :runner_fleet do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:runner1) { create(:ci_runner) }
+ let_it_be(:runner2) { create(:ci_runner) }
+ let_it_be(:runner_machine1) { create(:ci_runner_machine, runner: runner1) }
+ let_it_be(:runner_machine2) { create(:ci_runner_machine, runner: runner2) }
+
+ let(:base_runner_machines) do
+ Project.where(id: [runner_machine1, runner_machine2])
+ end
+
+ it 'avoids N+1 queries when authorizing a list of runner machines', :request_store do
+ preload_runner_machines_for_policy(user)
+ control = ActiveRecord::QueryRecorder.new { authorize_all_runner_machines(user) }
+
+ new_runner1 = create(:ci_runner)
+ new_runner2 = create(:ci_runner)
+ new_runner_machine1 = create(:ci_runner_machine, runner: new_runner1)
+ new_runner_machine2 = create(:ci_runner_machine, runner: new_runner2)
+
+ pristine_runner_machines = Project.where(id: base_runner_machines + [new_runner_machine1, new_runner_machine2])
+
+ preload_runner_machines_for_policy(user, pristine_runner_machines)
+ expect { authorize_all_runner_machines(user, pristine_runner_machines) }.not_to exceed_query_limit(control)
+ end
+
+ def authorize_all_runner_machines(current_user, runner_machine_list = base_runner_machines)
+ runner_machine_list.each { |runner_machine| current_user.can?(:read_runner_machine, runner_machine) }
+ end
+
+ def preload_runner_machines_for_policy(current_user, runner_machine_list = base_runner_machines)
+ described_class.new(runner_machine_list, current_user).execute
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 995b9c473fc..04f1bffce0a 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2187,6 +2187,24 @@ RSpec.describe User, feature_category: :user_profile do
end
end
+ context 'Duo Auth' do
+ context 'when enabled via GitLab settings' do
+ before do
+ allow(::Gitlab.config.duo_auth).to receive(:enabled).and_return(true)
+ end
+
+ it { expect(user.two_factor_otp_enabled?).to eq(true) }
+ end
+
+ context 'when disabled via GitLab settings' do
+ before do
+ allow(::Gitlab.config.duo_auth).to receive(:enabled).and_return(false)
+ end
+
+ it { expect(user.two_factor_otp_enabled?).to eq(false) }
+ end
+ end
+
context 'FortiTokenCloud' do
context 'when enabled via GitLab settings' do
before do
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb
index 674407c0a0e..3f556a75869 100644
--- a/spec/requests/api/graphql/ci/jobs_spec.rb
+++ b/spec/requests/api/graphql/ci/jobs_spec.rb
@@ -260,6 +260,68 @@ RSpec.describe 'Query.project.pipeline', feature_category: :continuous_integrati
end
end
+ describe '.jobs.runnerMachine' do
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:runner_machine) { create(:ci_runner_machine, created_at: Time.current, contacted_at: Time.current) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:build) do
+ create(:ci_build, pipeline: pipeline, name: 'my test job', runner_machine: runner_machine)
+ end
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipeline(iid: "#{pipeline.iid}") {
+ jobs {
+ nodes {
+ id
+ name
+ runnerMachine {
+ #{all_graphql_fields_for('CiRunnerMachine', excluded: [:runner], max_depth: 1)}
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ let(:jobs_graphql_data) { graphql_data_at(:project, :pipeline, :jobs, :nodes) }
+
+ it 'returns the runner machine in each job of a pipeline' do
+ post_graphql(query, current_user: admin)
+
+ expect(jobs_graphql_data).to contain_exactly(
+ a_graphql_entity_for(
+ build,
+ name: build.name,
+ runner_machine: a_graphql_entity_for(
+ runner_machine,
+ system_id: runner_machine.system_xid,
+ created_at: runner_machine.created_at.iso8601,
+ contacted_at: runner_machine.contacted_at.iso8601,
+ status: runner_machine.status.to_s.upcase
+ )
+ )
+ )
+ end
+
+ it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
+ admin2 = create(:admin)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post_graphql(query, current_user: admin)
+ end
+
+ runner_machine2 = create(:ci_runner_machine)
+ create(:ci_build, pipeline: pipeline, name: 'my test job2', runner_machine: runner_machine2)
+
+ expect { post_graphql(query, current_user: admin2) }.not_to exceed_all_query_limit(control)
+ end
+ end
+
describe '.jobs.count' do
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:successful_job) { create(:ci_build, :success, pipeline: pipeline) }
diff --git a/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb b/spec/requests/api/graphql/mutations/ci/job/cancel_spec.rb
index 468a9e57f56..abad1ae0812 100644
--- a/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb
+++ b/spec/requests/api/graphql/mutations/ci/job/cancel_spec.rb
@@ -15,12 +15,12 @@ RSpec.describe "JobCancel", feature_category: :continuous_integration do
id: job.to_global_id.to_s
}
graphql_mutation(:job_cancel, variables,
- <<-QL
+ <<-QL
errors
job {
id
}
- QL
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/ci/job_play_spec.rb b/spec/requests/api/graphql/mutations/ci/job/play_spec.rb
index 9ba80e51dee..8100274ed97 100644
--- a/spec/requests/api/graphql/mutations/ci/job_play_spec.rb
+++ b/spec/requests/api/graphql/mutations/ci/job/play_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe 'JobPlay', feature_category: :continuous_integration do
let(:mutation) do
graphql_mutation(:job_play, variables,
- <<-QL
+ <<-QL
errors
job {
id
@@ -28,7 +28,7 @@ RSpec.describe 'JobPlay', feature_category: :continuous_integration do
}
}
}
- QL
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/ci/job_retry_spec.rb b/spec/requests/api/graphql/mutations/ci/job/retry_spec.rb
index e49ee6f3163..4114c77491b 100644
--- a/spec/requests/api/graphql/mutations/ci/job_retry_spec.rb
+++ b/spec/requests/api/graphql/mutations/ci/job/retry_spec.rb
@@ -16,12 +16,12 @@ RSpec.describe 'JobRetry', feature_category: :continuous_integration do
id: job.to_global_id.to_s
}
graphql_mutation(:job_retry, variables,
- <<-QL
+ <<-QL
errors
job {
id
}
- QL
+ QL
)
end
@@ -57,12 +57,12 @@ RSpec.describe 'JobRetry', feature_category: :continuous_integration do
}
graphql_mutation(:job_retry, variables,
- <<-QL
+ <<-QL
errors
job {
id
}
- QL
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb b/spec/requests/api/graphql/mutations/ci/job/unschedule_spec.rb
index 6868b0ea279..08e155e808b 100644
--- a/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb
+++ b/spec/requests/api/graphql/mutations/ci/job/unschedule_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'JobUnschedule', feature_category: :continuous_integration do
id: job.to_global_id.to_s
}
graphql_mutation(:job_unschedule, variables,
- <<-QL
+ <<-QL
errors
job {
id
diff --git a/spec/services/packages/debian/generate_distribution_service_spec.rb b/spec/services/packages/debian/generate_distribution_service_spec.rb
index 6d179c791a3..27206b847e4 100644
--- a/spec/services/packages/debian/generate_distribution_service_spec.rb
+++ b/spec/services/packages/debian/generate_distribution_service_spec.rb
@@ -3,20 +3,32 @@
require 'spec_helper'
RSpec.describe Packages::Debian::GenerateDistributionService, feature_category: :package_registry do
- describe '#execute' do
- subject { described_class.new(distribution).execute }
+ include_context 'with published Debian package'
- let(:subject2) { described_class.new(distribution).execute }
- let(:subject3) { described_class.new(distribution).execute }
+ let(:service) { described_class.new(distribution) }
- include_context 'with published Debian package'
+ [:project, :group].each do |container_type|
+ context "for #{container_type}" do
+ include_context 'with Debian distribution', container_type
- [:project, :group].each do |container_type|
- context "for #{container_type}" do
- include_context 'with Debian distribution', container_type
+ describe '#execute' do
+ subject { service.execute }
+
+ let(:subject2) { described_class.new(distribution).execute }
+ let(:subject3) { described_class.new(distribution).execute }
it_behaves_like 'Generate Debian Distribution and component files'
end
+
+ describe '#lease_key' do
+ subject { service.send(:lease_key) }
+
+ let(:prefix) { "packages:debian:generate_distribution_service:" }
+
+ it 'returns an unique key' do
+ is_expected.to eq "#{prefix}#{container_type}_distribution:#{distribution.id}"
+ end
+ end
end
end
end
diff --git a/spec/services/users/validate_manual_otp_service_spec.rb b/spec/services/users/validate_manual_otp_service_spec.rb
index d71735814f2..4bb2c0b6ab6 100644
--- a/spec/services/users/validate_manual_otp_service_spec.rb
+++ b/spec/services/users/validate_manual_otp_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Users::ValidateManualOtpService do
+RSpec.describe Users::ValidateManualOtpService, feature_category: :system_access do
let_it_be(:user) { create(:user) }
let(:otp_code) { 42 }
@@ -32,6 +32,20 @@ RSpec.describe Users::ValidateManualOtpService do
validate
end
+
+ it 'handles unexpected error' do
+ error_message = "boom!"
+
+ expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::FortiAuthenticator::ManualOtp) do |strategy|
+ expect(strategy).to receive(:validate).with(otp_code).once.and_raise(StandardError, error_message)
+ end
+ expect(Gitlab::ErrorTracking).to receive(:log_exception)
+
+ result = validate
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
end
context 'FortiTokenCloud' do
@@ -49,16 +63,23 @@ RSpec.describe Users::ValidateManualOtpService do
end
end
- context 'unexpected error' do
+ context 'DuoAuth' do
before do
- stub_feature_flags(forti_authenticator: user)
- allow(::Gitlab.config.forti_authenticator).to receive(:enabled).and_return(true)
+ allow(::Gitlab.config.duo_auth).to receive(:enabled).and_return(true)
end
- it 'returns error' do
+ it 'calls DuoAuth strategy' do
+ expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp) do |strategy|
+ expect(strategy).to receive(:validate).with(otp_code).once
+ end
+
+ validate
+ end
+
+ it "handles unexpected error" do
error_message = "boom!"
- expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::FortiAuthenticator::ManualOtp) do |strategy|
+ expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp) do |strategy|
expect(strategy).to receive(:validate).with(otp_code).once.and_raise(StandardError, error_message)
end
expect(Gitlab::ErrorTracking).to receive(:log_exception)
diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
index 9f940d27341..2070cac24b0 100644
--- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
@@ -63,35 +63,6 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
expect(gitlab_shell.repository_exists?('default', old_project_repository_path)).to be(false)
expect(gitlab_shell.repository_exists?('default', old_repository_path)).to be(false)
end
-
- context ':repack_after_shard_migration feature flag disabled' do
- before do
- stub_feature_flags(repack_after_shard_migration: false)
- end
-
- it 'does not enqueue a GC run' do
- expect { subject.execute }
- .not_to change { Projects::GitGarbageCollectWorker.jobs.count }
- end
- end
-
- context ':repack_after_shard_migration feature flag enabled' do
- before do
- stub_feature_flags(repack_after_shard_migration: true)
- end
-
- it 'does not enqueue a GC run if housekeeping is disabled' do
- stub_application_setting(housekeeping_enabled: false)
-
- expect { subject.execute }
- .not_to change { Projects::GitGarbageCollectWorker.jobs.count }
- end
-
- it 'enqueues a GC run' do
- expect { subject.execute }
- .to change { Projects::GitGarbageCollectWorker.jobs.count }.by(1)
- end
- end
end
context 'when the filesystems are the same' do
diff --git a/spec/workers/packages/debian/generate_distribution_worker_spec.rb b/spec/workers/packages/debian/generate_distribution_worker_spec.rb
index c4e974ec8eb..acdfcc5275f 100644
--- a/spec/workers/packages/debian/generate_distribution_worker_spec.rb
+++ b/spec/workers/packages/debian/generate_distribution_worker_spec.rb
@@ -4,13 +4,13 @@ require 'spec_helper'
RSpec.describe Packages::Debian::GenerateDistributionWorker, type: :worker, feature_category: :package_registry do
describe '#perform' do
- let(:container_type) { distribution.container_type }
+ let(:container_type_as_string) { container_type.to_s }
let(:distribution_id) { distribution.id }
- subject { described_class.new.perform(container_type, distribution_id) }
+ subject { described_class.new.perform(container_type_as_string, distribution_id) }
- let(:subject2) { described_class.new.perform(container_type, distribution_id) }
- let(:subject3) { described_class.new.perform(container_type, distribution_id) }
+ let(:subject2) { described_class.new.perform(container_type_as_string, distribution_id) }
+ let(:subject3) { described_class.new.perform(container_type_as_string, distribution_id) }
include_context 'with published Debian package'
@@ -54,7 +54,7 @@ RSpec.describe Packages::Debian::GenerateDistributionWorker, type: :worker, feat
context 'with valid parameters' do
it_behaves_like 'an idempotent worker' do
- let(:job_args) { [container_type, distribution_id] }
+ let(:job_args) { [container_type_as_string, distribution_id] }
it_behaves_like 'Generate Debian Distribution and component files'
end
diff --git a/workhorse/go.mod b/workhorse/go.mod
index 3553b197cd2..0e347412ea6 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -7,7 +7,7 @@ require (
github.com/BurntSushi/toml v1.2.1
github.com/FZambia/sentinel v1.1.1
github.com/alecthomas/chroma/v2 v2.5.0
- github.com/aws/aws-sdk-go v1.44.216
+ github.com/aws/aws-sdk-go v1.44.217
github.com/disintegration/imaging v1.6.2
github.com/getsentry/raven-go v0.2.0
github.com/golang-jwt/jwt/v4 v4.5.0
diff --git a/workhorse/go.sum b/workhorse/go.sum
index 9fb8ea1524e..29139218d46 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -569,8 +569,8 @@ github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4
github.com/aws/aws-sdk-go v1.44.156/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.187/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.200/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
-github.com/aws/aws-sdk-go v1.44.216 h1:nDL5hEGBlUNHXMWbpP4dIyP8IB5tvRgksWE7biVu8JY=
-github.com/aws/aws-sdk-go v1.44.216/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.217 h1:FcWC56MRl+k756aH3qeMQTylSdeJ58WN0iFz3fkyRz0=
+github.com/aws/aws-sdk-go v1.44.217/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY=
github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
diff --git a/yarn.lock b/yarn.lock
index 2b5aa3bad87..a526d6622c2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1237,10 +1237,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.24.0.tgz#bc8265919aa04b06cd08be91637471bad195936d"
integrity sha512-R4s5qJUFUIbPflknpw1aI/PchiNq65vY7LVsJZnQkY+vi+AgmsETdut/AdferbGWmeWMU0q2wuVu9phE8lDUgA==
-"@gitlab/ui@56.4.1":
- version "56.4.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-56.4.1.tgz#bdf6284053b092608f6f0b70c6abeb36fd352aa2"
- integrity sha512-WlEryaV/pwaJkHa+ioCcyB9slE+2RoPs15cmS+OtA9XsmWKqZTI1S00uZG9GcnA9hDKZM+HP2Apy7ku+myzLDQ==
+"@gitlab/ui@57.0.0":
+ version "57.0.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-57.0.0.tgz#3a9e8894ba84ccf4e396441c17289d74757e2914"
+ integrity sha512-7mNcTR/qCgeuF0AsK6UnrckELyuT8W2Kw+wrNracy7esWcGsy/DgI91vECWt421jPe1eI7m/2N4mATuAMwicHQ==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.23.1"