Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e894595ad8
commit
0ff373dc41
|
|
@ -440,9 +440,6 @@ Graphql/Descriptions:
|
|||
- 'app/graphql/**/*'
|
||||
- 'ee/app/graphql/**/*'
|
||||
|
||||
RSpec/AnyInstanceOf:
|
||||
Enabled: false
|
||||
|
||||
# Cops for upgrade to gitlab-styles 3.1.0
|
||||
RSpec/ImplicitSubject:
|
||||
Enabled: false
|
||||
|
|
|
|||
|
|
@ -1037,3 +1037,520 @@ Graphql/Descriptions:
|
|||
- 'ee/app/graphql/types/vulnerable_dependency_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerable_package_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerable_projects_by_grade_type.rb'
|
||||
|
||||
# WIP: https://gitlab.com/gitlab-org/gitlab/-/issues/34997
|
||||
RSpec/AnyInstanceOf:
|
||||
Exclude:
|
||||
- 'ee/spec/controllers/admin/geo/nodes_controller_spec.rb'
|
||||
- 'ee/spec/controllers/ee/groups_controller_spec.rb'
|
||||
- 'ee/spec/controllers/groups/analytics/productivity_analytics_controller_spec.rb'
|
||||
- 'ee/spec/controllers/groups/epics/notes_controller_spec.rb'
|
||||
- 'ee/spec/controllers/groups/omniauth_callbacks_controller_spec.rb'
|
||||
- 'ee/spec/controllers/oauth/geo_auth_controller_spec.rb'
|
||||
- 'ee/spec/controllers/projects/environments_controller_spec.rb'
|
||||
- 'ee/spec/controllers/projects/integrations/jira/issues_controller_spec.rb'
|
||||
- 'ee/spec/controllers/projects/merge_requests_controller_spec.rb'
|
||||
- 'ee/spec/controllers/projects/path_locks_controller_spec.rb'
|
||||
- 'ee/spec/controllers/projects_controller_spec.rb'
|
||||
- 'ee/spec/controllers/subscriptions_controller_spec.rb'
|
||||
- 'ee/spec/controllers/trials_controller_spec.rb'
|
||||
- 'ee/spec/features/admin/admin_audit_logs_spec.rb'
|
||||
- 'ee/spec/features/admin/admin_reset_pipeline_minutes_spec.rb'
|
||||
- 'ee/spec/features/admin/admin_users_spec.rb'
|
||||
- 'ee/spec/features/admin/licenses/admin_views_license_spec.rb'
|
||||
- 'ee/spec/features/boards/scoped_issue_board_spec.rb'
|
||||
- 'ee/spec/features/ci_shared_runner_warnings_spec.rb'
|
||||
- 'ee/spec/features/groups/group_settings_spec.rb'
|
||||
- 'ee/spec/features/groups/navbar_spec.rb'
|
||||
- 'ee/spec/features/groups/saml_providers_spec.rb'
|
||||
- 'ee/spec/features/issues/form_spec.rb'
|
||||
- 'ee/spec/features/merge_request/user_creates_merge_request_spec.rb'
|
||||
- 'ee/spec/features/projects/new_project_spec.rb'
|
||||
- 'ee/spec/features/projects/services/user_activates_jira_spec.rb'
|
||||
- 'ee/spec/features/registrations/welcome_spec.rb'
|
||||
- 'ee/spec/features/security/project/internal_access_spec.rb'
|
||||
- 'ee/spec/features/security/project/private_access_spec.rb'
|
||||
- 'ee/spec/features/security/project/public_access_spec.rb'
|
||||
- 'ee/spec/features/trials/capture_lead_spec.rb'
|
||||
- 'ee/spec/features/trials/select_namespace_spec.rb'
|
||||
- 'ee/spec/features/users/login_spec.rb'
|
||||
- 'ee/spec/graphql/mutations/dast_on_demand_scans/create_spec.rb'
|
||||
- 'ee/spec/graphql/mutations/incident_management/oncall_schedule/create_spec.rb'
|
||||
- 'ee/spec/graphql/mutations/incident_management/oncall_schedule/destroy_spec.rb'
|
||||
- 'ee/spec/graphql/mutations/incident_management/oncall_schedule/update_spec.rb'
|
||||
- 'ee/spec/helpers/application_helper_spec.rb'
|
||||
- 'ee/spec/lib/ee/api/helpers_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/auth/ldap/sync/group_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/checks/push_rule_check_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/auth/group_saml/membership_enforcer_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/Jobs/dast_default_branch_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/Jobs/load_performance_testing_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/Verify/browser_performance_testing_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/api_fuzzing_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/container_scanning_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/coverage_fuzzing_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/dast_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/dependency_scanning_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/license_scanning_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/ci/templates/sast_gitlab_ci_yaml_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/elastic/project_search_results_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/expiring_subscription_message_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/geo/log_cursor/daemon_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb'
|
||||
- 'ee/spec/lib/omni_auth/strategies/group_saml_spec.rb'
|
||||
- 'ee/spec/lib/security/ci_configuration/sast_build_actions_spec.rb'
|
||||
- 'ee/spec/lib/system_check/geo/geo_database_configured_check_spec.rb'
|
||||
- 'ee/spec/migrations/schedule_populate_resolved_on_default_branch_column_spec.rb'
|
||||
- 'ee/spec/migrations/update_location_fingerprint_column_for_cs_spec.rb'
|
||||
- 'ee/spec/migrations/update_occurrence_severity_column_spec.rb'
|
||||
- 'ee/spec/migrations/update_undefined_confidence_from_occurrences_spec.rb'
|
||||
- 'ee/spec/migrations/update_undefined_confidence_from_vulnerabilities_spec.rb'
|
||||
- 'ee/spec/migrations/update_vulnerability_severity_column_spec.rb'
|
||||
- 'ee/spec/models/ee/namespace_spec.rb'
|
||||
- 'ee/spec/models/geo_node_status_spec.rb'
|
||||
- 'ee/spec/models/group_spec.rb'
|
||||
- 'ee/spec/models/issue_spec.rb'
|
||||
- 'ee/spec/models/merge_request_spec.rb'
|
||||
- 'ee/spec/models/project_import_state_spec.rb'
|
||||
- 'ee/spec/models/push_rule_spec.rb'
|
||||
- 'ee/spec/presenters/ci/pipeline_presenter_spec.rb'
|
||||
- 'ee/spec/presenters/projects/security/configuration_presenter_spec.rb'
|
||||
- 'ee/spec/requests/api/geo_nodes_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/mutations/dast_on_demand_scans/create_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/mutations/dast_site_profiles/delete_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/mutations/pipelines/run_dast_scan_spec.rb'
|
||||
- 'ee/spec/requests/api/issues_spec.rb'
|
||||
- 'ee/spec/requests/api/projects_spec.rb'
|
||||
- 'ee/spec/requests/git_http_spec.rb'
|
||||
- 'ee/spec/requests/groups_controller_spec.rb'
|
||||
- 'ee/spec/requests/omniauth_kerberos_spnego_spec.rb'
|
||||
- 'ee/spec/requests/repositories/git_http_controller_spec.rb'
|
||||
- 'ee/spec/services/alert_management/network_alert_service_spec.rb'
|
||||
- 'ee/spec/services/ci/expire_pipeline_cache_service_spec.rb'
|
||||
- 'ee/spec/services/ci/run_dast_scan_service_spec.rb'
|
||||
- 'ee/spec/services/ee/git/branch_push_service_spec.rb'
|
||||
- 'ee/spec/services/ee/merge_requests/create_from_vulnerability_data_service_spec.rb'
|
||||
- 'ee/spec/services/ee/merge_requests/refresh_service_spec.rb'
|
||||
- 'ee/spec/services/ee/security/ingress_modsecurity_usage_service_spec.rb'
|
||||
- 'ee/spec/services/ee/users/create_service_spec.rb'
|
||||
- 'ee/spec/services/ee/users/destroy_service_spec.rb'
|
||||
- 'ee/spec/services/geo/container_repository_sync_service_spec.rb'
|
||||
- 'ee/spec/services/geo/design_repository_sync_service_spec.rb'
|
||||
- 'ee/spec/services/geo/framework_repository_sync_service_spec.rb'
|
||||
- 'ee/spec/services/geo/hashed_storage_migration_service_spec.rb'
|
||||
- 'ee/spec/services/geo/metrics_update_service_spec.rb'
|
||||
- 'ee/spec/services/geo/move_repository_service_spec.rb'
|
||||
- 'ee/spec/services/geo/project_housekeeping_service_spec.rb'
|
||||
- 'ee/spec/services/geo/rename_repository_service_spec.rb'
|
||||
- 'ee/spec/services/geo/repository_destroy_service_spec.rb'
|
||||
- 'ee/spec/services/geo/repository_sync_service_spec.rb'
|
||||
- 'ee/spec/services/geo/wiki_sync_service_spec.rb'
|
||||
- 'ee/spec/services/groups/destroy_service_spec.rb'
|
||||
- 'ee/spec/services/groups/update_service_spec.rb'
|
||||
- 'ee/spec/services/merge_trains/check_status_service_spec.rb'
|
||||
- 'ee/spec/services/network_policies/resources_service_spec.rb'
|
||||
- 'ee/spec/services/projects/destroy_service_spec.rb'
|
||||
- 'ee/spec/services/projects/group_links/destroy_service_spec.rb'
|
||||
- 'ee/spec/services/projects/update_service_spec.rb'
|
||||
- 'ee/spec/services/slash_commands/global_slack_handler_spec.rb'
|
||||
- 'ee/spec/support/helpers/ee/stub_configuration.rb'
|
||||
- 'ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/features/gold_trial_callout_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/lib/gitlab/geo/geo_logs_event_source_info_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/member_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/services/base_sync_service_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/services/geo/geo_request_service_shared_examples.rb'
|
||||
- 'ee/spec/workers/build_finished_worker_spec.rb'
|
||||
- 'ee/spec/workers/concerns/elastic/indexing_control_spec.rb'
|
||||
- 'ee/spec/workers/elastic_commit_indexer_worker_spec.rb'
|
||||
- 'ee/spec/workers/geo/design_repository_shard_sync_worker_spec.rb'
|
||||
- 'ee/spec/workers/geo/file_download_dispatch_worker_spec.rb'
|
||||
- 'ee/spec/workers/geo/registry_sync_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/project_cache_worker_spec.rb'
|
||||
- 'ee/spec/workers/repository_import_worker_spec.rb'
|
||||
- 'ee/spec/workers/vulnerability_exports/export_deletion_worker_spec.rb'
|
||||
- 'qa/spec/runtime/release_spec.rb'
|
||||
- 'spec/controllers/admin/sessions_controller_spec.rb'
|
||||
- 'spec/controllers/application_controller_spec.rb'
|
||||
- 'spec/controllers/concerns/issuable_actions_spec.rb'
|
||||
- 'spec/controllers/concerns/static_object_external_storage_spec.rb'
|
||||
- 'spec/controllers/explore/projects_controller_spec.rb'
|
||||
- 'spec/controllers/groups/clusters_controller_spec.rb'
|
||||
- 'spec/controllers/groups/settings/ci_cd_controller_spec.rb'
|
||||
- 'spec/controllers/groups_controller_spec.rb'
|
||||
- 'spec/controllers/import/bitbucket_controller_spec.rb'
|
||||
- 'spec/controllers/oauth/jira/authorizations_controller_spec.rb'
|
||||
- 'spec/controllers/omniauth_callbacks_controller_spec.rb'
|
||||
- 'spec/controllers/projects/artifacts_controller_spec.rb'
|
||||
- 'spec/controllers/projects/branches_controller_spec.rb'
|
||||
- 'spec/controllers/projects/clusters_controller_spec.rb'
|
||||
- 'spec/controllers/projects/commit_controller_spec.rb'
|
||||
- 'spec/controllers/projects/commits_controller_spec.rb'
|
||||
- 'spec/controllers/projects/environments_controller_spec.rb'
|
||||
- 'spec/controllers/projects/imports_controller_spec.rb'
|
||||
- 'spec/controllers/projects/issues_controller_spec.rb'
|
||||
- 'spec/controllers/projects/jobs_controller_spec.rb'
|
||||
- 'spec/controllers/projects/labels_controller_spec.rb'
|
||||
- 'spec/controllers/projects/merge_requests_controller_spec.rb'
|
||||
- 'spec/controllers/projects/pipelines_controller_spec.rb'
|
||||
- 'spec/controllers/projects/service_hook_logs_controller_spec.rb'
|
||||
- 'spec/controllers/projects/services_controller_spec.rb'
|
||||
- 'spec/controllers/projects/tags_controller_spec.rb'
|
||||
- 'spec/controllers/registrations/experience_levels_controller_spec.rb'
|
||||
- 'spec/controllers/registrations_controller_spec.rb'
|
||||
- 'spec/controllers/sessions_controller_spec.rb'
|
||||
- 'spec/controllers/snippets/notes_controller_spec.rb'
|
||||
- 'spec/controllers/snippets_controller_spec.rb'
|
||||
- 'spec/features/admin/admin_mode/login_spec.rb'
|
||||
- 'spec/features/groups/clusters/eks_spec.rb'
|
||||
- 'spec/features/groups/members/tabs_spec.rb'
|
||||
- 'spec/features/ide/static_object_external_storage_csp_spec.rb'
|
||||
- 'spec/features/issuables/issuable_list_spec.rb'
|
||||
- 'spec/features/issues/form_spec.rb'
|
||||
- 'spec/features/merge_request/user_creates_image_diff_notes_spec.rb'
|
||||
- 'spec/features/merge_request/user_reviews_image_spec.rb'
|
||||
- 'spec/features/merge_request/user_sees_diff_spec.rb'
|
||||
- 'spec/features/merge_request/user_sees_merge_widget_spec.rb'
|
||||
- 'spec/features/profiles/personal_access_tokens_spec.rb'
|
||||
- 'spec/features/projects/clusters/gcp_spec.rb'
|
||||
- 'spec/features/projects/clusters_spec.rb'
|
||||
- 'spec/features/projects/container_registry_spec.rb'
|
||||
- 'spec/features/projects/files/user_browses_lfs_files_spec.rb'
|
||||
- 'spec/features/projects/jobs_spec.rb'
|
||||
- 'spec/features/projects/navbar_spec.rb'
|
||||
- 'spec/features/projects/pages_spec.rb'
|
||||
- 'spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb'
|
||||
- 'spec/features/projects/settings/service_desk_setting_spec.rb'
|
||||
- 'spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb'
|
||||
- 'spec/features/snippets/embedded_snippet_spec.rb'
|
||||
- 'spec/features/usage_stats_consent_spec.rb'
|
||||
- 'spec/finders/prometheus_metrics_finder_spec.rb'
|
||||
- 'spec/graphql/mutations/alert_management/create_alert_issue_spec.rb'
|
||||
- 'spec/graphql/mutations/alert_management/http_integration/create_spec.rb'
|
||||
- 'spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb'
|
||||
- 'spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb'
|
||||
- 'spec/graphql/mutations/alert_management/http_integration/update_spec.rb'
|
||||
- 'spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb'
|
||||
- 'spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb'
|
||||
- 'spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb'
|
||||
- 'spec/helpers/analytics/unique_visits_helper_spec.rb'
|
||||
- 'spec/helpers/projects_helper_spec.rb'
|
||||
- 'spec/initializers/lograge_spec.rb'
|
||||
- 'spec/lib/api/entities/merge_request_basic_spec.rb'
|
||||
- 'spec/lib/api/entities/merge_request_changes_spec.rb'
|
||||
- 'spec/lib/api/helpers_spec.rb'
|
||||
- 'spec/lib/backup/files_spec.rb'
|
||||
- 'spec/lib/backup/manager_spec.rb'
|
||||
- 'spec/lib/banzai/commit_renderer_spec.rb'
|
||||
- 'spec/lib/banzai/filter/external_issue_reference_filter_spec.rb'
|
||||
- 'spec/lib/banzai/filter/issue_reference_filter_spec.rb'
|
||||
- 'spec/lib/banzai/filter/repository_link_filter_spec.rb'
|
||||
- 'spec/lib/banzai/pipeline/gfm_pipeline_spec.rb'
|
||||
- 'spec/lib/extracts_ref_spec.rb'
|
||||
- 'spec/lib/feature_spec.rb'
|
||||
- 'spec/lib/gitlab/app_logger_spec.rb'
|
||||
- 'spec/lib/gitlab/asciidoc_spec.rb'
|
||||
- 'spec/lib/gitlab/auth/auth_finders_spec.rb'
|
||||
- 'spec/lib/gitlab/auth/blocked_user_tracker_spec.rb'
|
||||
- 'spec/lib/gitlab/auth/request_authenticator_spec.rb'
|
||||
- 'spec/lib/gitlab/auth_spec.rb'
|
||||
- 'spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb'
|
||||
- 'spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb'
|
||||
- 'spec/lib/gitlab/checks/diff_check_spec.rb'
|
||||
- 'spec/lib/gitlab/checks/lfs_check_spec.rb'
|
||||
- 'spec/lib/gitlab/checks/lfs_integrity_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/config/external/file/base_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/config/external/file/local_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/config/external/processor_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/pipeline/chain/build_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/pipeline/chain/command_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/npm_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/trace_spec.rb'
|
||||
- 'spec/lib/gitlab/current_settings_spec.rb'
|
||||
- 'spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb'
|
||||
- 'spec/lib/gitlab/database/multi_threaded_migration_spec.rb'
|
||||
- 'spec/lib/gitlab/diff/highlight_cache_spec.rb'
|
||||
- 'spec/lib/gitlab/diff/highlight_spec.rb'
|
||||
- 'spec/lib/gitlab/diff/position_spec.rb'
|
||||
- 'spec/lib/gitlab/email/handler/create_issue_handler_spec.rb'
|
||||
- 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb'
|
||||
- 'spec/lib/gitlab/etag_caching/middleware_spec.rb'
|
||||
- 'spec/lib/gitlab/exclusive_lease_helpers_spec.rb'
|
||||
- 'spec/lib/gitlab/fogbugz_import/importer_spec.rb'
|
||||
- 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb'
|
||||
- 'spec/lib/gitlab/git/repository_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/blob_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/commit_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/health_check_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/operation_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/ref_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/remote_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/repository_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/wiki_service_spec.rb'
|
||||
- 'spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb'
|
||||
- 'spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb'
|
||||
- 'spec/lib/gitlab/hashed_storage/migrator_spec.rb'
|
||||
- 'spec/lib/gitlab/import/merge_request_helpers_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/config_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/importer_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/lfs_restorer_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/version_checker_spec.rb'
|
||||
- 'spec/lib/gitlab/job_waiter_spec.rb'
|
||||
- 'spec/lib/gitlab/legacy_github_import/importer_spec.rb'
|
||||
- 'spec/lib/gitlab/legacy_github_import/project_creator_spec.rb'
|
||||
- 'spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb'
|
||||
- 'spec/lib/gitlab/metrics/rack_middleware_spec.rb'
|
||||
- 'spec/lib/gitlab/metrics/subscribers/active_record_spec.rb'
|
||||
- 'spec/lib/gitlab/metrics_spec.rb'
|
||||
- 'spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb'
|
||||
- 'spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb'
|
||||
- 'spec/lib/gitlab/sidekiq_middleware_spec.rb'
|
||||
- 'spec/lib/gitlab/tracking/destinations/product_analytics_spec.rb'
|
||||
- 'spec/lib/gitlab/tracking/destinations/snowplow_spec.rb'
|
||||
- 'spec/lib/gitlab/tracking_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_spec.rb'
|
||||
- 'spec/lib/gitlab/workhorse_spec.rb'
|
||||
- 'spec/lib/gitlab/x509/commit_spec.rb'
|
||||
- 'spec/lib/gitlab/x509/signature_spec.rb'
|
||||
- 'spec/lib/google_api/cloud_platform/client_spec.rb'
|
||||
- 'spec/lib/json_web_token/rsa_token_spec.rb'
|
||||
- 'spec/lib/mattermost/command_spec.rb'
|
||||
- 'spec/lib/mattermost/team_spec.rb'
|
||||
- 'spec/lib/system_check/simple_executor_spec.rb'
|
||||
- 'spec/models/ci/build_spec.rb'
|
||||
- 'spec/models/ci/runner_spec.rb'
|
||||
- 'spec/models/commit_spec.rb'
|
||||
- 'spec/models/environment_spec.rb'
|
||||
- 'spec/models/group_spec.rb'
|
||||
- 'spec/models/hooks/service_hook_spec.rb'
|
||||
- 'spec/models/hooks/system_hook_spec.rb'
|
||||
- 'spec/models/hooks/web_hook_spec.rb'
|
||||
- 'spec/models/issue_spec.rb'
|
||||
- 'spec/models/key_spec.rb'
|
||||
- 'spec/models/member_spec.rb'
|
||||
- 'spec/models/merge_request_diff_spec.rb'
|
||||
- 'spec/models/merge_request_spec.rb'
|
||||
- 'spec/models/note_spec.rb'
|
||||
- 'spec/models/project_import_state_spec.rb'
|
||||
- 'spec/models/project_services/jira_service_spec.rb'
|
||||
- 'spec/models/project_services/mattermost_slash_commands_service_spec.rb'
|
||||
- 'spec/models/project_spec.rb'
|
||||
- 'spec/models/repository_spec.rb'
|
||||
- 'spec/models/user_spec.rb'
|
||||
- 'spec/models/x509_certificate_spec.rb'
|
||||
- 'spec/policies/ci/build_policy_spec.rb'
|
||||
- 'spec/policies/ci/pipeline_policy_spec.rb'
|
||||
- 'spec/presenters/gitlab/blame_presenter_spec.rb'
|
||||
- 'spec/presenters/merge_request_presenter_spec.rb'
|
||||
- 'spec/requests/api/api_spec.rb'
|
||||
- 'spec/requests/api/ci/runner/jobs_artifacts_spec.rb'
|
||||
- 'spec/requests/api/ci/runner/jobs_put_spec.rb'
|
||||
- 'spec/requests/api/ci/runner/jobs_request_post_spec.rb'
|
||||
- 'spec/requests/api/ci/runner/jobs_trace_spec.rb'
|
||||
- 'spec/requests/api/ci/runner/runners_delete_spec.rb'
|
||||
- 'spec/requests/api/ci/runner/runners_post_spec.rb'
|
||||
- 'spec/requests/api/ci/runner/runners_verify_post_spec.rb'
|
||||
- 'spec/requests/api/graphql/gitlab_schema_spec.rb'
|
||||
- 'spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb'
|
||||
- 'spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb'
|
||||
- 'spec/requests/api/graphql_spec.rb'
|
||||
- 'spec/requests/api/helpers_spec.rb'
|
||||
- 'spec/requests/api/internal/base_spec.rb'
|
||||
- 'spec/requests/api/maven_packages_spec.rb'
|
||||
- 'spec/requests/api/merge_requests_spec.rb'
|
||||
- 'spec/requests/api/pages/pages_spec.rb'
|
||||
- 'spec/requests/api/project_export_spec.rb'
|
||||
- 'spec/requests/api/project_import_spec.rb'
|
||||
- 'spec/requests/api/projects_spec.rb'
|
||||
- 'spec/requests/api/snippets_spec.rb'
|
||||
- 'spec/requests/api/todos_spec.rb'
|
||||
- 'spec/requests/git_http_spec.rb'
|
||||
- 'spec/requests/import/gitlab_projects_controller_spec.rb'
|
||||
- 'spec/routing/routing_spec.rb'
|
||||
- 'spec/serializers/analytics_stage_serializer_spec.rb'
|
||||
- 'spec/serializers/merge_request_poll_cached_widget_entity_spec.rb'
|
||||
- 'spec/serializers/merge_request_poll_widget_entity_spec.rb'
|
||||
- 'spec/services/application_settings/update_service_spec.rb'
|
||||
- 'spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb'
|
||||
- 'spec/services/boards/lists/update_service_spec.rb'
|
||||
- 'spec/services/ci/create_pipeline_service_spec.rb'
|
||||
- 'spec/services/ci/destroy_expired_job_artifacts_service_spec.rb'
|
||||
- 'spec/services/ci/expire_pipeline_cache_service_spec.rb'
|
||||
- 'spec/services/ci/list_config_variables_service_spec.rb'
|
||||
- 'spec/services/ci/register_job_service_spec.rb'
|
||||
- 'spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb'
|
||||
- 'spec/services/ci/retry_build_service_spec.rb'
|
||||
- 'spec/services/ci/retry_pipeline_service_spec.rb'
|
||||
- 'spec/services/ci/stop_environments_service_spec.rb'
|
||||
- 'spec/services/clusters/applications/create_service_spec.rb'
|
||||
- 'spec/services/clusters/cleanup/project_namespace_service_spec.rb'
|
||||
- 'spec/services/clusters/cleanup/service_account_service_spec.rb'
|
||||
- 'spec/services/deployments/older_deployments_drop_service_spec.rb'
|
||||
- 'spec/services/deployments/update_environment_service_spec.rb'
|
||||
- 'spec/services/draft_notes/destroy_service_spec.rb'
|
||||
- 'spec/services/events/render_service_spec.rb'
|
||||
- 'spec/services/git/branch_push_service_spec.rb'
|
||||
- 'spec/services/git/process_ref_changes_service_spec.rb'
|
||||
- 'spec/services/groups/create_service_spec.rb'
|
||||
- 'spec/services/groups/update_service_spec.rb'
|
||||
- 'spec/services/integrations/test/project_service_spec.rb'
|
||||
- 'spec/services/issuable/destroy_service_spec.rb'
|
||||
- 'spec/services/issues/close_service_spec.rb'
|
||||
- 'spec/services/issues/reopen_service_spec.rb'
|
||||
- 'spec/services/members/destroy_service_spec.rb'
|
||||
- 'spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb'
|
||||
- 'spec/services/merge_requests/build_service_spec.rb'
|
||||
- 'spec/services/merge_requests/merge_service_spec.rb'
|
||||
- 'spec/services/merge_requests/mergeability_check_service_spec.rb'
|
||||
- 'spec/services/merge_requests/refresh_service_spec.rb'
|
||||
- 'spec/services/merge_requests/reload_diffs_service_spec.rb'
|
||||
- 'spec/services/merge_requests/resolved_discussion_notification_service_spec.rb'
|
||||
- 'spec/services/metrics/dashboard/custom_dashboard_service_spec.rb'
|
||||
- 'spec/services/metrics/dashboard/transient_embed_service_spec.rb'
|
||||
- 'spec/services/notes/create_service_spec.rb'
|
||||
- 'spec/services/notes/render_service_spec.rb'
|
||||
- 'spec/services/packages/conan/create_package_file_service_spec.rb'
|
||||
- 'spec/services/packages/nuget/metadata_extraction_service_spec.rb'
|
||||
- 'spec/services/packages/nuget/update_package_from_metadata_service_spec.rb'
|
||||
- 'spec/services/pages/delete_services_spec.rb'
|
||||
- 'spec/services/pod_logs/elasticsearch_service_spec.rb'
|
||||
- 'spec/services/pod_logs/kubernetes_service_spec.rb'
|
||||
- 'spec/services/post_receive_service_spec.rb'
|
||||
- 'spec/services/projects/after_rename_service_spec.rb'
|
||||
- 'spec/services/projects/container_repository/cleanup_tags_service_spec.rb'
|
||||
- 'spec/services/projects/container_repository/delete_tags_service_spec.rb'
|
||||
- 'spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb'
|
||||
- 'spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb'
|
||||
- 'spec/services/projects/destroy_service_spec.rb'
|
||||
- 'spec/services/projects/fork_service_spec.rb'
|
||||
- 'spec/services/projects/import_service_spec.rb'
|
||||
- 'spec/services/projects/lfs_pointers/lfs_download_service_spec.rb'
|
||||
- 'spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb'
|
||||
- 'spec/services/projects/prometheus/alerts/notify_service_spec.rb'
|
||||
- 'spec/services/projects/transfer_service_spec.rb'
|
||||
- 'spec/services/projects/update_remote_mirror_service_spec.rb'
|
||||
- 'spec/services/projects/update_service_spec.rb'
|
||||
- 'spec/services/projects/update_statistics_service_spec.rb'
|
||||
- 'spec/services/resource_events/change_labels_service_spec.rb'
|
||||
- 'spec/services/search_service_spec.rb'
|
||||
- 'spec/services/snippets/create_service_spec.rb'
|
||||
- 'spec/services/test_hooks/project_service_spec.rb'
|
||||
- 'spec/services/test_hooks/system_service_spec.rb'
|
||||
- 'spec/services/todo_service_spec.rb'
|
||||
- 'spec/services/users/destroy_service_spec.rb'
|
||||
- 'spec/services/users/migrate_to_ghost_user_service_spec.rb'
|
||||
- 'spec/spec_helper.rb'
|
||||
- 'spec/support/capybara.rb'
|
||||
- 'spec/support/helpers/api_helpers.rb'
|
||||
- 'spec/support/helpers/graphql_helpers.rb'
|
||||
- 'spec/support/helpers/ldap_helpers.rb'
|
||||
- 'spec/support/helpers/login_helpers.rb'
|
||||
- 'spec/support/helpers/metrics_dashboard_url_helpers.rb'
|
||||
- 'spec/support/helpers/rake_helpers.rb'
|
||||
- 'spec/support/helpers/stub_configuration.rb'
|
||||
- 'spec/support/helpers/stub_gitlab_calls.rb'
|
||||
- 'spec/support/helpers/test_env.rb'
|
||||
- 'spec/support/import_export/common_util.rb'
|
||||
- 'spec/support/services/migrate_to_ghost_user_service_shared_examples.rb'
|
||||
- 'spec/support/shared_contexts/email_shared_context.rb'
|
||||
- 'spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb'
|
||||
- 'spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/controllers/issuables_requiring_filter_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/controllers/unique_visits_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/controllers/update_invalid_issuable_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/features/archive_download_buttons_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/features/snippets_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/models/mentionable_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/models/with_uploads_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/path_extraction_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/api/discussions_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/api/snippets_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/rack_attack_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/snippet_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/services/alert_management_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/services/boards/issues_list_service_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/services/issuable_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/uploaders/object_storage_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/workers/authorized_projects_worker_shared_example.rb'
|
||||
- 'spec/support/shared_examples/workers/reactive_cacheable_shared_examples.rb'
|
||||
- 'spec/support/snowplow.rb'
|
||||
- 'spec/support/unicorn.rb'
|
||||
- 'spec/tasks/gitlab/cleanup_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/container_registry_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/db_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/git_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/praefect_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/shell_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/x509/update_rake_spec.rb'
|
||||
- 'spec/uploaders/file_mover_spec.rb'
|
||||
- 'spec/uploaders/records_uploads_spec.rb'
|
||||
- 'spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb'
|
||||
- 'spec/views/layouts/_head.html.haml_spec.rb'
|
||||
- 'spec/views/projects/artifacts/_artifact.html.haml_spec.rb'
|
||||
- 'spec/views/shared/runners/show.html.haml_spec.rb'
|
||||
- 'spec/workers/archive_trace_worker_spec.rb'
|
||||
- 'spec/workers/build_coverage_worker_spec.rb'
|
||||
- 'spec/workers/build_hooks_worker_spec.rb'
|
||||
- 'spec/workers/build_trace_sections_worker_spec.rb'
|
||||
- 'spec/workers/ci/build_schedule_worker_spec.rb'
|
||||
- 'spec/workers/ci/daily_build_group_report_results_worker_spec.rb'
|
||||
- 'spec/workers/cluster_configure_istio_worker_spec.rb'
|
||||
- 'spec/workers/cluster_provision_worker_spec.rb'
|
||||
- 'spec/workers/clusters/cleanup/project_namespace_worker_spec.rb'
|
||||
- 'spec/workers/clusters/cleanup/service_account_worker_spec.rb'
|
||||
- 'spec/workers/concerns/project_import_options_spec.rb'
|
||||
- 'spec/workers/create_commit_signature_worker_spec.rb'
|
||||
- 'spec/workers/create_note_diff_file_worker_spec.rb'
|
||||
- 'spec/workers/delete_diff_files_worker_spec.rb'
|
||||
- 'spec/workers/email_receiver_worker_spec.rb'
|
||||
- 'spec/workers/emails_on_push_worker_spec.rb'
|
||||
- 'spec/workers/error_tracking_issue_link_worker_spec.rb'
|
||||
- 'spec/workers/expire_pipeline_cache_worker_spec.rb'
|
||||
- 'spec/workers/git_garbage_collect_worker_spec.rb'
|
||||
- 'spec/workers/group_export_worker_spec.rb'
|
||||
- 'spec/workers/group_import_worker_spec.rb'
|
||||
- 'spec/workers/namespaceless_project_destroy_worker_spec.rb'
|
||||
- 'spec/workers/namespaces/root_statistics_worker_spec.rb'
|
||||
- 'spec/workers/new_note_worker_spec.rb'
|
||||
- 'spec/workers/object_pool/create_worker_spec.rb'
|
||||
- 'spec/workers/packages/nuget/extraction_worker_spec.rb'
|
||||
- 'spec/workers/pages_remove_worker_spec.rb'
|
||||
- 'spec/workers/pipeline_hooks_worker_spec.rb'
|
||||
- 'spec/workers/pipeline_process_worker_spec.rb'
|
||||
- 'spec/workers/pipeline_schedule_worker_spec.rb'
|
||||
- 'spec/workers/project_cache_worker_spec.rb'
|
||||
- 'spec/workers/stage_update_worker_spec.rb'
|
||||
- 'spec/workers/stuck_ci_jobs_worker_spec.rb'
|
||||
- 'spec/workers/wait_for_cluster_creation_worker_spec.rb'
|
||||
- 'ee/spec/workers/security/auto_fix_worker_spec.rb'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import bulkImportSourceGroupsQuery from '../graphql/queries/bulk_import_source_groups.query.graphql';
|
||||
import availableNamespacesQuery from '../graphql/queries/available_namespaces.query.graphql';
|
||||
import setTargetNamespaceMutation from '../graphql/mutations/set_target_namespace.mutation.graphql';
|
||||
import setNewNameMutation from '../graphql/mutations/set_new_name.mutation.graphql';
|
||||
import importGroupMutation from '../graphql/mutations/import_group.mutation.graphql';
|
||||
import ImportTableRow from './import_table_row.vue';
|
||||
|
||||
const mapApolloMutations = mutations =>
|
||||
Object.fromEntries(
|
||||
Object.entries(mutations).map(([key, mutation]) => [
|
||||
key,
|
||||
function mutate(config) {
|
||||
return this.$apollo.mutate({
|
||||
mutation,
|
||||
...config,
|
||||
});
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
ImportTableRow,
|
||||
},
|
||||
|
||||
apollo: {
|
||||
bulkImportSourceGroups: bulkImportSourceGroupsQuery,
|
||||
availableNamespaces: availableNamespacesQuery,
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapApolloMutations({
|
||||
setTargetNamespace: setTargetNamespaceMutation,
|
||||
setNewName: setNewNameMutation,
|
||||
importGroup: importGroupMutation,
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<gl-loading-icon v-if="$apollo.loading" size="md" class="gl-mt-5" />
|
||||
<div v-else-if="bulkImportSourceGroups.length">
|
||||
<table class="gl-w-full">
|
||||
<thead class="gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1">
|
||||
<th class="gl-py-4 import-jobs-from-col">{{ s__('BulkImport|From source group') }}</th>
|
||||
<th class="gl-py-4 import-jobs-to-col">{{ s__('BulkImport|To new group') }}</th>
|
||||
<th class="gl-py-4 import-jobs-status-col">{{ __('Status') }}</th>
|
||||
<th class="gl-py-4 import-jobs-cta-col"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="group in bulkImportSourceGroups">
|
||||
<import-table-row
|
||||
:key="group.id"
|
||||
:group="group"
|
||||
:available-namespaces="availableNamespaces"
|
||||
@update-target-namespace="
|
||||
setTargetNamespace({
|
||||
variables: { sourceGroupId: group.id, targetNamespace: $event },
|
||||
})
|
||||
"
|
||||
@update-new-name="
|
||||
setNewName({
|
||||
variables: { sourceGroupId: group.id, newName: $event },
|
||||
})
|
||||
"
|
||||
@import-group="importGroup({ variables: { sourceGroupId: group.id } })"
|
||||
/>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<script>
|
||||
import { GlButton, GlIcon, GlLink, GlFormInput } from '@gitlab/ui';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import Select2Select from '~/vue_shared/components/select2_select.vue';
|
||||
import ImportStatus from '../../components/import_status.vue';
|
||||
import { STATUSES } from '../../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Select2Select,
|
||||
ImportStatus,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlIcon,
|
||||
GlFormInput,
|
||||
},
|
||||
props: {
|
||||
group: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
availableNamespaces: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isDisabled() {
|
||||
return this.group.status !== STATUSES.NONE;
|
||||
},
|
||||
|
||||
isFinished() {
|
||||
return this.group.status === STATUSES.FINISHED;
|
||||
},
|
||||
|
||||
select2Options() {
|
||||
return {
|
||||
data: this.availableNamespaces.map(namespace => ({
|
||||
id: namespace.full_path,
|
||||
text: namespace.full_path,
|
||||
})),
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPath(group) {
|
||||
return `${group.import_target.target_namespace}/${group.import_target.new_name}`;
|
||||
},
|
||||
|
||||
getFullPath(group) {
|
||||
return joinPaths(gon.relative_url_root || '/', this.getPath(group));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr class="gl-border-gray-200 gl-border-0 gl-border-b-1">
|
||||
<td class="gl-p-4">
|
||||
<gl-link :href="group.web_url" target="_blank">
|
||||
{{ group.full_path }} <gl-icon name="external-link" />
|
||||
</gl-link>
|
||||
</td>
|
||||
<td class="gl-p-4">
|
||||
<gl-link v-if="isFinished" :href="getFullPath(group)">{{ getPath(group) }}</gl-link>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="import-entities-target-select gl-display-flex gl-align-items-stretch"
|
||||
:class="{
|
||||
disabled: isDisabled,
|
||||
}"
|
||||
>
|
||||
<select2-select
|
||||
:disabled="isDisabled"
|
||||
:options="select2Options"
|
||||
:value="group.import_target.target_namespace"
|
||||
@input="$emit('update-target-namespace', $event)"
|
||||
/>
|
||||
<div
|
||||
class="import-entities-target-select-separator gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1"
|
||||
>
|
||||
/
|
||||
</div>
|
||||
<gl-form-input
|
||||
class="gl-rounded-top-left-none gl-rounded-bottom-left-none"
|
||||
:disabled="isDisabled"
|
||||
:value="group.import_target.new_name"
|
||||
@input="$emit('update-new-name', $event)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="gl-p-4 gl-white-space-nowrap">
|
||||
<import-status :status="group.status" />
|
||||
</td>
|
||||
<td class="gl-p-4">
|
||||
<gl-button
|
||||
v-if="!isDisabled"
|
||||
variant="success"
|
||||
category="secondary"
|
||||
@click="$emit('import-group')"
|
||||
>{{ __('Import') }}</gl-button
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import { STATUSES } from '../../constants';
|
||||
import availableNamespacesQuery from './queries/available_namespaces.query.graphql';
|
||||
import { SourceGroupsManager } from './services/source_groups_manager';
|
||||
|
||||
export const clientTypenames = {
|
||||
BulkImportSourceGroup: 'ClientBulkImportSourceGroup',
|
||||
AvailableNamespace: 'ClientAvailableNamespace',
|
||||
};
|
||||
|
||||
export function createResolvers({ endpoints }) {
|
||||
return {
|
||||
Query: {
|
||||
async bulkImportSourceGroups(_, __, { client }) {
|
||||
const {
|
||||
data: { availableNamespaces },
|
||||
} = await client.query({ query: availableNamespacesQuery });
|
||||
|
||||
return axios.get(endpoints.status).then(({ data }) => {
|
||||
return data.importable_data.map(group => ({
|
||||
__typename: clientTypenames.BulkImportSourceGroup,
|
||||
...group,
|
||||
status: STATUSES.NONE,
|
||||
import_target: {
|
||||
new_name: group.full_path,
|
||||
target_namespace: availableNamespaces[0].full_path,
|
||||
},
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
availableNamespaces: () =>
|
||||
axios.get(endpoints.availableNamespaces).then(({ data }) =>
|
||||
data.map(namespace => ({
|
||||
__typename: clientTypenames.AvailableNamespace,
|
||||
...namespace,
|
||||
})),
|
||||
),
|
||||
},
|
||||
Mutation: {
|
||||
setTargetNamespace(_, { targetNamespace, sourceGroupId }, { client }) {
|
||||
new SourceGroupsManager({ client }).updateById(sourceGroupId, sourceGroup => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sourceGroup.import_target.target_namespace = targetNamespace;
|
||||
});
|
||||
},
|
||||
|
||||
setNewName(_, { newName, sourceGroupId }, { client }) {
|
||||
new SourceGroupsManager({ client }).updateById(sourceGroupId, sourceGroup => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sourceGroup.import_target.new_name = newName;
|
||||
});
|
||||
},
|
||||
|
||||
async importGroup(_, { sourceGroupId }, { client }) {
|
||||
const groupManager = new SourceGroupsManager({ client });
|
||||
const group = groupManager.findById(sourceGroupId);
|
||||
groupManager.setImportStatus(group, STATUSES.SCHEDULING);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const createApolloClient = ({ endpoints }) =>
|
||||
createDefaultClient(createResolvers({ endpoints }), { assumeImmutableResults: true });
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fragment BulkImportSourceGroupItem on ClientBulkImportSourceGroup {
|
||||
id
|
||||
web_url
|
||||
full_path
|
||||
full_name
|
||||
status
|
||||
import_target
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
mutation importGroup($sourceGroupId: String!) {
|
||||
importGroup(sourceGroupId: $sourceGroupId) @client
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
mutation setNewName($newName: String!, $sourceGroupId: String!) {
|
||||
setNewName(newName: $newName, sourceGroupId: $sourceGroupId) @client
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
mutation setTargetNamespace($targetNamespace: String!, $sourceGroupId: String!) {
|
||||
setTargetNamespace(targetNamespace: $targetNamespace, sourceGroupId: $sourceGroupId) @client
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
query availableNamespaces {
|
||||
availableNamespaces @client {
|
||||
id
|
||||
full_path
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#import "../fragments/bulk_import_source_group_item.fragment.graphql"
|
||||
|
||||
query bulkImportSourceGroups {
|
||||
bulkImportSourceGroups @client {
|
||||
...BulkImportSourceGroupItem
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
|
||||
import produce from 'immer';
|
||||
import ImportSourceGroupFragment from '../fragments/bulk_import_source_group_item.fragment.graphql';
|
||||
|
||||
function extractTypeConditionFromFragment(fragment) {
|
||||
return fragment.definitions[0]?.typeCondition.name.value;
|
||||
}
|
||||
|
||||
function generateGroupId(id) {
|
||||
return defaultDataIdFromObject({
|
||||
__typename: extractTypeConditionFromFragment(ImportSourceGroupFragment),
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
export class SourceGroupsManager {
|
||||
constructor({ client }) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
findById(id) {
|
||||
const cacheId = generateGroupId(id);
|
||||
return this.client.readFragment({ fragment: ImportSourceGroupFragment, id: cacheId });
|
||||
}
|
||||
|
||||
update(group, fn) {
|
||||
this.client.writeFragment({
|
||||
fragment: ImportSourceGroupFragment,
|
||||
id: generateGroupId(group.id),
|
||||
data: produce(group, fn),
|
||||
});
|
||||
}
|
||||
|
||||
updateById(id, fn) {
|
||||
const group = this.findById(id);
|
||||
this.update(group, fn);
|
||||
}
|
||||
|
||||
setImportStatus(group, status) {
|
||||
this.update(group, sourceGroup => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sourceGroup.status = status;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import { createApolloClient } from './graphql/client_factory';
|
||||
import ImportTable from './components/import_table.vue';
|
||||
|
||||
Vue.use(Translate);
|
||||
Vue.use(VueApollo);
|
||||
|
||||
export function mountImportGroupsApp(mountElement) {
|
||||
if (!mountElement) return undefined;
|
||||
|
||||
const { statusPath, availableNamespacesPath, createBulkImportPath } = mountElement.dataset;
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createApolloClient({
|
||||
endpoints: {
|
||||
status: statusPath,
|
||||
availableNamespaces: availableNamespacesPath,
|
||||
createBulkImport: createBulkImportPath,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return new Vue({
|
||||
el: mountElement,
|
||||
apolloProvider,
|
||||
render(createElement) {
|
||||
return createElement(ImportTable);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { mountImportGroupsApp } from '~/import_entities/import_groups';
|
||||
|
||||
const mountElement = document.getElementById('import-groups-mount-element');
|
||||
mountImportGroupsApp(mountElement);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import dateFormat from 'dateformat';
|
||||
import { GlColumnChart } from '@gitlab/ui/dist/charts';
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import { GlAlert, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { getDateInPast } from '~/lib/utils/datetime_utility';
|
||||
import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql';
|
||||
|
|
@ -50,6 +50,7 @@ export default {
|
|||
components: {
|
||||
GlAlert,
|
||||
GlColumnChart,
|
||||
GlSkeletonLoader,
|
||||
StatisticsList,
|
||||
PipelinesAreaChart,
|
||||
},
|
||||
|
|
@ -278,7 +279,8 @@ export default {
|
|||
<h4 class="gl-my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<statistics-list :counts="formattedCounts" />
|
||||
<gl-skeleton-loader v-if="$apollo.queries.counts.loading" :lines="5" />
|
||||
<statistics-list v-else :counts="formattedCounts" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>
|
||||
|
|
|
|||
|
|
@ -43,9 +43,33 @@
|
|||
}
|
||||
|
||||
.import-entities-target-select {
|
||||
&.disabled {
|
||||
.import-entities-target-select-separator,
|
||||
.select2-container.select2-container-disabled .select2-choice {
|
||||
color: var(--gray-400, $gray-400);
|
||||
border-color: var(--gray-100, $gray-100);
|
||||
background-color: var(--gray-10, $gray-10);
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice .select2-arrow {
|
||||
background-color: var(--gray-10, $gray-10);
|
||||
}
|
||||
}
|
||||
|
||||
.import-entities-target-select-separator {
|
||||
border-color: var(--gray-200, $gray-200);
|
||||
background-color: var(--gray-10, $gray-10);
|
||||
}
|
||||
|
||||
.select2-container {
|
||||
> .select2-choice {
|
||||
.select2-arrow {
|
||||
background-color: var(--white, $white);
|
||||
}
|
||||
|
||||
border-color: var(--gray-200, $gray-200);
|
||||
color: var(--gray-900, $gray-900) !important;
|
||||
background-color: var(--white, $white) !important;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,6 +186,10 @@ module WikiActions
|
|||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
def git_access
|
||||
render 'shared/wikis/git_access'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def container
|
||||
|
|
|
|||
|
|
@ -6,7 +6,4 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
alias_method :container, :project
|
||||
|
||||
feature_category :wiki
|
||||
|
||||
def git_access
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ module ApplicationSettingsHelper
|
|||
:password_authentication_enabled_for_git,
|
||||
:performance_bar_allowed_group_path,
|
||||
:performance_bar_enabled,
|
||||
:personal_access_token_prefix,
|
||||
:kroki_enabled,
|
||||
:kroki_url,
|
||||
:plantuml_enabled,
|
||||
|
|
|
|||
|
|
@ -249,6 +249,12 @@ class ApplicationSetting < ApplicationRecord
|
|||
|
||||
validates :user_default_internal_regex, js_regex: true, allow_nil: true
|
||||
|
||||
validates :personal_access_token_prefix,
|
||||
format: { with: /\A[a-zA-Z0-9_+=\/@:.-]+\z/,
|
||||
message: _("can contain only letters of the Base64 alphabet (RFC4648) with the addition of '@', ':' and '.'") },
|
||||
length: { maximum: 20, message: _('is too long (maximum is %{count} characters)') },
|
||||
allow_blank: true
|
||||
|
||||
validates :commit_email_hostname, format: { with: /\A[^@]+\z/ }
|
||||
|
||||
validates :archive_builds_in_seconds,
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ module ApplicationSettingImplementation
|
|||
password_authentication_enabled_for_git: true,
|
||||
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
|
||||
performance_bar_allowed_group_id: nil,
|
||||
personal_access_token_prefix: nil,
|
||||
plantuml_enabled: false,
|
||||
plantuml_url: nil,
|
||||
polling_interval_multiplier: 1,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ module Ci
|
|||
|
||||
accepts_nested_attributes_for :variables, reject_if: :persisted?
|
||||
|
||||
delegate :id, to: :project, prefix: true
|
||||
delegate :full_path, to: :project, prefix: true
|
||||
|
||||
validates :sha, presence: { unless: :importing? }
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ module CaseSensitivity
|
|||
def iwhere(params)
|
||||
criteria = self
|
||||
|
||||
params.each do |key, value|
|
||||
params.each do |column, value|
|
||||
column = arel_table[column] unless column.is_a?(Arel::Attribute)
|
||||
|
||||
criteria = case value
|
||||
when Array
|
||||
criteria.where(value_in(key, value))
|
||||
criteria.where(value_in(column, value))
|
||||
else
|
||||
criteria.where(value_equal(key, value))
|
||||
criteria.where(value_equal(column, value))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ module CaseSensitivity
|
|||
def value_equal(column, value)
|
||||
lower_value = lower_value(value)
|
||||
|
||||
lower_column(arel_table[column]).eq(lower_value).to_sql
|
||||
lower_column(column).eq(lower_value).to_sql
|
||||
end
|
||||
|
||||
def value_in(column, values)
|
||||
|
|
@ -36,7 +38,7 @@ module CaseSensitivity
|
|||
lower_value(value)
|
||||
end
|
||||
|
||||
lower_column(arel_table[column]).in(lower_values).to_sql
|
||||
lower_column(column).in(lower_values).to_sql
|
||||
end
|
||||
|
||||
def lower_value(value)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,36 @@
|
|||
# Object must have name and path db fields and respond to parent and parent_changed? methods.
|
||||
module Routable
|
||||
extend ActiveSupport::Concern
|
||||
include CaseSensitivity
|
||||
|
||||
# Finds a Routable object by its full path, without knowing the class.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# Routable.find_by_full_path('groupname') # -> Group
|
||||
# Routable.find_by_full_path('groupname/projectname') # -> Project
|
||||
#
|
||||
# Returns a single object, or nil.
|
||||
def self.find_by_full_path(path, follow_redirects: false, route_scope: Route, redirect_route_scope: RedirectRoute)
|
||||
return unless path.present?
|
||||
|
||||
# Case sensitive match first (it's cheaper and the usual case)
|
||||
# If we didn't have an exact match, we perform a case insensitive search
|
||||
#
|
||||
# We need to qualify the columns with the table name, to support both direct lookups on
|
||||
# Route/RedirectRoute, and scoped lookups through the Routable classes.
|
||||
route =
|
||||
route_scope.find_by(routes: { path: path }) ||
|
||||
route_scope.iwhere(Route.arel_table[:path] => path).take
|
||||
|
||||
if follow_redirects
|
||||
route ||= redirect_route_scope.iwhere(RedirectRoute.arel_table[:path] => path).take
|
||||
end
|
||||
|
||||
return unless route
|
||||
|
||||
route.is_a?(Routable) ? route : route.source
|
||||
end
|
||||
|
||||
included do
|
||||
# Remove `inverse_of: source` when upgraded to rails 5.2
|
||||
|
|
@ -30,15 +60,14 @@ module Routable
|
|||
#
|
||||
# Returns a single object, or nil.
|
||||
def find_by_full_path(path, follow_redirects: false)
|
||||
# Case sensitive match first (it's cheaper and the usual case)
|
||||
# If we didn't have an exact match, we perform a case insensitive search
|
||||
found = includes(:route).find_by(routes: { path: path }) || where_full_path_in([path]).take
|
||||
|
||||
return found if found
|
||||
|
||||
if follow_redirects
|
||||
joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path)
|
||||
end
|
||||
# TODO: Optimize these queries by avoiding joins
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/292252
|
||||
Routable.find_by_full_path(
|
||||
path,
|
||||
follow_redirects: follow_redirects,
|
||||
route_scope: includes(:route).references(:routes),
|
||||
redirect_route_scope: joins(:redirect_routes)
|
||||
)
|
||||
end
|
||||
|
||||
# Builds a relation to find multiple objects by their full paths.
|
||||
|
|
|
|||
|
|
@ -57,6 +57,13 @@ module TokenAuthenticatable
|
|||
token = read_attribute(token_field)
|
||||
token.present? && ActiveSupport::SecurityUtils.secure_compare(other_token, token)
|
||||
end
|
||||
|
||||
# Base strategy delegates to this method for formatting a token before
|
||||
# calling set_token. Can be overridden in models to e.g. add a prefix
|
||||
# to the tokens
|
||||
mod.define_method("format_#{token_field}") do |token|
|
||||
token
|
||||
end
|
||||
end
|
||||
|
||||
def token_authenticatable_module
|
||||
|
|
|
|||
|
|
@ -18,10 +18,15 @@ module TokenAuthenticatableStrategies
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def set_token(instance)
|
||||
def set_token(instance, token)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Default implementation returns the token as-is
|
||||
def format_token(instance, token)
|
||||
instance.send("format_#{@token_field}", token) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
def ensure_token(instance)
|
||||
write_new_token(instance) unless token_set?(instance)
|
||||
get_token(instance)
|
||||
|
|
@ -57,7 +62,8 @@ module TokenAuthenticatableStrategies
|
|||
|
||||
def write_new_token(instance)
|
||||
new_token = generate_available_token
|
||||
set_token(instance, new_token)
|
||||
formatted_token = format_token(instance, new_token)
|
||||
set_token(instance, formatted_token)
|
||||
end
|
||||
|
||||
def unique
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ class PersonalAccessToken < ApplicationRecord
|
|||
add_authentication_token_field :token, digest: true
|
||||
|
||||
REDIS_EXPIRY_TIME = 3.minutes
|
||||
TOKEN_LENGTH = 20
|
||||
|
||||
# PATs are 20 characters + optional configurable settings prefix (0..20)
|
||||
TOKEN_LENGTH_RANGE = (20..40).freeze
|
||||
|
||||
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
|
|
@ -77,6 +79,15 @@ class PersonalAccessToken < ApplicationRecord
|
|||
)
|
||||
end
|
||||
|
||||
def self.token_prefix
|
||||
Gitlab::CurrentSettings.current_application_settings.personal_access_token_prefix
|
||||
end
|
||||
|
||||
override :format_token
|
||||
def format_token(token)
|
||||
"#{self.class.token_prefix}#{token}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def validate_scopes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RedirectRoute < ApplicationRecord
|
||||
include CaseSensitivity
|
||||
|
||||
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
|
||||
validates :source, presence: true
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ class Route < ApplicationRecord
|
|||
include CaseSensitivity
|
||||
include Gitlab::SQL::Pattern
|
||||
|
||||
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
belongs_to :source, polymorphic: true, inverse_of: :route # rubocop:disable Cop/PolymorphicAssociations
|
||||
validates :source, presence: true
|
||||
|
||||
validates :path,
|
||||
|
|
|
|||
|
|
@ -1665,7 +1665,7 @@ class User < ApplicationRecord
|
|||
save
|
||||
end
|
||||
|
||||
# each existing user needs to have an `feed_token`.
|
||||
# each existing user needs to have a `feed_token`.
|
||||
# we do this on read since migrating all existing users is not a feasible
|
||||
# solution.
|
||||
def feed_token
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@
|
|||
= _('Specify an e-mail address regex pattern to identify default internal users.')
|
||||
= link_to _('More information'), help_page_path('user/permissions', anchor: 'setting-new-users-to-external'),
|
||||
target: '_blank'
|
||||
.form-group
|
||||
= f.label :personal_access_token_prefix, _('Personal Access Token prefix'), class: 'label-light'
|
||||
= f.text_field :personal_access_token_prefix, placeholder: _('Max 20 characters'), class: 'form-control'
|
||||
.form-group
|
||||
= f.label :user_show_add_ssh_key_message, _('Prompt users to upload SSH keys'), class: 'label-bold'
|
||||
.form-check
|
||||
|
|
|
|||
|
|
@ -6,3 +6,7 @@
|
|||
= s_('ImportGroups|Import groups from GitLab')
|
||||
%p.gl-my-0.gl-py-5.gl-border-solid.gl-border-gray-200.gl-border-0.gl-border-b-1
|
||||
= s_('ImportGroups|Importing groups from %{link}').html_safe % { link: external_link(@source_url, @source_url) }
|
||||
|
||||
#import-groups-mount-element{ data: { status_path: status_import_bulk_imports_path(format: :json),
|
||||
available_namespaces_path: import_available_namespaces_path(format: :json),
|
||||
create_bulk_import_path: import_bulk_imports_path(format: :json) } }
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@
|
|||
%a.gutter-toggle.float-right.d-block.d-md-none.js-sidebar-wiki-toggle{ href: "#" }
|
||||
= sprite_icon('chevron-double-lg-right', css_class: 'gl-icon')
|
||||
|
||||
- if @wiki.container.is_a?(Project)
|
||||
- git_access_url = wiki_path(@wiki, action: :git_access)
|
||||
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '', data: { qa_selector: 'clone_repository_link' } do
|
||||
= sprite_icon('download', css_class: 'gl-mr-2')
|
||||
%span= _("Clone repository")
|
||||
- git_access_url = wiki_path(@wiki, action: :git_access)
|
||||
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '', data: { qa_selector: 'clone_repository_link' } do
|
||||
= sprite_icon('download', css_class: 'gl-mr-2')
|
||||
%span= _("Clone repository")
|
||||
|
||||
- if @sidebar_error.present?
|
||||
= render 'shared/alert_info', body: s_('Wiki|The sidebar failed to load. You can reload the page to try again.')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support Git access for group wikis
|
||||
merge_request: 45892
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change the unique index on `security_findings` table
|
||||
merge_request: 50046
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Configurable personal access token prefix
|
||||
merge_request: 20968
|
||||
author: 'Max Wittig & Diego Louzán'
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update projects_imported.total usage metric
|
||||
merge_request: 49568
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ci_allow_failure_with_exit_codes
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49145
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292024
|
||||
milestone: '13.7'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
||||
|
|
@ -9,7 +9,7 @@ scope(path: '*repository_path', format: false) do
|
|||
end
|
||||
|
||||
# NOTE: LFS routes are exposed on all repository types, but we still check for
|
||||
# LFS availability on the repository container in LfsRequest#require_lfs_enabled!
|
||||
# LFS availability on the repository container in LfsRequest#lfs_check_access!
|
||||
|
||||
# Git LFS API (metadata)
|
||||
scope(path: 'info/lfs/objects', controller: :lfs_api) do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPersonalAccessTokenPrefixToApplicationSetting < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
# rubocop:disable Migration/AddLimitToTextColumns
|
||||
# limit is added in 20201119133604_add_text_limit_to_application_setting_personal_access_token_prefix
|
||||
def change
|
||||
add_column :application_settings, :personal_access_token_prefix, :text
|
||||
end
|
||||
# rubocop:enable Migration/AddLimitToTextColumns
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTextLimitToApplicationSettingPersonalAccessTokenPrefix < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_text_limit :application_settings, :personal_access_token_prefix, 20
|
||||
end
|
||||
|
||||
def down
|
||||
remove_text_limit :application_settings, :personal_access_token_prefix
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexProjectsOnImportTypeAndCreatorId < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :projects, [:creator_id, :import_type, :created_at],
|
||||
where: 'import_type IS NOT NULL',
|
||||
name: 'index_projects_on_creator_id_import_type_and_created_at_partial'
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :projects, 'index_projects_on_creator_id_import_type_and_created_at_partial'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeUniqueIndexOnSecurityFindings < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
OLD_INDEX_NAME = 'index_security_findings_on_uuid'
|
||||
NEW_INDEX_NAME = 'index_security_findings_on_uuid_and_scan_id'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
class SecurityFinding < ActiveRecord::Base
|
||||
include EachBatch
|
||||
|
||||
self.table_name = 'security_findings'
|
||||
end
|
||||
|
||||
def up
|
||||
add_concurrent_index :security_findings, [:uuid, :scan_id], unique: true, name: NEW_INDEX_NAME
|
||||
|
||||
remove_concurrent_index_by_name :security_findings, OLD_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
# It is very unlikely that we rollback this migration but just in case if we have to,
|
||||
# we have to clear the table because there can be multiple records with the same UUID
|
||||
# which would break the creation of unique index on the `uuid` column.
|
||||
# We choose clearing the table because just removing the duplicated records would
|
||||
# cause data inconsistencies.
|
||||
SecurityFinding.each_batch(of: 10000) { |relation| relation.delete_all }
|
||||
|
||||
add_concurrent_index :security_findings, :uuid, unique: true, name: OLD_INDEX_NAME
|
||||
|
||||
remove_concurrent_index_by_name :security_findings, NEW_INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
6c8fc7904f50a792e10b5f1b0abe90ba21b1bdfd47430b3caa0df870c0a24079
|
||||
|
|
@ -0,0 +1 @@
|
|||
bfb8ac3b697675bd4fca53273c6c6feb2f7a5659cbdaf57b9b4adb3e189b74ad
|
||||
|
|
@ -0,0 +1 @@
|
|||
734ef1c319549df72bbbfe3acf93ca05f7a6c5547a1efdcaba780195181f5f9a
|
||||
|
|
@ -0,0 +1 @@
|
|||
916f29e6ab89551fd785c3a8584c24b72d9002ada30d159e9ff826cb247199b5
|
||||
|
|
@ -9372,11 +9372,13 @@ CREATE TABLE application_settings (
|
|||
secret_detection_revocation_token_types_url text,
|
||||
cloud_license_enabled boolean DEFAULT false NOT NULL,
|
||||
disable_feed_token boolean DEFAULT false NOT NULL,
|
||||
personal_access_token_prefix text,
|
||||
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
|
||||
CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)),
|
||||
CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)),
|
||||
CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 255)),
|
||||
CONSTRAINT check_57123c9593 CHECK ((char_length(help_page_documentation_base_url) <= 255)),
|
||||
CONSTRAINT check_718b4458ae CHECK ((char_length(personal_access_token_prefix) <= 20)),
|
||||
CONSTRAINT check_85a39b68ff CHECK ((char_length(encrypted_ci_jwt_signing_key_iv) <= 255)),
|
||||
CONSTRAINT check_9a719834eb CHECK ((char_length(secret_detection_token_revocation_url) <= 255)),
|
||||
CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)),
|
||||
|
|
@ -22275,6 +22277,8 @@ CREATE INDEX index_projects_on_creator_id_and_created_at_and_id ON projects USIN
|
|||
|
||||
CREATE INDEX index_projects_on_creator_id_and_id ON projects USING btree (creator_id, id);
|
||||
|
||||
CREATE INDEX index_projects_on_creator_id_import_type_and_created_at_partial ON projects USING btree (creator_id, import_type, created_at) WHERE (import_type IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_projects_on_description_trigram ON projects USING gin (description gin_trgm_ops);
|
||||
|
||||
CREATE INDEX index_projects_on_id_and_archived_and_pending_delete ON projects USING btree (id) WHERE ((archived = false) AND (pending_delete = false));
|
||||
|
|
@ -22527,7 +22531,7 @@ CREATE INDEX index_security_findings_on_scanner_id ON security_findings USING bt
|
|||
|
||||
CREATE INDEX index_security_findings_on_severity ON security_findings USING btree (severity);
|
||||
|
||||
CREATE UNIQUE INDEX index_security_findings_on_uuid ON security_findings USING btree (uuid);
|
||||
CREATE UNIQUE INDEX index_security_findings_on_uuid_and_scan_id ON security_findings USING btree (uuid, scan_id);
|
||||
|
||||
CREATE INDEX index_self_managed_prometheus_alert_events_on_environment_id ON self_managed_prometheus_alert_events USING btree (environment_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,61 +28,62 @@ Example response:
|
|||
|
||||
```json
|
||||
{
|
||||
"default_projects_limit" : 100000,
|
||||
"signup_enabled" : true,
|
||||
"id" : 1,
|
||||
"default_branch_protection" : 2,
|
||||
"restricted_visibility_levels" : [],
|
||||
"password_authentication_enabled_for_web" : true,
|
||||
"after_sign_out_path" : null,
|
||||
"max_attachment_size" : 10,
|
||||
"max_import_size": 50,
|
||||
"user_oauth_applications" : true,
|
||||
"updated_at" : "2016-01-04T15:44:55.176Z",
|
||||
"session_expire_delay" : 10080,
|
||||
"home_page_url" : null,
|
||||
"default_snippet_visibility" : "private",
|
||||
"outbound_local_requests_whitelist": [],
|
||||
"domain_allowlist" : [],
|
||||
"domain_denylist_enabled" : false,
|
||||
"domain_denylist" : [],
|
||||
"created_at" : "2016-01-04T15:44:55.176Z",
|
||||
"default_ci_config_path" : null,
|
||||
"default_project_visibility" : "private",
|
||||
"default_group_visibility" : "private",
|
||||
"gravatar_enabled" : true,
|
||||
"sign_in_text" : null,
|
||||
"container_expiration_policies_enable_historic_entries": true,
|
||||
"container_registry_token_expire_delay": 5,
|
||||
"repository_storages_weighted": {"default": 100},
|
||||
"plantuml_enabled": false,
|
||||
"plantuml_url": null,
|
||||
"kroki_enabled": false,
|
||||
"kroki_url": null,
|
||||
"terminal_max_session_time": 0,
|
||||
"polling_interval_multiplier": 1.0,
|
||||
"rsa_key_restriction": 0,
|
||||
"dsa_key_restriction": 0,
|
||||
"ecdsa_key_restriction": 0,
|
||||
"ed25519_key_restriction": 0,
|
||||
"first_day_of_week": 0,
|
||||
"enforce_terms": true,
|
||||
"terms": "Hello world!",
|
||||
"performance_bar_allowed_group_id": 42,
|
||||
"user_show_add_ssh_key_message": true,
|
||||
"local_markdown_version": 0,
|
||||
"allow_local_requests_from_hooks_and_services": true,
|
||||
"allow_local_requests_from_web_hooks_and_services": true,
|
||||
"allow_local_requests_from_system_hooks": false,
|
||||
"asset_proxy_enabled": true,
|
||||
"asset_proxy_url": "https://assets.example.com",
|
||||
"asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"],
|
||||
"npm_package_requests_forwarding": true,
|
||||
"snippet_size_limit": 52428800,
|
||||
"issues_create_limit": 300,
|
||||
"raw_blob_request_limit": 300,
|
||||
"wiki_page_max_content_bytes": 52428800,
|
||||
"require_admin_approval_after_user_signup": false
|
||||
"default_projects_limit" : 100000,
|
||||
"signup_enabled" : true,
|
||||
"id" : 1,
|
||||
"default_branch_protection" : 2,
|
||||
"restricted_visibility_levels" : [],
|
||||
"password_authentication_enabled_for_web" : true,
|
||||
"after_sign_out_path" : null,
|
||||
"max_attachment_size" : 10,
|
||||
"max_import_size": 50,
|
||||
"user_oauth_applications" : true,
|
||||
"updated_at" : "2016-01-04T15:44:55.176Z",
|
||||
"session_expire_delay" : 10080,
|
||||
"home_page_url" : null,
|
||||
"default_snippet_visibility" : "private",
|
||||
"outbound_local_requests_whitelist": [],
|
||||
"domain_allowlist" : [],
|
||||
"domain_denylist_enabled" : false,
|
||||
"domain_denylist" : [],
|
||||
"created_at" : "2016-01-04T15:44:55.176Z",
|
||||
"default_ci_config_path" : null,
|
||||
"default_project_visibility" : "private",
|
||||
"default_group_visibility" : "private",
|
||||
"gravatar_enabled" : true,
|
||||
"sign_in_text" : null,
|
||||
"container_expiration_policies_enable_historic_entries": true,
|
||||
"container_registry_token_expire_delay": 5,
|
||||
"repository_storages_weighted": {"default": 100},
|
||||
"plantuml_enabled": false,
|
||||
"plantuml_url": null,
|
||||
"kroki_enabled": false,
|
||||
"kroki_url": null,
|
||||
"terminal_max_session_time": 0,
|
||||
"polling_interval_multiplier": 1.0,
|
||||
"rsa_key_restriction": 0,
|
||||
"dsa_key_restriction": 0,
|
||||
"ecdsa_key_restriction": 0,
|
||||
"ed25519_key_restriction": 0,
|
||||
"first_day_of_week": 0,
|
||||
"enforce_terms": true,
|
||||
"terms": "Hello world!",
|
||||
"performance_bar_allowed_group_id": 42,
|
||||
"user_show_add_ssh_key_message": true,
|
||||
"local_markdown_version": 0,
|
||||
"allow_local_requests_from_hooks_and_services": true,
|
||||
"allow_local_requests_from_web_hooks_and_services": true,
|
||||
"allow_local_requests_from_system_hooks": false,
|
||||
"asset_proxy_enabled": true,
|
||||
"asset_proxy_url": "https://assets.example.com",
|
||||
"asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"],
|
||||
"npm_package_requests_forwarding": true,
|
||||
"snippet_size_limit": 52428800,
|
||||
"issues_create_limit": 300,
|
||||
"raw_blob_request_limit": 300,
|
||||
"wiki_page_max_content_bytes": 52428800,
|
||||
"require_admin_approval_after_user_signup": false,
|
||||
"personal_access_token_prefix": "GL-"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -91,12 +92,12 @@ the `file_template_project_id`, `deletion_adjourned_period`, or the `geo_node_al
|
|||
|
||||
```json
|
||||
{
|
||||
"id" : 1,
|
||||
"signup_enabled" : true,
|
||||
"file_template_project_id": 1,
|
||||
"geo_node_allowed_ips": "0.0.0.0/0, ::/0",
|
||||
"deletion_adjourned_period": 7,
|
||||
...
|
||||
"id" : 1,
|
||||
"signup_enabled" : true,
|
||||
"file_template_project_id": 1,
|
||||
"geo_node_allowed_ips": "0.0.0.0/0, ::/0",
|
||||
"deletion_adjourned_period": 7,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -174,7 +175,8 @@ Example response:
|
|||
"issues_create_limit": 300,
|
||||
"raw_blob_request_limit": 300,
|
||||
"wiki_page_max_content_bytes": 52428800,
|
||||
"require_admin_approval_after_user_signup": false
|
||||
"require_admin_approval_after_user_signup": false,
|
||||
"personal_access_token_prefix": "GL-"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -318,6 +320,7 @@ listed in the descriptions of the relevant settings.
|
|||
| `performance_bar_allowed_group_id` | string | no | (Deprecated: Use `performance_bar_allowed_group_path` instead) Path of the group that is allowed to toggle the performance bar. |
|
||||
| `performance_bar_allowed_group_path` | string | no | Path of the group that is allowed to toggle the performance bar. |
|
||||
| `performance_bar_enabled` | boolean | no | (Deprecated: Pass `performance_bar_allowed_group_path: nil` instead) Allow enabling the performance bar. |
|
||||
| `personal_access_token_prefix` | string | no | Prefix for all generated personal access tokens. |
|
||||
| `plantuml_enabled` | boolean | no | (**If enabled, requires:** `plantuml_url`) Enable PlantUML integration. Default is `false`. |
|
||||
| `plantuml_url` | string | required by: `plantuml_enabled` | The PlantUML instance URL for integration. |
|
||||
| `polling_interval_multiplier` | decimal | no | Interval multiplier used by endpoints that perform polling. Set to `0` to disable polling. |
|
||||
|
|
|
|||
|
|
@ -119,7 +119,9 @@ This work is being done as part of dedicated epic: [Improve internal usage of
|
|||
Feature Flags](https://gitlab.com/groups/gitlab-org/-/epics/3551). This epic
|
||||
describes a meta reasons for making these changes.
|
||||
|
||||
## Who
|
||||
## [Who](#who)
|
||||
|
||||
### Blueprint
|
||||
|
||||
Proposal:
|
||||
|
||||
|
|
@ -140,4 +142,24 @@ DRIs:
|
|||
| Leadership | Craig Gomes |
|
||||
| Engineering | Kamil Trzciński |
|
||||
|
||||
### [Stakeholders](#stakeholders)
|
||||
|
||||
| Role | Person | Title
|
||||
|--------------------|-----------------------|--------------------------------------------------------------------|
|
||||
| Executive Sponsor | Christopher Lefelhocz | Senior Director of Development |
|
||||
| Facilitator | Darby Frey | Senior Engineering Manager, Verify |
|
||||
| DRI / Leadership | Craig Gomes | Backend Engineering Manager, Memory and Database |
|
||||
| DRI / Engineering | Kamil Trzciński | Distinguished Engineer, Ops and Enablement |
|
||||
| DRI / Product | Kenny Johnston | Senior Director of Product Management, Ops |
|
||||
| Functional Lead | Ricky Wiens | Backend Engineering Manager, Verify:Testing |
|
||||
| Functional Lead | Anthony Sandoval | Engineering Manager, Reliability |
|
||||
| Functional Lead | James Heimbuck | Senior Product Manager, Verify:Testing |
|
||||
| Member | Grzegorz Bizon | Staff Backend Engineer, Verify |
|
||||
| Member | Michelle Gill | Engineering Manager, Create:Source Code |
|
||||
| Member | Wayne Haber | Director of Engineering, Threat Management |
|
||||
| Member | Doug Stull | Senior Fullstack Engineer, Growth:Expansion |
|
||||
| Member | Andrew Fontaine | Senior Frontend Engineer, Release |
|
||||
| Member | Rémy Coutable | Staff Backend Engineer, Engineering Productivity |
|
||||
| Member | Marin Jankovski | Senior Engineering Manager, Infrastructure, Delivery & Scalability |
|
||||
|
||||
<!-- vale gitlab.Spelling = YES -->
|
||||
|
|
|
|||
|
|
@ -35,6 +35,25 @@ If you choose a size larger than what is currently configured for the web server
|
|||
you will likely get errors. See the [troubleshooting section](#troubleshooting) for more
|
||||
details.
|
||||
|
||||
## Personal Access Token prefix
|
||||
|
||||
You can set a global prefix for all generated Personal Access Tokens.
|
||||
|
||||
A prefix can help you identify PATs visually, as well as with automation tools.
|
||||
|
||||
### Setting a prefix
|
||||
|
||||
Only a GitLab administrator can set the prefix, which is a global setting applied
|
||||
to any PAT generated in the system by any user:
|
||||
|
||||
1. Navigate to **Admin Area > Settings > General**.
|
||||
1. Expand the **Account and limit** section.
|
||||
1. Fill in the **Personal Access Token prefix** field.
|
||||
1. Click **Save changes**.
|
||||
|
||||
It is also possible to configure the prefix via the [settings API](../../../api/settings.md)
|
||||
using the `personal_access_token_prefix` field.
|
||||
|
||||
## Repository size limit **(STARTER ONLY)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/740) in [GitLab Enterprise Edition 8.12](https://about.gitlab.com/releases/2016/09/22/gitlab-8-12-released/#limit-project-size-ee).
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ and above.
|
|||
|
||||
There are a few limitations compared to project wikis:
|
||||
|
||||
- Local Git access is not supported yet.
|
||||
- Git LFS is not supported.
|
||||
- Group wikis are not included in global search, group exports, backups, and Geo replication.
|
||||
- Changes to group wikis don't show up in the group's activity feed.
|
||||
- Group wikis [can't be moved](../../api/project_repository_storage_moves.md#limitations) using the project
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ module API
|
|||
optional :performance_bar_allowed_group_id, type: String, desc: 'Deprecated: Use :performance_bar_allowed_group_path instead. Path of the group that is allowed to toggle the performance bar.' # support legacy names, can be removed in v6
|
||||
optional :performance_bar_allowed_group_path, type: String, desc: 'Path of the group that is allowed to toggle the performance bar.'
|
||||
optional :performance_bar_enabled, type: String, desc: 'Deprecated: Pass `performance_bar_allowed_group_path: nil` instead. Allow enabling the performance.' # support legacy names, can be removed in v6
|
||||
optional :personal_access_token_prefix, type: String, desc: 'Prefix to prepend to all personal access tokens'
|
||||
optional :kroki_enabled, type: Boolean, desc: 'Enable Kroki'
|
||||
given kroki_enabled: ->(val) { val } do
|
||||
requires :kroki_url, type: String, desc: 'The Kroki server URL'
|
||||
|
|
|
|||
|
|
@ -194,6 +194,10 @@ module Gitlab
|
|||
|
||||
def access_token
|
||||
strong_memoize(:access_token) do
|
||||
# The token can be a PAT or an OAuth (doorkeeper) token
|
||||
# It is also possible that a PAT is encapsulated in a `Bearer` OAuth token
|
||||
# (e.g. NPM client registry auth), this case will be properly handled
|
||||
# by find_personal_access_token
|
||||
find_oauth_access_token || find_personal_access_token
|
||||
end
|
||||
end
|
||||
|
|
@ -237,7 +241,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def matches_personal_access_token_length?(token)
|
||||
token.length == PersonalAccessToken::TOKEN_LENGTH
|
||||
PersonalAccessToken::TOKEN_LENGTH_RANGE.include?(token.length)
|
||||
end
|
||||
|
||||
# Check if the request is GET/HEAD, or if CSRF token is valid.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
class Config
|
||||
module Entry
|
||||
##
|
||||
# Entry that represents allow_failure settings.
|
||||
#
|
||||
class AllowFailure < ::Gitlab::Config::Entry::Node
|
||||
include ::Gitlab::Config::Entry::Attributable
|
||||
include ::Gitlab::Config::Entry::Validatable
|
||||
|
||||
ALLOWED_KEYS = %i[exit_codes].freeze
|
||||
attributes ALLOWED_KEYS
|
||||
|
||||
validations do
|
||||
validates :config, hash_or_boolean: true
|
||||
validates :config, allowed_keys: ALLOWED_KEYS
|
||||
validates :exit_codes, array_of_integers_or_integer: true, allow_nil: true
|
||||
end
|
||||
|
||||
def value
|
||||
@config[:exit_codes] = Array.wrap(exit_codes) if exit_codes.present?
|
||||
@config
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -22,6 +22,7 @@ module Gitlab
|
|||
in: ALLOWED_WHEN,
|
||||
message: "should be one of: #{ALLOWED_WHEN.join(', ')}"
|
||||
}
|
||||
validates :allow_failure, boolean: true
|
||||
end
|
||||
|
||||
validate on: :composed do
|
||||
|
|
@ -47,7 +48,7 @@ module Gitlab
|
|||
inherit: false,
|
||||
metadata: { allowed_needs: %i[job bridge] }
|
||||
|
||||
attributes :when
|
||||
attributes :when, :allow_failure
|
||||
|
||||
def self.matching?(name, config)
|
||||
!name.to_s.start_with?('.') &&
|
||||
|
|
@ -72,6 +73,10 @@ module Gitlab
|
|||
def bridge_needs
|
||||
needs_value[:bridge] if needs_value
|
||||
end
|
||||
|
||||
def ignored?
|
||||
allow_failure.nil? ? manual_action? : allow_failure
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ module Gitlab
|
|||
|
||||
validates :dependencies, array_of_strings: true
|
||||
validates :resource_group, type: String
|
||||
validates :allow_failure, hash_or_boolean: true
|
||||
end
|
||||
|
||||
validates :start_in, duration: { limit: '1 week' }, if: :delayed?
|
||||
|
|
@ -117,9 +118,14 @@ module Gitlab
|
|||
description: 'Parallel configuration for this job.',
|
||||
inherit: false
|
||||
|
||||
entry :allow_failure, ::Gitlab::Ci::Config::Entry::AllowFailure,
|
||||
description: 'Indicates whether this job is allowed to fail or not.',
|
||||
inherit: false
|
||||
|
||||
attributes :script, :tags, :when, :dependencies,
|
||||
:needs, :retry, :parallel, :start_in,
|
||||
:interruptible, :timeout, :resource_group, :release
|
||||
:interruptible, :timeout, :resource_group,
|
||||
:release, :allow_failure
|
||||
|
||||
def self.matching?(name, config)
|
||||
!name.to_s.start_with?('.') &&
|
||||
|
|
@ -166,11 +172,32 @@ module Gitlab
|
|||
release: release_value,
|
||||
after_script: after_script_value,
|
||||
ignore: ignored?,
|
||||
allow_failure_criteria: allow_failure_criteria,
|
||||
needs: needs_defined? ? needs_value : nil,
|
||||
resource_group: resource_group,
|
||||
scheduling_type: needs_defined? ? :dag : :stage
|
||||
).compact
|
||||
end
|
||||
|
||||
def ignored?
|
||||
allow_failure_defined? ? static_allow_failure : manual_action?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allow_failure_criteria
|
||||
return unless ::Gitlab::Ci::Features.allow_failure_with_exit_codes_enabled?
|
||||
|
||||
if allow_failure_defined? && allow_failure_value.is_a?(Hash)
|
||||
allow_failure_value
|
||||
end
|
||||
end
|
||||
|
||||
def static_allow_failure
|
||||
return false if allow_failure_value.is_a?(Hash)
|
||||
|
||||
allow_failure_value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ module Gitlab
|
|||
with_options allow_nil: true do
|
||||
validates :extends, array_of_strings_or_string: true
|
||||
validates :rules, array_of_hashes: true
|
||||
validates :allow_failure, boolean: true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -65,7 +64,7 @@ module Gitlab
|
|||
inherit: false,
|
||||
default: {}
|
||||
|
||||
attributes :extends, :rules, :allow_failure
|
||||
attributes :extends, :rules
|
||||
end
|
||||
|
||||
def compose!(deps = nil)
|
||||
|
|
@ -141,10 +140,6 @@ module Gitlab
|
|||
def manual_action?
|
||||
self.when == 'manual'
|
||||
end
|
||||
|
||||
def ignored?
|
||||
allow_failure.nil? ? manual_action? : allow_failure
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ module Gitlab
|
|||
def self.ci_pipeline_editor_page_enabled?(project)
|
||||
::Feature.enabled?(:ci_pipeline_editor_page, project, default_enabled: false)
|
||||
end
|
||||
|
||||
def self.allow_failure_with_exit_codes_enabled?
|
||||
::Feature.enabled?(:ci_allow_failure_with_exit_codes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ module Gitlab
|
|||
@seed_attributes
|
||||
.deep_merge(pipeline_attributes)
|
||||
.deep_merge(rules_attributes)
|
||||
.deep_merge(allow_failure_criteria_attributes)
|
||||
.deep_merge(cache_attributes)
|
||||
end
|
||||
|
||||
|
|
@ -154,9 +155,13 @@ module Gitlab
|
|||
end
|
||||
|
||||
def rules_attributes
|
||||
return {} unless @using_rules
|
||||
|
||||
rules_result.build_attributes
|
||||
strong_memoize(:rules_attributes) do
|
||||
if @using_rules
|
||||
rules_result.build_attributes
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def rules_result
|
||||
|
|
@ -176,6 +181,17 @@ module Gitlab
|
|||
@cache.build_attributes
|
||||
end
|
||||
end
|
||||
|
||||
# If a job uses `allow_failure:exit_codes` and `rules:allow_failure`
|
||||
# we need to prevent the exit codes from being persisted because they
|
||||
# would break the behavior defined by `rules:allow_failure`.
|
||||
def allow_failure_criteria_attributes
|
||||
return {} unless ::Gitlab::Ci::Features.allow_failure_with_exit_codes_enabled?
|
||||
return {} if rules_attributes[:allow_failure].nil?
|
||||
return {} unless @seed_attributes.dig(:options, :allow_failure_criteria)
|
||||
|
||||
{ options: { allow_failure_criteria: nil } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ variables:
|
|||
FUZZAPI_VERSION: latest
|
||||
FUZZAPI_CONFIG: .gitlab-api-fuzzing.yml
|
||||
FUZZAPI_TIMEOUT: 30
|
||||
FUZZAPI_REPORT: gl-api-fuzzing-report.xml
|
||||
FUZZAPI_REPORT: gl-api-fuzzing-report.json
|
||||
FUZZAPI_REPORT_ASSET_PATH: assets
|
||||
#
|
||||
FUZZAPI_D_NETWORK: testing-net
|
||||
#
|
||||
|
|
@ -45,6 +46,7 @@ apifuzzer_fuzz:
|
|||
variables:
|
||||
FUZZAPI_PROJECT: $CI_PROJECT_PATH
|
||||
FUZZAPI_API: http://apifuzzer:80
|
||||
FUZZAPI_NEW_REPORT: 1
|
||||
TZ: America/Los_Angeles
|
||||
services:
|
||||
- name: $FUZZAPI_IMAGE
|
||||
|
|
@ -75,6 +77,9 @@ apifuzzer_fuzz:
|
|||
# Run user provided pre-script
|
||||
- sh -c "$FUZZAPI_PRE_SCRIPT"
|
||||
#
|
||||
# Make sure asset path exists
|
||||
- mkdir -p $FUZZAPI_REPORT_ASSET_PATH
|
||||
#
|
||||
# Start scanning
|
||||
- worker-entry
|
||||
#
|
||||
|
|
@ -82,8 +87,12 @@ apifuzzer_fuzz:
|
|||
- sh -c "$FUZZAPI_POST_SCRIPT"
|
||||
#
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- $FUZZAPI_REPORT_ASSET_PATH
|
||||
- $FUZZAPI_REPORT
|
||||
reports:
|
||||
junit: $FUZZAPI_REPORT
|
||||
api_fuzzing: $FUZZAPI_REPORT
|
||||
|
||||
apifuzzer_fuzz_dnd:
|
||||
stage: fuzz
|
||||
|
|
@ -115,6 +124,9 @@ apifuzzer_fuzz_dnd:
|
|||
# Run user provided pre-script
|
||||
- sh -c "$FUZZAPI_PRE_SCRIPT"
|
||||
#
|
||||
# Make sure asset path exists
|
||||
- mkdir -p $FUZZAPI_REPORT_ASSET_PATH
|
||||
#
|
||||
# Start peach testing engine container
|
||||
- |
|
||||
docker run -d \
|
||||
|
|
@ -155,6 +167,8 @@ apifuzzer_fuzz_dnd:
|
|||
-e FUZZAPI_PROFILE \
|
||||
-e FUZZAPI_CONFIG \
|
||||
-e FUZZAPI_REPORT \
|
||||
-e FUZZAPI_REPORT_ASSET_PATH \
|
||||
-e FUZZAPI_NEW_REPORT=1 \
|
||||
-e FUZZAPI_HAR \
|
||||
-e FUZZAPI_OPENAPI \
|
||||
-e FUZZAPI_POSTMAN_COLLECTION \
|
||||
|
|
@ -168,6 +182,8 @@ apifuzzer_fuzz_dnd:
|
|||
-e FUZZAPI_SERVICE_START_TIMEOUT \
|
||||
-e FUZZAPI_HTTP_USERNAME \
|
||||
-e FUZZAPI_HTTP_PASSWORD \
|
||||
-e CI_PROJECT_URL \
|
||||
-e CI_JOB_ID \
|
||||
-e CI_COMMIT_BRANCH=${CI_COMMIT_BRANCH} \
|
||||
$FUZZAPI_D_WORKER_ENV \
|
||||
$FUZZAPI_D_WORKER_PORTS \
|
||||
|
|
@ -193,6 +209,8 @@ apifuzzer_fuzz_dnd:
|
|||
-e FUZZAPI_PROFILE \
|
||||
-e FUZZAPI_CONFIG \
|
||||
-e FUZZAPI_REPORT \
|
||||
-e FUZZAPI_REPORT_ASSET_PATH \
|
||||
-e FUZZAPI_NEW_REPORT=1 \
|
||||
-e FUZZAPI_HAR \
|
||||
-e FUZZAPI_OPENAPI \
|
||||
-e FUZZAPI_POSTMAN_COLLECTION \
|
||||
|
|
@ -206,7 +224,10 @@ apifuzzer_fuzz_dnd:
|
|||
-e FUZZAPI_SERVICE_START_TIMEOUT \
|
||||
-e FUZZAPI_HTTP_USERNAME \
|
||||
-e FUZZAPI_HTTP_PASSWORD \
|
||||
-e CI_PROJECT_URL \
|
||||
-e CI_JOB_ID \
|
||||
-v $CI_PROJECT_DIR:/app \
|
||||
-v `pwd`/$FUZZAPI_REPORT_ASSET_PATH:/app/$FUZZAPI_REPORT_ASSET_PATH:rw \
|
||||
-p 81:80 \
|
||||
-p 8001:8000 \
|
||||
-p 515:514 \
|
||||
|
|
@ -239,7 +260,9 @@ apifuzzer_fuzz_dnd:
|
|||
paths:
|
||||
- ./gl-api_fuzzing*.log
|
||||
- ./gl-api_fuzzing*.zip
|
||||
- $FUZZAPI_REPORT_ASSET_PATH
|
||||
- $FUZZAPI_REPORT
|
||||
reports:
|
||||
junit: $FUZZAPI_REPORT
|
||||
api_fuzzing: $FUZZAPI_REPORT
|
||||
|
||||
# end
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ module Gitlab
|
|||
options: {
|
||||
image: job[:image],
|
||||
services: job[:services],
|
||||
allow_failure_criteria: job[:allow_failure_criteria],
|
||||
artifacts: job[:artifacts],
|
||||
dependencies: job[:dependencies],
|
||||
cross_dependencies: job.dig(:needs, :cross_dependency),
|
||||
|
|
|
|||
|
|
@ -134,6 +134,16 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
class HashOrBooleanValidator < ActiveModel::EachValidator
|
||||
include LegacyValidationHelpers
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
unless value.is_a?(Hash) || validate_boolean(value)
|
||||
record.errors.add(attribute, 'should be a hash or a boolean value')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class KeyValidator < ActiveModel::EachValidator
|
||||
include LegacyValidationHelpers
|
||||
|
||||
|
|
@ -158,6 +168,22 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
class ArrayOfIntegersOrIntegerValidator < ActiveModel::EachValidator
|
||||
include LegacyValidationHelpers
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
unless validate_integer(value) || validate_array_of_integers(value)
|
||||
record.errors.add(attribute, 'should be an array of integers or an integer')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_array_of_integers(values)
|
||||
values.is_a?(Array) && values.all? { |value| validate_integer(value) }
|
||||
end
|
||||
end
|
||||
|
||||
class RegexpValidator < ActiveModel::EachValidator
|
||||
include LegacyValidationHelpers
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ module Gitlab
|
|||
end,
|
||||
container_class: ProjectWiki,
|
||||
project_resolver: -> (wiki) { wiki.try(:project) },
|
||||
guest_read_ability: :download_wiki_code,
|
||||
suffix: :wiki
|
||||
).freeze
|
||||
SNIPPET = RepoType.new(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ module Gitlab
|
|||
module RepoPath
|
||||
NotFoundError = Class.new(StandardError)
|
||||
|
||||
# Returns an array containing:
|
||||
# - The repository container
|
||||
# - The related project (if available)
|
||||
# - The repository type
|
||||
# - The original container path (if redirected)
|
||||
#
|
||||
# @returns [HasRepository, Project, String, String]
|
||||
def self.parse(path)
|
||||
repo_path = path.delete_prefix('/').delete_suffix('.git')
|
||||
redirected_path = nil
|
||||
|
|
@ -30,7 +37,15 @@ module Gitlab
|
|||
[nil, nil, Gitlab::GlRepository.default_type, nil]
|
||||
end
|
||||
|
||||
# Returns an array containing:
|
||||
# - The repository container
|
||||
# - The related project (if available)
|
||||
# - The original container path (if redirected)
|
||||
#
|
||||
# @returns [HasRepository, Project, String]
|
||||
def self.find_container(type, full_path)
|
||||
return [nil, nil, nil] if full_path.blank?
|
||||
|
||||
if type.snippet?
|
||||
snippet, redirected_path = find_snippet(full_path)
|
||||
|
||||
|
|
@ -47,26 +62,24 @@ module Gitlab
|
|||
end
|
||||
|
||||
def self.find_project(project_path)
|
||||
return [nil, nil] if project_path.blank?
|
||||
|
||||
project = Project.find_by_full_path(project_path, follow_redirects: true)
|
||||
redirected_path = redirected?(project, project_path) ? project_path : nil
|
||||
redirected_path = project_path if redirected?(project, project_path)
|
||||
|
||||
[project, redirected_path]
|
||||
end
|
||||
|
||||
def self.redirected?(project, project_path)
|
||||
project && project.full_path.casecmp(project_path) != 0
|
||||
def self.redirected?(container, container_path)
|
||||
container && container.full_path.casecmp(container_path) != 0
|
||||
end
|
||||
|
||||
# Snippet_path can be either:
|
||||
# - snippets/1
|
||||
# - h5bp/html5-boilerplate/snippets/53
|
||||
def self.find_snippet(snippet_path)
|
||||
return [nil, nil] if snippet_path.blank?
|
||||
|
||||
snippet_id, project_path = extract_snippet_info(snippet_path)
|
||||
project, redirected_path = find_project(project_path)
|
||||
return [nil, nil] unless snippet_id
|
||||
|
||||
project, redirected_path = find_project(project_path) if project_path
|
||||
|
||||
[Snippet.find_by_id_and_project(id: snippet_id, project: project), redirected_path]
|
||||
end
|
||||
|
|
@ -74,19 +87,23 @@ module Gitlab
|
|||
# Wiki path can be either:
|
||||
# - namespace/project
|
||||
# - group/subgroup/project
|
||||
def self.find_wiki(wiki_path)
|
||||
return [nil, nil] if wiki_path.blank?
|
||||
#
|
||||
# And also in EE:
|
||||
# - group
|
||||
# - group/subgroup
|
||||
def self.find_wiki(container_path)
|
||||
container = Routable.find_by_full_path(container_path, follow_redirects: true)
|
||||
redirected_path = container_path if redirected?(container, container_path)
|
||||
|
||||
project, redirected_path = find_project(wiki_path)
|
||||
|
||||
[project&.wiki, redirected_path]
|
||||
# In CE, Group#wiki is not available so this will return nil for a group path.
|
||||
[container&.try(:wiki), redirected_path]
|
||||
end
|
||||
|
||||
def self.extract_snippet_info(snippet_path)
|
||||
path_segments = snippet_path.split('/')
|
||||
snippet_id = path_segments.pop
|
||||
path_segments.pop # Remove snippets from path
|
||||
project_path = File.join(path_segments)
|
||||
path_segments.pop # Remove 'snippets' from path
|
||||
project_path = File.join(path_segments).presence
|
||||
|
||||
[snippet_id, project_path]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -584,7 +584,7 @@ module Gitlab
|
|||
gitlab: distinct_count(::BulkImport.where(time_period, source_type: :gitlab), :user_id)
|
||||
},
|
||||
projects_imported: {
|
||||
total: count(Project.where(time_period).where.not(import_type: nil)),
|
||||
total: distinct_count(::Project.where(time_period).where.not(import_type: nil), :creator_id),
|
||||
gitlab_project: projects_imported_count('gitlab_project', time_period),
|
||||
gitlab: projects_imported_count('gitlab', time_period),
|
||||
github: projects_imported_count('github', time_period),
|
||||
|
|
@ -894,7 +894,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def projects_imported_count(from, time_period)
|
||||
distinct_count(::Project.imported_from(from).where(time_period), :creator_id) # rubocop: disable CodeReuse/ActiveRecord
|
||||
distinct_count(::Project.imported_from(from).where(time_period).where.not(import_type: nil), :creator_id) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
# rubocop:disable CodeReuse/ActiveRecord
|
||||
|
|
|
|||
|
|
@ -4774,6 +4774,12 @@ msgstr ""
|
|||
msgid "Bulk request concurrency"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|From source group"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|To new group"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|expected an associated Group but has an associated Project"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -16876,6 +16882,9 @@ msgstr ""
|
|||
msgid "Max 100,000 events"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max 20 characters"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max Group Export Download requests per minute per user"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -20173,6 +20182,9 @@ msgstr ""
|
|||
msgid "Personal Access Token"
|
||||
msgstr ""
|
||||
|
||||
msgid "Personal Access Token prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32331,6 +32343,9 @@ msgstr ""
|
|||
msgid "by"
|
||||
msgstr ""
|
||||
|
||||
msgid "can contain only letters of the Base64 alphabet (RFC4648) with the addition of '@', ':' and '.'"
|
||||
msgstr ""
|
||||
|
||||
msgid "cannot be a date in the past"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,44 @@ RSpec.describe Admin::ApplicationSettingsController do
|
|||
expect(ApplicationSetting.current.default_branch_name).to eq("example_branch_name")
|
||||
end
|
||||
|
||||
context "personal access token prefix settings" do
|
||||
let(:application_settings) { ApplicationSetting.current }
|
||||
|
||||
shared_examples "accepts prefix setting" do |prefix|
|
||||
it "updates personal_access_token_prefix setting" do
|
||||
put :update, params: { application_setting: { personal_access_token_prefix: prefix } }
|
||||
|
||||
expect(response).to redirect_to(general_admin_application_settings_path)
|
||||
expect(application_settings.reload.personal_access_token_prefix).to eq(prefix)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "rejects prefix setting" do |prefix|
|
||||
it "does not update personal_access_token_prefix setting" do
|
||||
put :update, params: { application_setting: { personal_access_token_prefix: prefix } }
|
||||
|
||||
expect(response).not_to redirect_to(general_admin_application_settings_path)
|
||||
expect(application_settings.reload.personal_access_token_prefix).not_to eq(prefix)
|
||||
end
|
||||
end
|
||||
|
||||
context "with valid prefix" do
|
||||
include_examples("accepts prefix setting", "a_prefix@")
|
||||
end
|
||||
|
||||
context "with blank prefix" do
|
||||
include_examples("accepts prefix setting", "")
|
||||
end
|
||||
|
||||
context "with too long prefix" do
|
||||
include_examples("rejects prefix setting", "a_prefix@" * 10)
|
||||
end
|
||||
|
||||
context "with invalid characters prefix" do
|
||||
include_examples("rejects prefix setting", "a_préfixñ:")
|
||||
end
|
||||
end
|
||||
|
||||
context 'external policy classification settings' do
|
||||
let(:settings) do
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Repositories::GitHttpController do
|
||||
include GitHttpHelpers
|
||||
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:personal_snippet) { create(:personal_snippet, :public, :repository) }
|
||||
let_it_be(:project_snippet) { create(:project_snippet, :public, :repository, project: project) }
|
||||
|
|
|
|||
|
|
@ -26,5 +26,9 @@ FactoryBot.define do
|
|||
trait :invalid do
|
||||
token_digest { nil }
|
||||
end
|
||||
|
||||
trait :no_prefix do
|
||||
after(:build) { |personal_access_token| personal_access_token.set_token(Devise.friendly_token) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,6 +22,19 @@ RSpec.describe 'Import/Export - Connect to another instance', :js do
|
|||
it 'successfully connects to remote instance' do
|
||||
source_url = 'https://gitlab.com'
|
||||
pat = 'demo-pat'
|
||||
stub_path = 'stub-group'
|
||||
|
||||
stub_request(:get, "%{url}/api/v4/groups?page=1&per_page=30&top_level_only=true" % { url: source_url }).to_return(
|
||||
body: [{
|
||||
id: 2595438,
|
||||
web_url: 'https://gitlab.com/groups/auto-breakfast',
|
||||
name: 'Stub',
|
||||
path: stub_path,
|
||||
full_name: 'Stub',
|
||||
full_path: stub_path
|
||||
}].to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
|
||||
expect(page).to have_content 'Import groups from another instance of GitLab'
|
||||
|
||||
|
|
@ -31,6 +44,7 @@ RSpec.describe 'Import/Export - Connect to another instance', :js do
|
|||
click_on 'Connect instance'
|
||||
|
||||
expect(page).to have_content 'Importing groups from %{url}' % { url: source_url }
|
||||
expect(page).to have_content stub_path
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlButton, GlLink, GlFormInput } from '@gitlab/ui';
|
||||
import Select2Select from '~/vue_shared/components/select2_select.vue';
|
||||
import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
|
||||
import { STATUSES } from '~/import_entities/constants';
|
||||
import { availableNamespacesFixture } from '../graphql/fixtures';
|
||||
|
||||
const getFakeGroup = status => ({
|
||||
web_url: 'https://fake.host/',
|
||||
full_path: 'fake_group_1',
|
||||
full_name: 'fake_name_1',
|
||||
import_target: {
|
||||
target_namespace: 'root',
|
||||
new_name: 'group1',
|
||||
},
|
||||
id: 1,
|
||||
status,
|
||||
});
|
||||
|
||||
describe('import table row', () => {
|
||||
let wrapper;
|
||||
let group;
|
||||
|
||||
const findByText = (cmp, text) => {
|
||||
return wrapper.findAll(cmp).wrappers.find(node => node.text().indexOf(text) === 0);
|
||||
};
|
||||
const findImportButton = () => findByText(GlButton, 'Import');
|
||||
const findNameInput = () => wrapper.find(GlFormInput);
|
||||
const findNamespaceDropdown = () => wrapper.find(Select2Select);
|
||||
|
||||
const createComponent = props => {
|
||||
wrapper = shallowMount(ImportTableRow, {
|
||||
propsData: {
|
||||
availableNamespaces: availableNamespacesFixture,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
beforeEach(() => {
|
||||
group = getFakeGroup(STATUSES.NONE);
|
||||
createComponent({ group });
|
||||
});
|
||||
|
||||
it.each`
|
||||
selector | sourceEvent | payload | event
|
||||
${findNamespaceDropdown} | ${'input'} | ${'demo'} | ${'update-target-namespace'}
|
||||
${findNameInput} | ${'input'} | ${'demo'} | ${'update-new-name'}
|
||||
${findImportButton} | ${'click'} | ${undefined} | ${'import-group'}
|
||||
`('invokes $event', ({ selector, sourceEvent, payload, event }) => {
|
||||
selector().vm.$emit(sourceEvent, payload);
|
||||
expect(wrapper.emitted(event)).toBeDefined();
|
||||
expect(wrapper.emitted(event)[0][0]).toBe(payload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity status is NONE', () => {
|
||||
beforeEach(() => {
|
||||
group = getFakeGroup(STATUSES.NONE);
|
||||
createComponent({ group });
|
||||
});
|
||||
|
||||
it('renders Import button', () => {
|
||||
expect(findByText(GlButton, 'Import').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders namespace dropdown as not disabled', () => {
|
||||
expect(findNamespaceDropdown().attributes('disabled')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity status is SCHEDULING', () => {
|
||||
beforeEach(() => {
|
||||
group = getFakeGroup(STATUSES.SCHEDULING);
|
||||
createComponent({ group });
|
||||
});
|
||||
|
||||
it('does not render Import button', () => {
|
||||
expect(findByText(GlButton, 'Import')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('renders namespace dropdown as disabled', () => {
|
||||
expect(findNamespaceDropdown().attributes('disabled')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity status is FINISHED', () => {
|
||||
beforeEach(() => {
|
||||
group = getFakeGroup(STATUSES.FINISHED);
|
||||
createComponent({ group });
|
||||
});
|
||||
|
||||
it('does not render Import button', () => {
|
||||
expect(findByText(GlButton, 'Import')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('does not render namespace dropdown', () => {
|
||||
expect(findNamespaceDropdown().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders target as link', () => {
|
||||
const TARGET_LINK = `${group.import_target.target_namespace}/${group.import_target.new_name}`;
|
||||
expect(findByText(GlLink, TARGET_LINK).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createMockApollo from 'jest/helpers/mock_apollo_helper';
|
||||
import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
|
||||
import ImportTable from '~/import_entities/import_groups/components/import_table.vue';
|
||||
import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql';
|
||||
import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql';
|
||||
import importGroupMutation from '~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql';
|
||||
|
||||
import { STATUSES } from '~/import_entities/constants';
|
||||
|
||||
import { availableNamespacesFixture, generateFakeEntry } from '../graphql/fixtures';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
describe('import table', () => {
|
||||
let wrapper;
|
||||
let apolloProvider;
|
||||
|
||||
const createComponent = ({ bulkImportSourceGroups }) => {
|
||||
apolloProvider = createMockApollo([], {
|
||||
Query: {
|
||||
availableNamespaces: () => availableNamespacesFixture,
|
||||
bulkImportSourceGroups,
|
||||
},
|
||||
Mutation: {
|
||||
setTargetNamespace: jest.fn(),
|
||||
setNewName: jest.fn(),
|
||||
importGroup: jest.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
wrapper = shallowMount(ImportTable, {
|
||||
localVue,
|
||||
apolloProvider,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
it('renders loading icon while performing request', async () => {
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => new Promise(() => {}),
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not renders loading icon when request is completed', async () => {
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => [],
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders import row for each group in response', async () => {
|
||||
const FAKE_GROUPS = [
|
||||
generateFakeEntry({ id: 1, status: STATUSES.NONE }),
|
||||
generateFakeEntry({ id: 2, status: STATUSES.FINISHED }),
|
||||
];
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => FAKE_GROUPS,
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.findAll(ImportTableRow)).toHaveLength(FAKE_GROUPS.length);
|
||||
});
|
||||
|
||||
describe('converts row events to mutation invocations', () => {
|
||||
const FAKE_GROUP = generateFakeEntry({ id: 1, status: STATUSES.NONE });
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => [FAKE_GROUP],
|
||||
});
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
it.each`
|
||||
event | payload | mutation | variables
|
||||
${'update-target-namespace'} | ${'new-namespace'} | ${setTargetNamespaceMutation} | ${{ sourceGroupId: FAKE_GROUP.id, targetNamespace: 'new-namespace' }}
|
||||
${'update-new-name'} | ${'new-name'} | ${setNewNameMutation} | ${{ sourceGroupId: FAKE_GROUP.id, newName: 'new-name' }}
|
||||
${'import-group'} | ${undefined} | ${importGroupMutation} | ${{ sourceGroupId: FAKE_GROUP.id }}
|
||||
`('correctly maps $event to mutation', async ({ event, payload, mutation, variables }) => {
|
||||
jest.spyOn(apolloProvider.defaultClient, 'mutate');
|
||||
wrapper.find(ImportTableRow).vm.$emit(event, payload);
|
||||
await waitForPromises();
|
||||
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
|
||||
mutation,
|
||||
variables,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
||||
import { createMockClient } from 'mock-apollo-client';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import {
|
||||
clientTypenames,
|
||||
createResolvers,
|
||||
} from '~/import_entities/import_groups/graphql/client_factory';
|
||||
import { STATUSES } from '~/import_entities/constants';
|
||||
|
||||
import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql';
|
||||
import availableNamespacesQuery from '~/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql';
|
||||
import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql';
|
||||
import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql';
|
||||
import importGroupMutation from '~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql';
|
||||
import httpStatus from '~/lib/utils/http_status';
|
||||
import { statusEndpointFixture, availableNamespacesFixture } from './fixtures';
|
||||
|
||||
const FAKE_ENDPOINTS = {
|
||||
status: '/fake_status_url',
|
||||
availableNamespaces: '/fake_available_namespaces',
|
||||
createBulkImport: '/fake_create_bulk_import',
|
||||
};
|
||||
|
||||
describe('Bulk import resolvers', () => {
|
||||
let axiosMockAdapter;
|
||||
let client;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMockAdapter = new MockAdapter(axios);
|
||||
client = createMockClient({
|
||||
cache: new InMemoryCache({
|
||||
fragmentMatcher: { match: () => true },
|
||||
addTypename: false,
|
||||
}),
|
||||
resolvers: createResolvers({ endpoints: FAKE_ENDPOINTS }),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMockAdapter.restore();
|
||||
});
|
||||
|
||||
describe('queries', () => {
|
||||
describe('availableNamespaces', () => {
|
||||
let results;
|
||||
|
||||
beforeEach(async () => {
|
||||
axiosMockAdapter
|
||||
.onGet(FAKE_ENDPOINTS.availableNamespaces)
|
||||
.reply(httpStatus.OK, availableNamespacesFixture);
|
||||
|
||||
const response = await client.query({ query: availableNamespacesQuery });
|
||||
results = response.data.availableNamespaces;
|
||||
});
|
||||
|
||||
it('mirrors REST endpoint response fields', () => {
|
||||
const extractRelevantFields = obj => ({ id: obj.id, full_path: obj.full_path });
|
||||
|
||||
expect(results.map(extractRelevantFields)).toStrictEqual(
|
||||
availableNamespacesFixture.map(extractRelevantFields),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bulkImportSourceGroups', () => {
|
||||
let results;
|
||||
|
||||
beforeEach(async () => {
|
||||
axiosMockAdapter.onGet(FAKE_ENDPOINTS.status).reply(httpStatus.OK, statusEndpointFixture);
|
||||
axiosMockAdapter
|
||||
.onGet(FAKE_ENDPOINTS.availableNamespaces)
|
||||
.reply(httpStatus.OK, availableNamespacesFixture);
|
||||
|
||||
const response = await client.query({ query: bulkImportSourceGroupsQuery });
|
||||
results = response.data.bulkImportSourceGroups;
|
||||
});
|
||||
|
||||
it('mirrors REST endpoint response fields', () => {
|
||||
const MIRRORED_FIELDS = ['id', 'full_name', 'full_path', 'web_url'];
|
||||
expect(
|
||||
results.every((r, idx) =>
|
||||
MIRRORED_FIELDS.every(
|
||||
field => r[field] === statusEndpointFixture.importable_data[idx][field],
|
||||
),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('populates each result instance with status field default to none', () => {
|
||||
expect(results.every(r => r.status === STATUSES.NONE)).toBe(true);
|
||||
});
|
||||
|
||||
it('populates each result instance with import_target defaulted to first available namespace', () => {
|
||||
expect(
|
||||
results.every(
|
||||
r => r.import_target.target_namespace === availableNamespacesFixture[0].full_path,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mutations', () => {
|
||||
let results;
|
||||
const GROUP_ID = 1;
|
||||
|
||||
beforeEach(() => {
|
||||
client.writeQuery({
|
||||
query: bulkImportSourceGroupsQuery,
|
||||
data: {
|
||||
bulkImportSourceGroups: [
|
||||
{
|
||||
__typename: clientTypenames.BulkImportSourceGroup,
|
||||
id: GROUP_ID,
|
||||
status: STATUSES.NONE,
|
||||
web_url: 'https://fake.host/1',
|
||||
full_path: 'fake_group_1',
|
||||
full_name: 'fake_name_1',
|
||||
import_target: {
|
||||
target_namespace: 'root',
|
||||
new_name: 'group1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
client
|
||||
.watchQuery({
|
||||
query: bulkImportSourceGroupsQuery,
|
||||
fetchPolicy: 'cache-only',
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
results = data.bulkImportSourceGroups;
|
||||
});
|
||||
});
|
||||
|
||||
it('setTargetNamespaces updates group target namespace', async () => {
|
||||
const NEW_TARGET_NAMESPACE = 'target';
|
||||
await client.mutate({
|
||||
mutation: setTargetNamespaceMutation,
|
||||
variables: { sourceGroupId: GROUP_ID, targetNamespace: NEW_TARGET_NAMESPACE },
|
||||
});
|
||||
|
||||
expect(results[0].import_target.target_namespace).toBe(NEW_TARGET_NAMESPACE);
|
||||
});
|
||||
|
||||
it('setNewName updates group target name', async () => {
|
||||
const NEW_NAME = 'new';
|
||||
await client.mutate({
|
||||
mutation: setNewNameMutation,
|
||||
variables: { sourceGroupId: GROUP_ID, newName: NEW_NAME },
|
||||
});
|
||||
|
||||
expect(results[0].import_target.new_name).toBe(NEW_NAME);
|
||||
});
|
||||
|
||||
describe('importGroup', () => {
|
||||
it('sets status to SCHEDULING when request initiates', async () => {
|
||||
axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(() => new Promise(() => {}));
|
||||
|
||||
client.mutate({
|
||||
mutation: importGroupMutation,
|
||||
variables: { sourceGroupId: GROUP_ID },
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
const { bulkImportSourceGroups: intermediateResults } = client.readQuery({
|
||||
query: bulkImportSourceGroupsQuery,
|
||||
});
|
||||
|
||||
expect(intermediateResults[0].status).toBe(STATUSES.SCHEDULING);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { clientTypenames } from '~/import_entities/import_groups/graphql/client_factory';
|
||||
|
||||
export const generateFakeEntry = ({ id, status, ...rest }) => ({
|
||||
__typename: clientTypenames.BulkImportSourceGroup,
|
||||
web_url: `https://fake.host/${id}`,
|
||||
full_path: `fake_group_${id}`,
|
||||
full_name: `fake_name_${id}`,
|
||||
import_target: {
|
||||
target_namespace: 'root',
|
||||
new_name: `group${id}`,
|
||||
},
|
||||
id,
|
||||
status,
|
||||
...rest,
|
||||
});
|
||||
|
||||
export const statusEndpointFixture = {
|
||||
importable_data: [
|
||||
{
|
||||
id: 2595438,
|
||||
full_name: 'AutoBreakfast',
|
||||
full_path: 'auto-breakfast',
|
||||
web_url: 'https://gitlab.com/groups/auto-breakfast',
|
||||
},
|
||||
{
|
||||
id: 4347861,
|
||||
full_name: 'GitLab Data',
|
||||
full_path: 'gitlab-data',
|
||||
web_url: 'https://gitlab.com/groups/gitlab-data',
|
||||
},
|
||||
{
|
||||
id: 5723700,
|
||||
full_name: 'GitLab Services',
|
||||
full_path: 'gitlab-services',
|
||||
web_url: 'https://gitlab.com/groups/gitlab-services',
|
||||
},
|
||||
{
|
||||
id: 349181,
|
||||
full_name: 'GitLab-examples',
|
||||
full_path: 'gitlab-examples',
|
||||
web_url: 'https://gitlab.com/groups/gitlab-examples',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const availableNamespacesFixture = [
|
||||
{ id: 24, full_path: 'Commit451' },
|
||||
{ id: 22, full_path: 'gitlab-org' },
|
||||
{ id: 23, full_path: 'gnuwget' },
|
||||
{ id: 25, full_path: 'jashkenas' },
|
||||
];
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
|
||||
import { SourceGroupsManager } from '~/import_entities/import_groups/graphql/services/source_groups_manager';
|
||||
import ImportSourceGroupFragment from '~/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql';
|
||||
import { clientTypenames } from '~/import_entities/import_groups/graphql/client_factory';
|
||||
|
||||
describe('SourceGroupsManager', () => {
|
||||
let manager;
|
||||
let client;
|
||||
|
||||
const getFakeGroup = () => ({
|
||||
__typename: clientTypenames.BulkImportSourceGroup,
|
||||
id: 5,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
client = {
|
||||
readFragment: jest.fn(),
|
||||
writeFragment: jest.fn(),
|
||||
};
|
||||
|
||||
manager = new SourceGroupsManager({ client });
|
||||
});
|
||||
|
||||
it('finds item by group id', () => {
|
||||
const ID = 5;
|
||||
|
||||
const FAKE_GROUP = getFakeGroup();
|
||||
client.readFragment.mockReturnValue(FAKE_GROUP);
|
||||
const group = manager.findById(ID);
|
||||
expect(group).toBe(FAKE_GROUP);
|
||||
expect(client.readFragment).toHaveBeenCalledWith({
|
||||
fragment: ImportSourceGroupFragment,
|
||||
id: defaultDataIdFromObject(getFakeGroup()),
|
||||
});
|
||||
});
|
||||
|
||||
it('updates group with provided function', () => {
|
||||
const UPDATED_GROUP = {};
|
||||
const fn = jest.fn().mockReturnValue(UPDATED_GROUP);
|
||||
manager.update(getFakeGroup(), fn);
|
||||
|
||||
expect(client.writeFragment).toHaveBeenCalledWith({
|
||||
fragment: ImportSourceGroupFragment,
|
||||
id: defaultDataIdFromObject(getFakeGroup()),
|
||||
data: UPDATED_GROUP,
|
||||
});
|
||||
});
|
||||
|
||||
it('updates group by id with provided function', () => {
|
||||
const UPDATED_GROUP = {};
|
||||
const fn = jest.fn().mockReturnValue(UPDATED_GROUP);
|
||||
client.readFragment.mockReturnValue(getFakeGroup());
|
||||
manager.updateById(getFakeGroup().id, fn);
|
||||
|
||||
expect(client.readFragment).toHaveBeenCalledWith({
|
||||
fragment: ImportSourceGroupFragment,
|
||||
id: defaultDataIdFromObject(getFakeGroup()),
|
||||
});
|
||||
|
||||
expect(client.writeFragment).toHaveBeenCalledWith({
|
||||
fragment: ImportSourceGroupFragment,
|
||||
id: defaultDataIdFromObject(getFakeGroup()),
|
||||
data: UPDATED_GROUP,
|
||||
});
|
||||
});
|
||||
|
||||
it('sets import status when group is provided', () => {
|
||||
client.readFragment.mockReturnValue(getFakeGroup());
|
||||
|
||||
const NEW_STATUS = 'NEW_STATUS';
|
||||
manager.setImportStatus(getFakeGroup(), NEW_STATUS);
|
||||
|
||||
expect(client.writeFragment).toHaveBeenCalledWith({
|
||||
fragment: ImportSourceGroupFragment,
|
||||
id: defaultDataIdFromObject(getFakeGroup()),
|
||||
data: {
|
||||
...getFakeGroup(),
|
||||
status: NEW_STATUS,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -384,6 +384,16 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
|
||||
expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
|
||||
context 'when using a non-prefixed access token' do
|
||||
let(:personal_access_token) { create(:personal_access_token, :no_prefix, user: user) }
|
||||
|
||||
it 'returns user' do
|
||||
set_header('HTTP_AUTHORIZATION', "Bearer #{personal_access_token.token}")
|
||||
|
||||
expect(find_user_from_access_token).to eq user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -187,11 +187,17 @@ RSpec.describe Gitlab::Ci::Build::Rules do
|
|||
let(:start_in) { nil }
|
||||
let(:allow_failure) { nil }
|
||||
|
||||
subject { Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure) }
|
||||
subject(:result) do
|
||||
Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure)
|
||||
end
|
||||
|
||||
describe '#build_attributes' do
|
||||
subject(:build_attributes) do
|
||||
result.build_attributes
|
||||
end
|
||||
|
||||
it 'compacts nil values' do
|
||||
expect(subject.build_attributes).to eq(options: {}, when: 'on_success')
|
||||
is_expected.to eq(options: {}, when: 'on_success')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Config::Entry::AllowFailure do
|
||||
let(:entry) { described_class.new(config.deep_dup) }
|
||||
let(:expected_config) { config }
|
||||
|
||||
describe 'validations' do
|
||||
context 'when entry config value is valid' do
|
||||
shared_examples 'valid entry' do
|
||||
describe '#value' do
|
||||
it 'returns key value' do
|
||||
expect(entry.value).to eq(expected_config)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it 'is valid' do
|
||||
expect(entry).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with boolean values' do
|
||||
it_behaves_like 'valid entry' do
|
||||
let(:config) { true }
|
||||
end
|
||||
|
||||
it_behaves_like 'valid entry' do
|
||||
let(:config) { false }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with hash values' do
|
||||
it_behaves_like 'valid entry' do
|
||||
let(:config) { { exit_codes: 137 } }
|
||||
let(:expected_config) { { exit_codes: [137] } }
|
||||
end
|
||||
|
||||
it_behaves_like 'valid entry' do
|
||||
let(:config) { { exit_codes: [42, 137] } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when entry value is not valid' do
|
||||
shared_examples 'invalid entry' do
|
||||
describe '#valid?' do
|
||||
it { expect(entry).not_to be_valid }
|
||||
it { expect(entry.errors).to include(error_message) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it has a wrong type' do
|
||||
let(:config) { [1] }
|
||||
let(:error_message) do
|
||||
'allow failure config should be a hash or a boolean value'
|
||||
end
|
||||
|
||||
it_behaves_like 'invalid entry'
|
||||
end
|
||||
|
||||
context 'with string exit codes' do
|
||||
let(:config) { { exit_codes: 'string' } }
|
||||
let(:error_message) do
|
||||
'allow failure exit codes should be an array of integers or an integer'
|
||||
end
|
||||
|
||||
it_behaves_like 'invalid entry'
|
||||
end
|
||||
|
||||
context 'with array of strings as exit codes' do
|
||||
let(:config) { { exit_codes: ['string 1', 'string 2'] } }
|
||||
let(:error_message) do
|
||||
'allow failure exit codes should be an array of integers or an integer'
|
||||
end
|
||||
|
||||
it_behaves_like 'invalid entry'
|
||||
end
|
||||
|
||||
context 'when it has an extra keys' do
|
||||
let(:config) { { extra: true } }
|
||||
let(:error_message) do
|
||||
'allow failure config contains unknown keys: extra'
|
||||
end
|
||||
|
||||
it_behaves_like 'invalid entry'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -227,6 +227,23 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when bridge config contains exit_codes' do
|
||||
let(:config) do
|
||||
{ script: 'rspec', allow_failure: { exit_codes: [42] } }
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
it 'returns an error message' do
|
||||
expect(subject.errors)
|
||||
.to include(/allow failure should be a boolean value/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#manual_action?' do
|
||||
|
|
|
|||
|
|
@ -670,6 +670,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
|
|||
end
|
||||
|
||||
describe '#ignored?' do
|
||||
before do
|
||||
entry.compose!
|
||||
end
|
||||
|
||||
context 'when job is a manual action' do
|
||||
context 'when it is not specified if job is allowed to fail' do
|
||||
let(:config) do
|
||||
|
|
@ -700,6 +704,16 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
|
|||
expect(entry).not_to be_ignored
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job is dynamically allowed to fail' do
|
||||
let(:config) do
|
||||
{ script: 'deploy', when: 'manual', allow_failure: { exit_codes: 42 } }
|
||||
end
|
||||
|
||||
it 'is not an ignored job' do
|
||||
expect(entry).not_to be_ignored
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job is not a manual action' do
|
||||
|
|
@ -709,6 +723,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
|
|||
it 'is not an ignored job' do
|
||||
expect(entry).not_to be_ignored
|
||||
end
|
||||
|
||||
it 'does not return allow_failure' do
|
||||
expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job is allowed to fail' do
|
||||
|
|
@ -717,6 +735,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
|
|||
it 'is an ignored job' do
|
||||
expect(entry).to be_ignored
|
||||
end
|
||||
|
||||
it 'does not return allow_failure_criteria' do
|
||||
expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job is not allowed to fail' do
|
||||
|
|
@ -725,6 +747,32 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
|
|||
it 'is not an ignored job' do
|
||||
expect(entry).not_to be_ignored
|
||||
end
|
||||
|
||||
it 'does not return allow_failure_criteria' do
|
||||
expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job is dynamically allowed to fail' do
|
||||
let(:config) { { script: 'deploy', allow_failure: { exit_codes: 42 } } }
|
||||
|
||||
it 'is not an ignored job' do
|
||||
expect(entry).not_to be_ignored
|
||||
end
|
||||
|
||||
it 'returns allow_failure_criteria' do
|
||||
expect(entry.value[:allow_failure_criteria]).to match(exit_codes: [42])
|
||||
end
|
||||
|
||||
context 'with ci_allow_failure_with_exit_codes disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_allow_failure_with_exit_codes: false)
|
||||
end
|
||||
|
||||
it 'does not return allow_failure_criteria' do
|
||||
expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -165,6 +165,45 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
|
||||
it { is_expected.to include(options: {}) }
|
||||
end
|
||||
|
||||
context 'with allow_failure' do
|
||||
let(:options) do
|
||||
{ allow_failure_criteria: { exit_codes: [42] } }
|
||||
end
|
||||
|
||||
let(:rules) do
|
||||
[{ if: '$VAR == null', when: 'always' }]
|
||||
end
|
||||
|
||||
let(:attributes) do
|
||||
{
|
||||
name: 'rspec',
|
||||
ref: 'master',
|
||||
options: options,
|
||||
rules: rules
|
||||
}
|
||||
end
|
||||
|
||||
context 'when rules does not override allow_failure' do
|
||||
it { is_expected.to match a_hash_including(options: options) }
|
||||
end
|
||||
|
||||
context 'when rules set allow_failure to true' do
|
||||
let(:rules) do
|
||||
[{ if: '$VAR == null', when: 'always', allow_failure: true }]
|
||||
end
|
||||
|
||||
it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
|
||||
end
|
||||
|
||||
context 'when rules set allow_failure to false' do
|
||||
let(:rules) do
|
||||
[{ if: '$VAR == null', when: 'always', allow_failure: false }]
|
||||
end
|
||||
|
||||
it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bridge?' do
|
||||
|
|
|
|||
|
|
@ -231,6 +231,23 @@ module Gitlab
|
|||
expect(subject[:allow_failure]).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when allow_failure has exit_codes' do
|
||||
let(:config) do
|
||||
YAML.dump(rspec: { script: 'rspec',
|
||||
when: 'manual',
|
||||
allow_failure: { exit_codes: 1 } })
|
||||
end
|
||||
|
||||
it 'is not allowed to fail' do
|
||||
expect(subject[:allow_failure]).to be false
|
||||
end
|
||||
|
||||
it 'saves allow_failure_criteria into options' do
|
||||
expect(subject[:options]).to match(
|
||||
a_hash_including(allow_failure_criteria: { exit_codes: [1] }))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job is not a manual action' do
|
||||
|
|
@ -254,6 +271,22 @@ module Gitlab
|
|||
expect(subject[:allow_failure]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when allow_failure is dynamically specified' do
|
||||
let(:config) do
|
||||
YAML.dump(rspec: { script: 'rspec',
|
||||
allow_failure: { exit_codes: 1 } })
|
||||
end
|
||||
|
||||
it 'is not allowed to fail' do
|
||||
expect(subject[:allow_failure]).to be false
|
||||
end
|
||||
|
||||
it 'saves allow_failure_criteria into options' do
|
||||
expect(subject[:options]).to match(
|
||||
a_hash_including(allow_failure_criteria: { exit_codes: [1] }))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -2494,7 +2527,13 @@ module Gitlab
|
|||
context 'returns errors if job allow_failure parameter is not an boolean' do
|
||||
let(:config) { YAML.dump({ rspec: { script: "test", allow_failure: "string" } }) }
|
||||
|
||||
it_behaves_like 'returns errors', 'jobs:rspec allow failure should be a boolean value'
|
||||
it_behaves_like 'returns errors', 'jobs:rspec allow failure should be a hash or a boolean value'
|
||||
end
|
||||
|
||||
context 'returns errors if job exit_code parameter from allow_failure is not an integer' do
|
||||
let(:config) { YAML.dump({ rspec: { script: "test", allow_failure: { exit_codes: 'string' } } }) }
|
||||
|
||||
it_behaves_like 'returns errors', 'jobs:rspec:allow_failure exit codes should be an array of integers or an integer'
|
||||
end
|
||||
|
||||
context 'returns errors if job stage is not a string' do
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
gitlab: 2
|
||||
},
|
||||
projects_imported: {
|
||||
total: 20,
|
||||
total: 2,
|
||||
gitlab_project: 2,
|
||||
gitlab: 2,
|
||||
github: 2,
|
||||
|
|
@ -248,7 +248,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
gitlab: 1
|
||||
},
|
||||
projects_imported: {
|
||||
total: 10,
|
||||
total: 1,
|
||||
gitlab_project: 1,
|
||||
gitlab: 1,
|
||||
github: 1,
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe CaseSensitivity do
|
||||
describe '.iwhere' do
|
||||
let(:connection) { ActiveRecord::Base.connection }
|
||||
let(:model) do
|
||||
let_it_be(:connection) { ActiveRecord::Base.connection }
|
||||
let_it_be(:model) do
|
||||
Class.new(ActiveRecord::Base) do
|
||||
include CaseSensitivity
|
||||
self.table_name = 'namespaces'
|
||||
end
|
||||
end
|
||||
|
||||
let!(:model_1) { model.create!(path: 'mOdEl-1', name: 'mOdEl 1') }
|
||||
let!(:model_2) { model.create!(path: 'mOdEl-2', name: 'mOdEl 2') }
|
||||
let_it_be(:model_1) { model.create!(path: 'mOdEl-1', name: 'mOdEl 1') }
|
||||
let_it_be(:model_2) { model.create!(path: 'mOdEl-2', name: 'mOdEl 2') }
|
||||
|
||||
it 'finds a single instance by a single attribute regardless of case' do
|
||||
expect(model.iwhere(path: 'MODEL-1')).to contain_exactly(model_1)
|
||||
|
|
@ -28,6 +28,10 @@ RSpec.describe CaseSensitivity do
|
|||
.to contain_exactly(model_1)
|
||||
end
|
||||
|
||||
it 'finds instances by custom Arel attributes' do
|
||||
expect(model.iwhere(model.arel_table[:path] => 'MODEL-1')).to contain_exactly(model_1)
|
||||
end
|
||||
|
||||
it 'builds a query using LOWER' do
|
||||
query = model.iwhere(path: %w(MODEL-1 model-2), name: 'model 1').to_sql
|
||||
expected_query = <<~QRY.strip
|
||||
|
|
|
|||
|
|
@ -2,8 +2,69 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.shared_examples '.find_by_full_path' do
|
||||
describe '.find_by_full_path', :aggregate_failures do
|
||||
it 'finds records by their full path' do
|
||||
expect(described_class.find_by_full_path(record.full_path)).to eq(record)
|
||||
expect(described_class.find_by_full_path(record.full_path.upcase)).to eq(record)
|
||||
end
|
||||
|
||||
it 'returns nil for unknown paths' do
|
||||
expect(described_class.find_by_full_path('unknown')).to be_nil
|
||||
end
|
||||
|
||||
it 'includes route information when loading a record' do
|
||||
control_count = ActiveRecord::QueryRecorder.new do
|
||||
described_class.find_by_full_path(record.full_path)
|
||||
end.count
|
||||
|
||||
expect do
|
||||
described_class.find_by_full_path(record.full_path).route
|
||||
end.not_to exceed_all_query_limit(control_count)
|
||||
end
|
||||
|
||||
context 'with redirect routes' do
|
||||
let_it_be(:redirect_route) { create(:redirect_route, source: record) }
|
||||
|
||||
context 'without follow_redirects option' do
|
||||
it 'does not find records by their redirected path' do
|
||||
expect(described_class.find_by_full_path(redirect_route.path)).to be_nil
|
||||
expect(described_class.find_by_full_path(redirect_route.path.upcase)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with follow_redirects option set to true' do
|
||||
it 'finds records by their canonical path' do
|
||||
expect(described_class.find_by_full_path(record.full_path, follow_redirects: true)).to eq(record)
|
||||
expect(described_class.find_by_full_path(record.full_path.upcase, follow_redirects: true)).to eq(record)
|
||||
end
|
||||
|
||||
it 'finds records by their redirected path' do
|
||||
expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to eq(record)
|
||||
expect(described_class.find_by_full_path(redirect_route.path.upcase, follow_redirects: true)).to eq(record)
|
||||
end
|
||||
|
||||
it 'returns nil for unknown paths' do
|
||||
expect(described_class.find_by_full_path('unknown', follow_redirects: true)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Routable do
|
||||
it_behaves_like '.find_by_full_path' do
|
||||
let_it_be(:record) { create(:group) }
|
||||
end
|
||||
|
||||
it_behaves_like '.find_by_full_path' do
|
||||
let_it_be(:record) { create(:project) }
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Group, 'Routable' do
|
||||
let!(:group) { create(:group, name: 'foo') }
|
||||
let_it_be_with_reload(:group) { create(:group, name: 'foo') }
|
||||
let_it_be(:nested_group) { create(:group, parent: group) }
|
||||
|
||||
describe 'Validations' do
|
||||
it { is_expected.to validate_presence_of(:route) }
|
||||
|
|
@ -59,61 +120,20 @@ RSpec.describe Group, 'Routable' do
|
|||
end
|
||||
|
||||
describe '.find_by_full_path' do
|
||||
let!(:nested_group) { create(:group, parent: group) }
|
||||
|
||||
context 'without any redirect routes' do
|
||||
it { expect(described_class.find_by_full_path(group.to_param)).to eq(group) }
|
||||
it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) }
|
||||
it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) }
|
||||
it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
|
||||
|
||||
it 'includes route information when loading a record' do
|
||||
path = group.to_param
|
||||
control_count = ActiveRecord::QueryRecorder.new { described_class.find_by_full_path(path) }.count
|
||||
|
||||
expect { described_class.find_by_full_path(path).route }.not_to exceed_all_query_limit(control_count)
|
||||
end
|
||||
it_behaves_like '.find_by_full_path' do
|
||||
let_it_be(:record) { group }
|
||||
end
|
||||
|
||||
context 'with redirect routes' do
|
||||
let!(:group_redirect_route) { group.redirect_routes.create!(path: 'bar') }
|
||||
let!(:nested_group_redirect_route) { nested_group.redirect_routes.create!(path: nested_group.path.sub('foo', 'bar')) }
|
||||
it_behaves_like '.find_by_full_path' do
|
||||
let_it_be(:record) { nested_group }
|
||||
end
|
||||
|
||||
context 'without follow_redirects option' do
|
||||
context 'with the given path not matching any route' do
|
||||
it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
|
||||
end
|
||||
it 'does not find projects with a matching path' do
|
||||
project = create(:project)
|
||||
redirect_route = create(:redirect_route, source: project)
|
||||
|
||||
context 'with the given path matching the canonical route' do
|
||||
it { expect(described_class.find_by_full_path(group.to_param)).to eq(group) }
|
||||
it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) }
|
||||
it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) }
|
||||
end
|
||||
|
||||
context 'with the given path matching a redirect route' do
|
||||
it { expect(described_class.find_by_full_path(group_redirect_route.path)).to eq(nil) }
|
||||
it { expect(described_class.find_by_full_path(group_redirect_route.path.upcase)).to eq(nil) }
|
||||
it { expect(described_class.find_by_full_path(nested_group_redirect_route.path)).to eq(nil) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with follow_redirects option set to true' do
|
||||
context 'with the given path not matching any route' do
|
||||
it { expect(described_class.find_by_full_path('unknown', follow_redirects: true)).to eq(nil) }
|
||||
end
|
||||
|
||||
context 'with the given path matching the canonical route' do
|
||||
it { expect(described_class.find_by_full_path(group.to_param, follow_redirects: true)).to eq(group) }
|
||||
it { expect(described_class.find_by_full_path(group.to_param.upcase, follow_redirects: true)).to eq(group) }
|
||||
it { expect(described_class.find_by_full_path(nested_group.to_param, follow_redirects: true)).to eq(nested_group) }
|
||||
end
|
||||
|
||||
context 'with the given path matching a redirect route' do
|
||||
it { expect(described_class.find_by_full_path(group_redirect_route.path, follow_redirects: true)).to eq(group) }
|
||||
it { expect(described_class.find_by_full_path(group_redirect_route.path.upcase, follow_redirects: true)).to eq(group) }
|
||||
it { expect(described_class.find_by_full_path(nested_group_redirect_route.path, follow_redirects: true)).to eq(nested_group) }
|
||||
end
|
||||
end
|
||||
expect(described_class.find_by_full_path(project.full_path)).to be_nil
|
||||
expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -131,8 +151,6 @@ RSpec.describe Group, 'Routable' do
|
|||
end
|
||||
|
||||
context 'with valid paths' do
|
||||
let!(:nested_group) { create(:group, parent: group) }
|
||||
|
||||
it 'returns the projects matching the paths' do
|
||||
result = described_class.where_full_path_in([group.to_param, nested_group.to_param])
|
||||
|
||||
|
|
@ -148,32 +166,36 @@ RSpec.describe Group, 'Routable' do
|
|||
end
|
||||
|
||||
describe '#full_path' do
|
||||
let(:group) { create(:group) }
|
||||
let(:nested_group) { create(:group, parent: group) }
|
||||
|
||||
it { expect(group.full_path).to eq(group.path) }
|
||||
it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") }
|
||||
end
|
||||
|
||||
describe '#full_name' do
|
||||
let(:group) { create(:group) }
|
||||
let(:nested_group) { create(:group, parent: group) }
|
||||
|
||||
it { expect(group.full_name).to eq(group.name) }
|
||||
it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Project, 'Routable' do
|
||||
describe '#full_path' do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
it_behaves_like '.find_by_full_path' do
|
||||
let_it_be(:record) { project }
|
||||
end
|
||||
|
||||
it 'does not find groups with a matching path' do
|
||||
group = create(:group)
|
||||
redirect_route = create(:redirect_route, source: group)
|
||||
|
||||
expect(described_class.find_by_full_path(group.full_path)).to be_nil
|
||||
expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to be_nil
|
||||
end
|
||||
|
||||
describe '#full_path' do
|
||||
it { expect(project.full_path).to eq "#{project.namespace.full_path}/#{project.path}" }
|
||||
end
|
||||
|
||||
describe '#full_name' do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
|
||||
it { expect(project.full_name).to eq "#{project.namespace.human_name} / #{project.name}" }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -105,8 +105,8 @@ RSpec.describe PersonalAccessToken, 'TokenAuthenticatable' do
|
|||
it 'sets new token' do
|
||||
subject
|
||||
|
||||
expect(personal_access_token.token).to eq(token_value)
|
||||
expect(personal_access_token.token_digest).to eq(Gitlab::CryptoHelper.sha256(token_value))
|
||||
expect(personal_access_token.token).to eq("#{PersonalAccessToken.token_prefix}#{token_value}")
|
||||
expect(personal_access_token.token_digest).to eq(Gitlab::CryptoHelper.sha256("#{PersonalAccessToken.token_prefix}#{token_value}"))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@ RSpec.describe API::Internal::Base do
|
|||
end
|
||||
|
||||
it 'returns a token without expiry when the expires_at parameter is missing' do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
secret_token: secret_token,
|
||||
|
|
@ -229,12 +231,14 @@ RSpec.describe API::Internal::Base do
|
|||
}
|
||||
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{20}\z/)
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
|
||||
expect(json_response['expires_at']).to be_nil
|
||||
end
|
||||
|
||||
it 'returns a token with expiry when it receives a valid expires_at parameter' do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
secret_token: secret_token,
|
||||
|
|
@ -245,7 +249,7 @@ RSpec.describe API::Internal::Base do
|
|||
}
|
||||
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{20}\z/)
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
|
||||
expect(json_response['expires_at']).to eq('9001-11-17')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ RSpec.describe API::Settings, 'Settings' do
|
|||
expect(json_response['spam_check_endpoint_url']).to be_nil
|
||||
expect(json_response['wiki_page_max_content_bytes']).to be_a(Integer)
|
||||
expect(json_response['require_admin_approval_after_user_signup']).to eq(true)
|
||||
expect(json_response['personal_access_token_prefix']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -122,7 +123,8 @@ RSpec.describe API::Settings, 'Settings' do
|
|||
spam_check_endpoint_url: 'https://example.com/spam_check',
|
||||
disabled_oauth_sign_in_sources: 'unknown',
|
||||
import_sources: 'github,bitbucket',
|
||||
wiki_page_max_content_bytes: 12345
|
||||
wiki_page_max_content_bytes: 12345,
|
||||
personal_access_token_prefix: "GL-"
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
|
@ -166,6 +168,7 @@ RSpec.describe API::Settings, 'Settings' do
|
|||
expect(json_response['disabled_oauth_sign_in_sources']).to eq([])
|
||||
expect(json_response['import_sources']).to match_array(%w(github bitbucket))
|
||||
expect(json_response['wiki_page_max_content_bytes']).to eq(12345)
|
||||
expect(json_response['personal_access_token_prefix']).to eq("GL-")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -451,5 +454,25 @@ RSpec.describe API::Settings, 'Settings' do
|
|||
expect(json_response['error']).to eq('spam_check_endpoint_url is missing')
|
||||
end
|
||||
end
|
||||
|
||||
context "personal access token prefix settings" do
|
||||
context "handles validation errors" do
|
||||
it "fails to update the settings with too long prefix" do
|
||||
put api("/application/settings", admin), params: { personal_access_token_prefix: "prefix" * 10 }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
message = json_response["message"]
|
||||
expect(message["personal_access_token_prefix"]).to include(a_string_matching("is too long"))
|
||||
end
|
||||
|
||||
it "fails to update the settings with invalid characters in the prefix" do
|
||||
put api("/application/settings", admin), params: { personal_access_token_prefix: "éñ" }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
message = json_response["message"]
|
||||
expect(message["personal_access_token_prefix"]).to include(a_string_matching("can contain only letters of the Base64 alphabet"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -93,6 +93,73 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with allow_failure and exit_codes', :aggregate_failures do
|
||||
def find_job(name)
|
||||
pipeline.builds.find_by(name: name)
|
||||
end
|
||||
|
||||
let(:config) do
|
||||
<<-EOY
|
||||
job-1:
|
||||
script: exit 42
|
||||
allow_failure:
|
||||
exit_codes: 42
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "master"
|
||||
allow_failure: false
|
||||
|
||||
job-2:
|
||||
script: exit 42
|
||||
allow_failure:
|
||||
exit_codes: 42
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "master"
|
||||
allow_failure: true
|
||||
|
||||
job-3:
|
||||
script: exit 42
|
||||
allow_failure:
|
||||
exit_codes: 42
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "master"
|
||||
when: manual
|
||||
EOY
|
||||
end
|
||||
|
||||
it 'creates a pipeline' do
|
||||
expect(pipeline).to be_persisted
|
||||
expect(build_names).to contain_exactly(
|
||||
'job-1', 'job-2', 'job-3'
|
||||
)
|
||||
end
|
||||
|
||||
it 'assigns job:allow_failure values to the builds' do
|
||||
expect(find_job('job-1').allow_failure).to eq(false)
|
||||
expect(find_job('job-2').allow_failure).to eq(true)
|
||||
expect(find_job('job-3').allow_failure).to eq(false)
|
||||
end
|
||||
|
||||
it 'removes exit_codes if allow_failure is specified' do
|
||||
expect(find_job('job-1').options.dig(:allow_failure_criteria)).to be_nil
|
||||
expect(find_job('job-2').options.dig(:allow_failure_criteria)).to be_nil
|
||||
expect(find_job('job-3').options.dig(:allow_failure_criteria, :exit_codes)).to eq([42])
|
||||
end
|
||||
|
||||
context 'with ci_allow_failure_with_exit_codes disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_allow_failure_with_exit_codes: false)
|
||||
end
|
||||
|
||||
it 'does not persist allow_failure_criteria' do
|
||||
expect(pipeline).to be_persisted
|
||||
|
||||
expect(find_job('job-1').options.key?(:allow_failure_criteria)).to be_falsey
|
||||
expect(find_job('job-2').options.key?(:allow_failure_criteria)).to be_falsey
|
||||
expect(find_job('job-3').options.key?(:allow_failure_criteria)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when workflow:rules are used' do
|
||||
|
|
|
|||
|
|
@ -504,6 +504,17 @@ RSpec.shared_examples 'wiki controller actions' do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#git_access' do
|
||||
render_views
|
||||
|
||||
it 'renders the git access page' do
|
||||
get :git_access, params: routing_params
|
||||
|
||||
expect(response).to render_template('shared/wikis/git_access')
|
||||
expect(response.body).to include(wiki.http_url_to_repo)
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_to_wiki(wiki, page)
|
||||
redirect_to(controller.wiki_page_path(wiki, page))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,16 +16,6 @@ RSpec.describe 'shared/wikis/_sidebar.html.haml' do
|
|||
expect(rendered).to have_link('Clone repository')
|
||||
end
|
||||
|
||||
context 'the wiki is not a project wiki' do
|
||||
it 'does not include the clone repository link' do
|
||||
allow(wiki).to receive(:container).and_return(create(:group))
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Clone repository')
|
||||
end
|
||||
end
|
||||
|
||||
context 'the sidebar failed to load' do
|
||||
before do
|
||||
assign(:sidebar_error, Object.new)
|
||||
|
|
|
|||
Loading…
Reference in New Issue